Merge "Moved AGP to compileOnly dependency in Room Gradle Plugin." into androidx-main
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/builtintypes/builtintypes-common/api/current.txt
similarity index 100%
rename from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
rename to appactions/builtintypes/builtintypes-common/api/current.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/appactions/builtintypes/builtintypes-common/api/res-current.txt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/api/res-current.txt
rename to appactions/builtintypes/builtintypes-common/api/res-current.txt
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/builtintypes/builtintypes-common/api/restricted_current.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to appactions/builtintypes/builtintypes-common/api/restricted_current.txt
diff --git a/appactions/builtintypes/builtintypes-core/build.gradle b/appactions/builtintypes/builtintypes-common/build.gradle
similarity index 72%
copy from appactions/builtintypes/builtintypes-core/build.gradle
copy to appactions/builtintypes/builtintypes-common/build.gradle
index fd57aad..2ba7736 100644
--- a/appactions/builtintypes/builtintypes-core/build.gradle
+++ b/appactions/builtintypes/builtintypes-common/build.gradle
@@ -24,16 +24,14 @@
 }
 
 dependencies {
+    api(project(":appactions:builtintypes:builtintypes"))
     api(libs.kotlinStdlib)
 
-    testImplementation(libs.junit)
-    testImplementation(libs.truth)
-
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
+    samples(project(":appactions:builtintypes:builtintypes-common:builtintypes-common-samples"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core"
+    namespace "androidx.appactions.builtintypes.common"
     defaultConfig {
         minSdkVersion 26
     }
@@ -46,8 +44,9 @@
 }
 
 androidx {
-    name = "AppActions Builtin Types Core"
+    name = "AppActions Built-in Types Common"
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2023"
-    description = "This library exposes a core set of data types based on schema.org definitions."
+    description = "This library exposes a set of common data types based on schema.org " +
+            "definitions that are shared amongst the other builtintypes-* artifacts."
 }
diff --git a/appactions/builtintypes/builtintypes-core/samples/build.gradle b/appactions/builtintypes/builtintypes-common/samples/build.gradle
similarity index 84%
copy from appactions/builtintypes/builtintypes-core/samples/build.gradle
copy to appactions/builtintypes/builtintypes-common/samples/build.gradle
index 871e254..cec1ab3 100644
--- a/appactions/builtintypes/builtintypes-core/samples/build.gradle
+++ b/appactions/builtintypes/builtintypes-common/samples/build.gradle
@@ -26,19 +26,19 @@
     api(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation(project(":appactions:builtintypes:builtintypes-core"))
+    implementation(project(":appactions:builtintypes:builtintypes-common"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core.samples"
+    namespace "androidx.appactions.builtintypes.common.samples"
     defaultConfig {
         minSdkVersion 26
     }
 }
 
 androidx {
-    name = "Built-in Types Core Samples"
+    name = "Built-in Types Common Samples"
     type = LibraryType.SAMPLES
     inceptionYear = "2023"
-    description = "Samples for AndroidX Built-in Types Core Library"
+    description = "Samples for AndroidX Built-in Types Common Library"
 }
diff --git a/appactions/builtintypes/builtintypes-common/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-common-documentation.md b/appactions/builtintypes/builtintypes-common/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-common-documentation.md
new file mode 100644
index 0000000..428c273
--- /dev/null
+++ b/appactions/builtintypes/builtintypes-common/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-common-documentation.md
@@ -0,0 +1,3 @@
+# Module root
+
+androidx.appactions.builtintypes builtintypes-common
diff --git a/camera/camera-effects/api/1.3.0-beta01.txt b/appactions/builtintypes/builtintypes-communications/api/current.txt
similarity index 100%
rename from camera/camera-effects/api/1.3.0-beta01.txt
rename to appactions/builtintypes/builtintypes-communications/api/current.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/appactions/builtintypes/builtintypes-communications/api/res-current.txt
similarity index 100%
copy from appactions/builtintypes/builtintypes-core/api/res-current.txt
copy to appactions/builtintypes/builtintypes-communications/api/res-current.txt
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/builtintypes/builtintypes-communications/api/restricted_current.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to appactions/builtintypes/builtintypes-communications/api/restricted_current.txt
diff --git a/appactions/builtintypes/builtintypes-core/build.gradle b/appactions/builtintypes/builtintypes-communications/build.gradle
similarity index 72%
copy from appactions/builtintypes/builtintypes-core/build.gradle
copy to appactions/builtintypes/builtintypes-communications/build.gradle
index fd57aad..9352a5e 100644
--- a/appactions/builtintypes/builtintypes-core/build.gradle
+++ b/appactions/builtintypes/builtintypes-communications/build.gradle
@@ -24,16 +24,14 @@
 }
 
 dependencies {
+    api(project(":appactions:builtintypes:builtintypes"))
     api(libs.kotlinStdlib)
 
-    testImplementation(libs.junit)
-    testImplementation(libs.truth)
-
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
+    samples(project(":appactions:builtintypes:builtintypes-communications:builtintypes-communications-samples"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core"
+    namespace "androidx.appactions.builtintypes.communications"
     defaultConfig {
         minSdkVersion 26
     }
@@ -46,8 +44,9 @@
 }
 
 androidx {
-    name = "AppActions Builtin Types Core"
+    name = "AppActions Built-in Types Communications"
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2023"
-    description = "This library exposes a core set of data types based on schema.org definitions."
+    description = "This library exposes a set of communications related data types based on " +
+            "schema.org definitions."
 }
diff --git a/appactions/builtintypes/builtintypes-core/samples/build.gradle b/appactions/builtintypes/builtintypes-communications/samples/build.gradle
similarity index 82%
copy from appactions/builtintypes/builtintypes-core/samples/build.gradle
copy to appactions/builtintypes/builtintypes-communications/samples/build.gradle
index 871e254..26b7f63 100644
--- a/appactions/builtintypes/builtintypes-core/samples/build.gradle
+++ b/appactions/builtintypes/builtintypes-communications/samples/build.gradle
@@ -26,19 +26,19 @@
     api(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation(project(":appactions:builtintypes:builtintypes-core"))
+    implementation(project(":appactions:builtintypes:builtintypes-communications"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core.samples"
+    namespace "androidx.appactions.builtintypes.communications.samples"
     defaultConfig {
         minSdkVersion 26
     }
 }
 
 androidx {
-    name = "Built-in Types Core Samples"
+    name = "Built-in Types Communications Samples"
     type = LibraryType.SAMPLES
     inceptionYear = "2023"
-    description = "Samples for AndroidX Built-in Types Core Library"
+    description = "Samples for AndroidX Built-in Types Communications Library"
 }
diff --git a/appactions/builtintypes/builtintypes-communications/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-communications-documentation.md b/appactions/builtintypes/builtintypes-communications/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-communications-documentation.md
new file mode 100644
index 0000000..7ab912d
--- /dev/null
+++ b/appactions/builtintypes/builtintypes-communications/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-communications-documentation.md
@@ -0,0 +1,3 @@
+# Module root
+
+androidx.appactions.builtintypes builtintypes-communications
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-core-documentation.md b/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-core-documentation.md
deleted file mode 100644
index 7891548..0000000
--- a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-core-documentation.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Module root
-
-androidx.appactions.builtintypes builtintypes-core
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/builtintypes/builtintypes-productivity/api/current.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to appactions/builtintypes/builtintypes-productivity/api/current.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/appactions/builtintypes/builtintypes-productivity/api/res-current.txt
similarity index 100%
copy from appactions/builtintypes/builtintypes-core/api/res-current.txt
copy to appactions/builtintypes/builtintypes-productivity/api/res-current.txt
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/builtintypes/builtintypes-productivity/api/restricted_current.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to appactions/builtintypes/builtintypes-productivity/api/restricted_current.txt
diff --git a/appactions/builtintypes/builtintypes-core/build.gradle b/appactions/builtintypes/builtintypes-productivity/build.gradle
similarity index 74%
copy from appactions/builtintypes/builtintypes-core/build.gradle
copy to appactions/builtintypes/builtintypes-productivity/build.gradle
index fd57aad..40f2da3 100644
--- a/appactions/builtintypes/builtintypes-core/build.gradle
+++ b/appactions/builtintypes/builtintypes-productivity/build.gradle
@@ -24,16 +24,14 @@
 }
 
 dependencies {
+    api(project(":appactions:builtintypes:builtintypes"))
     api(libs.kotlinStdlib)
 
-    testImplementation(libs.junit)
-    testImplementation(libs.truth)
-
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
+    samples(project(":appactions:builtintypes:builtintypes-productivity:builtintypes-productivity-samples"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core"
+    namespace "androidx.appactions.builtintypes.productivity"
     defaultConfig {
         minSdkVersion 26
     }
@@ -46,8 +44,8 @@
 }
 
 androidx {
-    name = "AppActions Builtin Types Core"
+    name = "AppActions Built-in Types Productivity"
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2023"
-    description = "This library exposes a core set of data types based on schema.org definitions."
+    description = "This library exposes a set of productivity related data types based on schema.org definitions."
 }
diff --git a/appactions/builtintypes/builtintypes-core/samples/build.gradle b/appactions/builtintypes/builtintypes-productivity/samples/build.gradle
similarity index 83%
copy from appactions/builtintypes/builtintypes-core/samples/build.gradle
copy to appactions/builtintypes/builtintypes-productivity/samples/build.gradle
index 871e254..b4c00cb 100644
--- a/appactions/builtintypes/builtintypes-core/samples/build.gradle
+++ b/appactions/builtintypes/builtintypes-productivity/samples/build.gradle
@@ -26,19 +26,19 @@
     api(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation(project(":appactions:builtintypes:builtintypes-core"))
+    implementation(project(":appactions:builtintypes:builtintypes-productivity"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core.samples"
+    namespace "androidx.appactions.builtintypes.productivity.samples"
     defaultConfig {
         minSdkVersion 26
     }
 }
 
 androidx {
-    name = "Built-in Types Core Samples"
+    name = "Built-in Types Productivity Samples"
     type = LibraryType.SAMPLES
     inceptionYear = "2023"
-    description = "Samples for AndroidX Built-in Types Core Library"
+    description = "Samples for AndroidX Built-in Types Productivity Library"
 }
diff --git a/appactions/builtintypes/builtintypes-productivity/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-productivity-documentation.md b/appactions/builtintypes/builtintypes-productivity/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-productivity-documentation.md
new file mode 100644
index 0000000..f4ef348
--- /dev/null
+++ b/appactions/builtintypes/builtintypes-productivity/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-productivity-documentation.md
@@ -0,0 +1,3 @@
+# Module root
+
+androidx.appactions.builtintypes builtintypes-productivity
diff --git a/appactions/builtintypes/builtintypes-core/api/current.txt b/appactions/builtintypes/builtintypes/api/current.txt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/api/current.txt
rename to appactions/builtintypes/builtintypes/api/current.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/appactions/builtintypes/builtintypes/api/res-current.txt
similarity index 100%
copy from appactions/builtintypes/builtintypes-core/api/res-current.txt
copy to appactions/builtintypes/builtintypes/api/res-current.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/restricted_current.txt b/appactions/builtintypes/builtintypes/api/restricted_current.txt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/api/restricted_current.txt
rename to appactions/builtintypes/builtintypes/api/restricted_current.txt
diff --git a/appactions/builtintypes/builtintypes-core/build.gradle b/appactions/builtintypes/builtintypes/build.gradle
similarity index 87%
rename from appactions/builtintypes/builtintypes-core/build.gradle
rename to appactions/builtintypes/builtintypes/build.gradle
index fd57aad..419d53b 100644
--- a/appactions/builtintypes/builtintypes-core/build.gradle
+++ b/appactions/builtintypes/builtintypes/build.gradle
@@ -29,11 +29,11 @@
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
+    samples(project(":appactions:builtintypes:builtintypes:builtintypes-samples"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core"
+    namespace "androidx.appactions.builtintypes"
     defaultConfig {
         minSdkVersion 26
     }
@@ -46,7 +46,7 @@
 }
 
 androidx {
-    name = "AppActions Builtin Types Core"
+    name = "AppActions Built-in Types"
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2023"
     description = "This library exposes a core set of data types based on schema.org definitions."
diff --git a/appactions/builtintypes/builtintypes-core/samples/build.gradle b/appactions/builtintypes/builtintypes/samples/build.gradle
similarity index 85%
rename from appactions/builtintypes/builtintypes-core/samples/build.gradle
rename to appactions/builtintypes/builtintypes/samples/build.gradle
index 871e254..151b129 100644
--- a/appactions/builtintypes/builtintypes-core/samples/build.gradle
+++ b/appactions/builtintypes/builtintypes/samples/build.gradle
@@ -26,19 +26,19 @@
     api(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation(project(":appactions:builtintypes:builtintypes-core"))
+    implementation(project(":appactions:builtintypes:builtintypes"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core.samples"
+    namespace "androidx.appactions.builtintypes.samples"
     defaultConfig {
         minSdkVersion 26
     }
 }
 
 androidx {
-    name = "Built-in Types Core Samples"
+    name = "Built-in Types Samples"
     type = LibraryType.SAMPLES
     inceptionYear = "2023"
-    description = "Samples for AndroidX Built-in Types Core Library"
+    description = "Samples for AndroidX Built-in Types Library"
 }
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ByDaySamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ByDaySamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ByDaySamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ByDaySamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/DisambiguatingDescriptionSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/DisambiguatingDescriptionSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/DisambiguatingDescriptionSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/DisambiguatingDescriptionSamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndDateSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndDateSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndDateSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndDateSamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndTimeSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndTimeSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndTimeSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/EndTimeSamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ExceptDateSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ExceptDateSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ExceptDateSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/ExceptDateSamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/RepeatFrequencySamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/RepeatFrequencySamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/RepeatFrequencySamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/RepeatFrequencySamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartDateSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartDateSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartDateSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartDateSamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartTimeSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartTimeSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartTimeSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/properties/StartTimeSamples.kt
diff --git a/appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/types/DayOfWeekSamples.kt b/appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/types/DayOfWeekSamples.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/samples/src/main/java/androidx/appactions/builtintypes/samples/types/DayOfWeekSamples.kt
rename to appactions/builtintypes/builtintypes/samples/src/main/java/androidx/appactions/builtintypes/samples/types/DayOfWeekSamples.kt
diff --git a/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-documentation.md b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-documentation.md
new file mode 100644
index 0000000..c899946e
--- /dev/null
+++ b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/androidx-appactions-builtintypes-builtintypes-documentation.md
@@ -0,0 +1,3 @@
+# Module root
+
+androidx.appactions.builtintypes builtintypes
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ByDay.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/DisambiguatingDescription.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/EndTime.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/ExceptDate.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/Name.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/Name.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/Name.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/Name.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/RepeatFrequency.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/properties/StartTime.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/DayOfWeek.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ExecutionStatus.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/GenericErrorStatus.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Intangible.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/ObjectCreationLimitReachedStatus.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Person.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Person.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Person.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Person.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Schedule.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/SuccessStatus.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Thing.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Timer.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt b/appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt
rename to appactions/builtintypes/builtintypes/src/main/java/androidx/appactions/builtintypes/types/UnsupportedOperationStatus.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/test/java/androidx/appactions/builtintypes/types/DataTypeTest.kt b/appactions/builtintypes/builtintypes/src/test/java/androidx/appactions/builtintypes/types/DataTypeTest.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/test/java/androidx/appactions/builtintypes/types/DataTypeTest.kt
rename to appactions/builtintypes/builtintypes/src/test/java/androidx/appactions/builtintypes/types/DataTypeTest.kt
diff --git a/appactions/builtintypes/builtintypes-core/src/test/java/androidx/appactions/builtintypes/types/ExtensionTest.kt b/appactions/builtintypes/builtintypes/src/test/java/androidx/appactions/builtintypes/types/ExtensionTest.kt
similarity index 100%
rename from appactions/builtintypes/builtintypes-core/src/test/java/androidx/appactions/builtintypes/types/ExtensionTest.kt
rename to appactions/builtintypes/builtintypes/src/test/java/androidx/appactions/builtintypes/types/ExtensionTest.kt
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 3dcabbf..ce28be8 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
@@ -23,7 +23,6 @@
 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.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
@@ -92,7 +91,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var callFormat: Call.CanonicalValue.CallFormat? = null
             private var participantList: List<ParticipantValue> = mutableListOf()
 
@@ -104,7 +103,7 @@
                 this.participantList = participantList
             }
 
-            override fun build(): Arguments = Arguments(callFormat, participantList)
+            fun build(): Arguments = Arguments(callFormat, participantList)
         }
     }
 
@@ -181,7 +180,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.CALL_FORMAT.path,
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 88f8c82..23f7b24 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
@@ -23,7 +23,6 @@
 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.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
@@ -89,7 +88,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var recipientList: List<RecipientValue> = mutableListOf()
             private var messageText: String? = null
 
@@ -101,7 +100,7 @@
                 this.messageText = messageTextList
             }
 
-            override fun build(): Arguments = Arguments(recipientList, messageText)
+            fun build(): Arguments = Arguments(recipientList, messageText)
         }
     }
 
@@ -179,7 +178,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     SlotMetadata.RECIPIENT.path,
diff --git a/appactions/interaction/interaction-capabilities-core/build.gradle b/appactions/interaction/interaction-capabilities-core/build.gradle
index 91505ce..c0840a5 100644
--- a/appactions/interaction/interaction-capabilities-core/build.gradle
+++ b/appactions/interaction/interaction-capabilities-core/build.gradle
@@ -24,7 +24,7 @@
 
 dependencies {
     api(project(path: ":appactions:interaction:interaction-proto", configuration: "shadowJar"))
-    api(project(":appactions:builtintypes:builtintypes-core"))
+    api(project(":appactions:builtintypes:builtintypes"))
 
     api(libs.autoValueAnnotations)
     implementation(libs.guavaListenableFuture)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt
index 84f058a..2f59407 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.kt
@@ -16,7 +16,6 @@
 
 package androidx.appactions.interaction.capabilities.core.impl.spec
 
-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.SlotTypeConverter
 import androidx.appactions.interaction.capabilities.core.impl.spec.ParamBinding.ArgumentSetter
@@ -29,10 +28,11 @@
 /**
  * A builder for the `ActionSpec`.
  */
-class ActionSpecBuilder<ArgumentsT, ArgumentsBuilderT : BuilderOf<ArgumentsT>, OutputT>
+class ActionSpecBuilder<ArgumentsT, ArgumentsBuilderT, OutputT>
 private constructor(
     private val capabilityName: String,
-    private val argumentBuilderSupplier: Supplier<ArgumentsBuilderT>
+    private val argumentBuilderSupplier: Supplier<ArgumentsBuilderT>,
+    private val builderFinalizer: Function<ArgumentsBuilderT, ArgumentsT>
 ) {
     private val paramBindingList: MutableList<ParamBinding<ArgumentsT, ArgumentsBuilderT>> =
         ArrayList()
@@ -40,18 +40,22 @@
 
     /** Sets the argument type and its builder and returns a new `ActionSpecBuilder`.  */
     @Suppress("UNUSED_PARAMETER")
-    fun <NewArgumentsT, NewArgumentsBuilderT : BuilderOf<NewArgumentsT>> setArguments(
+    fun <NewArgumentsT, NewArgumentsBuilderT> setArguments(
         unused: Class<NewArgumentsT>,
-        argumentBuilderSupplier: Supplier<NewArgumentsBuilderT>
+        argumentBuilderSupplier: Supplier<NewArgumentsBuilderT>,
+        builderFinalizer: Function<NewArgumentsBuilderT, NewArgumentsT>
     ): ActionSpecBuilder<NewArgumentsT, NewArgumentsBuilderT, OutputT> {
-        return ActionSpecBuilder(this.capabilityName, argumentBuilderSupplier)
+        return ActionSpecBuilder(this.capabilityName, argumentBuilderSupplier, builderFinalizer)
     }
 
     @Suppress("UNUSED_PARAMETER")
     fun <NewOutputT> setOutput(
         unused: Class<NewOutputT>
     ): ActionSpecBuilder<ArgumentsT, ArgumentsBuilderT, NewOutputT> {
-        return ActionSpecBuilder(this.capabilityName, this.argumentBuilderSupplier)
+        return ActionSpecBuilder(this.capabilityName,
+            this.argumentBuilderSupplier,
+            this.builderFinalizer
+        )
     }
 
     /**
@@ -172,7 +176,8 @@
             capabilityName,
             argumentBuilderSupplier,
             paramBindingList.toList(),
-            outputBindings.toMap()
+            outputBindings.toMap(),
+            builderFinalizer
         )
     }
 
@@ -183,8 +188,10 @@
          */
         fun ofCapabilityNamed(
             capabilityName: String
-        ): ActionSpecBuilder<Any, BuilderOf<Any>, Any> {
-            return ActionSpecBuilder(capabilityName) { BuilderOf { Object() } }
+        ): ActionSpecBuilder<Any, Any, Any> {
+            return ActionSpecBuilder(capabilityName, { Supplier { Object() } }) {
+                Function<Any, Any> { Object() }
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt
index 1c59144..f8055fc 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.kt
@@ -16,7 +16,6 @@
 
 package androidx.appactions.interaction.capabilities.core.impl.spec
 
-import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
 import androidx.appactions.interaction.proto.FulfillmentResponse
@@ -26,11 +25,12 @@
 import java.util.function.Supplier
 
 /** The implementation of `ActionSpec` interface.  */
-internal class ActionSpecImpl<ArgumentsT, ArgumentsBuilderT : BuilderOf<ArgumentsT>, OutputT>(
+internal class ActionSpecImpl<ArgumentsT, ArgumentsBuilderT, OutputT>(
     private val capabilityName: String,
     private val argumentBuilderSupplier: Supplier<ArgumentsBuilderT>,
     private val paramBindingList: List<ParamBinding<ArgumentsT, ArgumentsBuilderT>>,
-    private val outputBindings: Map<String, Function<OutputT, List<ParamValue>>>
+    private val outputBindings: Map<String, Function<OutputT, List<ParamValue>>>,
+    private val builderFinalizer: Function<ArgumentsBuilderT, ArgumentsT>
 ) : ActionSpec<ArgumentsT, OutputT> {
     override fun createAppAction(
         identifier: String,
@@ -62,7 +62,7 @@
                 )
             }
         }
-        return argumentBuilder.build()
+        return builderFinalizer.apply(argumentBuilder)
     }
 
     override fun convertOutputToProto(output: OutputT): FulfillmentResponse.StructuredOutput {
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt
index c8cd221..822de42 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.kt
@@ -16,11 +16,10 @@
 
 package androidx.appactions.interaction.capabilities.core.impl.spec
 
-import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
 import androidx.appactions.interaction.proto.ParamValue
 
-data class ParamBinding<ArgumentsT, ArgumentsBuilderT : BuilderOf<ArgumentsT>>
+data class ParamBinding<ArgumentsT, ArgumentsBuilderT>
 internal constructor(
     val name: String,
     val argumentSetter: ArgumentSetter<ArgumentsBuilderT>
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
index 0ee8fa3..cfd73af 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
@@ -341,7 +341,7 @@
     companion object {
         val ACTION_SPEC: ActionSpec<Arguments, Output> =
             ActionSpecBuilder.ofCapabilityNamed("actions.intent.TEST")
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "requiredString",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
index 6948b25..645a4bb3 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
@@ -50,7 +50,7 @@
 public final class ActionSpecTest {
     private static final ActionSpec<Arguments, Output> ACTION_SPEC =
             ActionSpecBuilder.Companion.ofCapabilityNamed("actions.intent.TEST")
-                    .setArguments(Arguments.class, Arguments.Builder::new)
+                    .setArguments(Arguments.class, Arguments.Builder::new, Arguments.Builder::build)
                     .setOutput(Output.class)
                     .bindParameter(
                             "requiredString",
@@ -106,7 +106,9 @@
 
     private static final ActionSpec<GenericEntityArguments, Output> GENERIC_TYPES_ACTION_SPEC =
             ActionSpecBuilder.Companion.ofCapabilityNamed("actions.intent.TEST")
-                    .setArguments(GenericEntityArguments.class, GenericEntityArguments.Builder::new)
+                    .setArguments(GenericEntityArguments.class,
+                            GenericEntityArguments.Builder::new,
+                            GenericEntityArguments.Builder::build)
                     .setOutput(Output.class)
                     .bindParameter(
                             "requiredEntity",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt
index 2c67156..2d1ba61 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilityImplTest.kt
@@ -1642,7 +1642,7 @@
             ActionSpecBuilder.ofCapabilityNamed(
                 CAPABILITY_NAME
             )
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "required",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.kt
index 477ffd7..e700f8e 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.kt
@@ -16,8 +16,6 @@
 
 package androidx.appactions.interaction.capabilities.core.testing.spec
 
-import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
-
 class Arguments internal constructor(
     val requiredStringField: String?,
     val optionalStringField: String?,
@@ -49,7 +47,7 @@
         return result
     }
 
-    class Builder : BuilderOf<Arguments> {
+    class Builder {
         private var requiredStringField: String? = null
         private var optionalStringField: String? = null
         private var repeatedStringField: List<String> = listOf()
@@ -63,7 +61,7 @@
         fun setRepeatedStringField(repeatedStringField: List<String>): Builder =
             apply { this.repeatedStringField = repeatedStringField }
 
-        override fun build(): Arguments =
+        fun build(): Arguments =
             Arguments(requiredStringField, optionalStringField, repeatedStringField)
     }
 }
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt
index 878980f..0760934 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.kt
@@ -19,7 +19,6 @@
 import androidx.appactions.builtintypes.experimental.types.ListItem
 import androidx.appactions.interaction.capabilities.core.AppEntityListener
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
-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
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -55,7 +54,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var listItem: ListItem? = null
             private var string: String? = null
 
@@ -65,7 +64,7 @@
             fun setAnyString(stringSlotB: String): Builder =
                 apply { this.string = stringSlotB }
 
-            override fun build(): Arguments = Arguments(listItem, string)
+            fun build(): Arguments = Arguments(listItem, string)
         }
     }
 
@@ -79,7 +78,7 @@
 
     companion object {
         val ACTION_SPEC = ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-            .setArguments(Arguments::class.java, Arguments::Builder)
+            .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
             .setOutput(Output::class.java)
             .bindParameter(
                 "listItem",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt
index 3b1766f..19feb85 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.kt
@@ -18,7 +18,6 @@
 
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 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
 
@@ -53,7 +52,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var stringSlotA: String? = null
             private var stringSlotB: String? = null
 
@@ -63,7 +62,7 @@
             fun setStringSlotB(stringSlotB: String): Builder =
                 apply { this.stringSlotB = stringSlotB }
 
-            override fun build(): Arguments = Arguments(stringSlotA, stringSlotB)
+            fun build(): Arguments = Arguments(stringSlotA, stringSlotB)
         }
     }
 
@@ -75,7 +74,7 @@
 
     companion object {
         val ACTION_SPEC = ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-            .setArguments(Arguments::class.java, Arguments::Builder)
+            .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
             .setOutput(Output::class.java)
             .bindParameter(
                 "stringSlotA",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/GenericEntityArguments.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/GenericEntityArguments.kt
index 5c61176..d150ee3 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/GenericEntityArguments.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/GenericEntityArguments.kt
@@ -16,8 +16,6 @@
 
 package androidx.appactions.interaction.capabilities.core.testing.spec
 
-import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
-
 class GenericEntityArguments internal constructor(
     val singularField: TestEntity?,
     val optionalField: TestEntity?,
@@ -49,7 +47,7 @@
         return result
     }
 
-    class Builder : BuilderOf<GenericEntityArguments> {
+    class Builder {
         private var singularField: TestEntity? = null
         private var optionalField: TestEntity? = null
         private var repeatedField: List<TestEntity> = listOf()
@@ -63,7 +61,7 @@
         fun setRepeatedField(repeatedField: List<TestEntity>): Builder =
             apply { this.repeatedField = repeatedField }
 
-        override fun build(): GenericEntityArguments =
+        fun build(): GenericEntityArguments =
             GenericEntityArguments(singularField, optionalField, repeatedField)
     }
 }
\ No newline at end of file
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 9334c2e..103466c 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
@@ -19,7 +19,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -78,7 +77,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var startTime: LocalTime? = null
             private var endTime: LocalTime? = null
 
@@ -88,7 +87,7 @@
             fun setEndTime(endTime: LocalTime): Builder =
                 apply { this.endTime = endTime }
 
-            override fun build(): Arguments = Arguments(startTime, endTime)
+            fun build(): Arguments = Arguments(startTime, endTime)
         }
     }
 
@@ -104,7 +103,8 @@
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(
                     Arguments::class.java,
-                    Arguments::Builder
+                    Arguments::Builder,
+                    Arguments.Builder::build
                 )
                 .setOutput(Output::class.java)
                 .bindParameter(
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 9f7ddd53..5f93001 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
@@ -19,7 +19,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -83,7 +82,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var startTime: LocalTime? = null
             private var endTime: LocalTime? = null
 
@@ -93,7 +92,7 @@
             fun setEndTime(endTime: LocalTime): Builder =
                 apply { this.endTime = endTime }
 
-            override fun build(): Arguments = Arguments(startTime, endTime)
+            fun build(): Arguments = Arguments(startTime, endTime)
         }
     }
 
@@ -109,7 +108,8 @@
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(
                     Arguments::class.java,
-                    Arguments::Builder
+                    Arguments::Builder,
+                    Arguments.Builder::build
                 )
                 .setOutput(Output::class.java)
                 .bindParameter(
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 2d3143a..51856a7 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
@@ -19,7 +19,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -71,13 +70,13 @@
             return name.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var name: String? = null
 
             fun setName(name: String): Builder =
                 apply { this.name = name }
 
-            override fun build(): Arguments = Arguments(name)
+            fun build(): Arguments = Arguments(name)
         }
     }
 
@@ -91,7 +90,7 @@
         // TODO(b/273602015): Update to use Name property from builtintype library.
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.NAME.path,
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 974fe21..2321ea7 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
@@ -19,7 +19,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -71,13 +70,13 @@
             return name.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var name: String? = null
 
             fun setName(name: String): Builder =
                 apply { this.name = name }
 
-            override fun build(): Arguments = Arguments(name)
+            fun build(): Arguments = Arguments(name)
         }
     }
 
@@ -91,7 +90,7 @@
         // TODO(b/273602015): Update to use Name property from builtintype library.
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.NAME.path,
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 92f8bbd..fe45625 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
@@ -19,7 +19,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -83,7 +82,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var duration: Duration? = null
             private var name: String? = null
 
@@ -93,7 +92,7 @@
             fun setName(name: String): Builder =
                 apply { this.name = name }
 
-            override fun build(): Arguments = Arguments(duration, name)
+            fun build(): Arguments = Arguments(duration, name)
         }
     }
 
@@ -107,7 +106,7 @@
         // TODO(b/273602015): Update to use Name property from builtintype library.
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.DURATION.path,
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 0421d7d..31c7fe9 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
@@ -19,7 +19,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -71,13 +70,13 @@
             return name.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var name: String? = null
 
             fun setName(name: String): Builder =
                 apply { this.name = name }
 
-            override fun build(): Arguments = Arguments(name)
+            fun build(): Arguments = Arguments(name)
         }
     }
 
@@ -91,7 +90,7 @@
         // TODO(b/273602015): Update to use Name property from builtintype library.
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.NAME.path,
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/AlarmValue.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/AlarmValue.kt
new file mode 100644
index 0000000..a3a8d6a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/AlarmValue.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.productivity
+
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.interaction.capabilities.core.SearchAction
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
+import androidx.appactions.interaction.capabilities.core.impl.converters.UnionTypeSpec
+import androidx.appactions.interaction.capabilities.serializers.types.ALARM_TYPE_SPEC
+import java.util.Objects
+
+class AlarmValue private constructor(val asAlarm: Alarm?, val asAlarmFilter: SearchAction<Alarm>?) {
+    constructor(alarm: Alarm) : this(alarm, null)
+
+    // TODO(b/268071906) add AlarmFilter type to SearchAction
+    constructor(alarmFilter: SearchAction<Alarm>) : this(null, alarmFilter)
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as AlarmValue
+
+        return asAlarm == other.asAlarm && asAlarmFilter == other.asAlarmFilter
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(asAlarm, asAlarmFilter)
+    }
+
+    companion object {
+        private val TYPE_SPEC =
+            UnionTypeSpec.Builder<AlarmValue>()
+                .bindMemberType(
+                    memberGetter = AlarmValue::asAlarm,
+                    ctor = { AlarmValue(it) },
+                    typeSpec = ALARM_TYPE_SPEC,
+                )
+                .bindMemberType(
+                    memberGetter = AlarmValue::asAlarmFilter,
+                    ctor = { AlarmValue(it) },
+                    typeSpec =
+                        TypeConverters.createSearchActionTypeSpec(
+                            ALARM_TYPE_SPEC
+                        ),
+                )
+                .build()
+        internal val PARAM_VALUE_CONVERTER = ParamValueConverter.of(TYPE_SPEC)
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt
new file mode 100644
index 0000000..b449a2c
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarm.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.appactions.interaction.capabilities.productivity
+
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.builtintypes.types.GenericErrorStatus
+import androidx.appactions.builtintypes.types.ObjectCreationLimitReachedStatus
+import androidx.appactions.builtintypes.types.Schedule
+import androidx.appactions.builtintypes.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.converters.EntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.core.properties.StringValue
+import androidx.appactions.interaction.capabilities.serializers.types.ALARM_TYPE_SPEC
+import androidx.appactions.interaction.capabilities.serializers.types.SCHEDULE_TYPE_SPEC
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+
+private const val CAPABILITY_NAME = "actions.intent.CREATE_ALARM"
+
+/** A capability corresponding to actions.intent.CREATE_ALARM */
+@CapabilityFactory(name = CAPABILITY_NAME)
+class CreateAlarm private constructor() {
+    internal enum class SlotMetadata(val path: String) {
+        SCHEDULE("alarm.alarmSchedule"),
+        NAME("alarm.name"),
+        IDENTIFIER("alarm.identifier")
+    }
+
+    class CapabilityBuilder :
+        Capability.Builder<
+            CapabilityBuilder,
+            Arguments,
+            Output,
+            Confirmation,
+            ExecutionSession
+            >(ACTION_SPEC) {
+        fun setIdentifierProperty(
+            identifier: Property<StringValue>
+        ): CapabilityBuilder = setProperty(
+            SlotMetadata.IDENTIFIER.path,
+            identifier,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
+
+        fun setNameProperty(name: Property<StringValue>): CapabilityBuilder = setProperty(
+            SlotMetadata.NAME.path,
+            name,
+            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+        )
+
+        fun setScheduleProperty(
+            alarmSchedule: Property<Schedule>
+        ): CapabilityBuilder = setProperty(
+            SlotMetadata.SCHEDULE.path,
+            alarmSchedule,
+            EntityConverter.of(SCHEDULE_TYPE_SPEC)
+        )
+    }
+
+    class Arguments internal constructor(
+        val identifier: String?,
+        val name: String?,
+        val schedule: Schedule?
+    ) {
+        override fun toString(): String {
+            return "Arguments(identifier=$identifier,name=$name,schedule=$schedule)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as Arguments
+
+            if (identifier != other.identifier) return false
+            if (name != other.name) return false
+            if (schedule != other.schedule) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = identifier.hashCode()
+            result += 31 * name.hashCode()
+            result += 31 * schedule.hashCode()
+            return result
+        }
+
+        class Builder {
+            private var identifier: String? = null
+            private var name: String? = null
+            private var schedule: Schedule? = null
+
+            fun setIdentifier(identifier: String): Builder = apply { this.identifier = identifier }
+
+            fun setName(name: String): Builder = apply { this.name = name }
+
+            fun setSchedule(schedule: Schedule): Builder = apply { this.schedule = schedule }
+
+            fun build(): Arguments = Arguments(identifier, name, schedule)
+        }
+    }
+
+    class Output internal constructor(val alarm: Alarm?, val executionStatus: ExecutionStatus?) {
+        override fun toString(): String {
+            return "Output(alarm=$alarm,executionStatus=$executionStatus)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as Output
+
+            if (alarm != other.alarm) return false
+            if (executionStatus != other.executionStatus) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = alarm.hashCode()
+            result += 31 * executionStatus.hashCode()
+            return result
+        }
+
+        class Builder {
+            private var alarm: Alarm? = null
+            private var executionStatus: ExecutionStatus? = null
+
+            fun setAlarm(alarm: Alarm): Builder = apply { this.alarm = alarm }
+
+            fun setExecutionStatus(executionStatus: ExecutionStatus) = apply {
+                this.executionStatus = executionStatus
+            }
+
+            fun setExecutionStatus(successStatus: SuccessStatus) = apply {
+                this.setExecutionStatus(ExecutionStatus(successStatus))
+            }
+
+            fun setExecutionStatus(genericErrorStatus: GenericErrorStatus) = apply {
+                this.setExecutionStatus(ExecutionStatus(genericErrorStatus))
+            }
+
+            fun setExecutionStatus(
+                objectCreationLimitReachedStatus: ObjectCreationLimitReachedStatus
+            ) = apply {
+                    this.setExecutionStatus(ExecutionStatus(objectCreationLimitReachedStatus))
+            }
+
+            fun build(): Output = Output(alarm, executionStatus)
+        }
+    }
+
+    class ExecutionStatus {
+        private var successStatus: SuccessStatus? = null
+        private var genericErrorStatus: GenericErrorStatus? = null
+        private var objectCreationLimitReachedStatus: ObjectCreationLimitReachedStatus? = null
+
+        constructor(successStatus: SuccessStatus) {
+            this.successStatus = successStatus
+        }
+
+        constructor(genericErrorStatus: GenericErrorStatus) {
+            this.genericErrorStatus = genericErrorStatus
+        }
+
+        constructor(objectCreationLimitReachedStatus: ObjectCreationLimitReachedStatus) {
+            this.objectCreationLimitReachedStatus = objectCreationLimitReachedStatus
+        }
+
+        internal fun toParamValue(): ParamValue {
+            var status: String = ""
+            if (successStatus != null) {
+                status = successStatus.toString()
+            }
+            if (genericErrorStatus != null) {
+                status = genericErrorStatus.toString()
+            }
+            if (objectCreationLimitReachedStatus != null) {
+                status = objectCreationLimitReachedStatus.toString()
+            }
+            val value: Value = Value.newBuilder().setStringValue(status).build()
+            return ParamValue.newBuilder()
+                .setStructValue(
+                    Struct.newBuilder().putFields(TypeConverters.FIELD_NAME_TYPE, value).build()
+                )
+                .build()
+        }
+    }
+
+    sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+    class Confirmation internal constructor()
+
+    companion object {
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
+                .setOutput(Output::class.java)
+                .bindParameter(
+                    SlotMetadata.IDENTIFIER.path,
+                    Arguments.Builder::setIdentifier,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
+                )
+                .bindParameter(
+                    SlotMetadata.NAME.path,
+                    Arguments.Builder::setName,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER
+                )
+                .bindParameter(
+                    SlotMetadata.SCHEDULE.path,
+                    Arguments.Builder::setSchedule,
+                    ParamValueConverter.of(SCHEDULE_TYPE_SPEC)
+                )
+                .bindOutput(
+                    "alarm",
+                    Output::alarm,
+                    ParamValueConverter.of(ALARM_TYPE_SPEC)::toParamValue
+                )
+                .bindOutput(
+                    "executionStatus",
+                    Output::executionStatus,
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt
new file mode 100644
index 0000000..9b5d1da
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarm.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.appactions.interaction.capabilities.productivity
+
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.builtintypes.types.GenericErrorStatus
+import androidx.appactions.builtintypes.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.converters.EntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.serializers.types.ALARM_TYPE_SPEC
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+
+private const val CAPABILITY_NAME = "actions.intent.DISMISS_ALARM"
+
+/** A capability corresponding to actions.intent.DISMISS_ALARM */
+@CapabilityFactory(name = CAPABILITY_NAME)
+class DismissAlarm private constructor() {
+    internal enum class SlotMetadata(val path: String) {
+        ALARM("alarm")
+    }
+
+    class CapabilityBuilder : Capability.Builder<
+        CapabilityBuilder,
+        Arguments,
+        Output,
+        Confirmation,
+        ExecutionSession
+        >(ACTION_SPEC) {
+        fun setAlarmProperty(alarm: Property<Alarm>): CapabilityBuilder = setProperty(
+            SlotMetadata.ALARM.path,
+            alarm,
+            EntityConverter.of(ALARM_TYPE_SPEC)
+        )
+    }
+
+    class Arguments internal constructor(val alarm: AlarmValue?) {
+        override fun toString(): String {
+            return "Arguments(alarm=$alarm)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as Arguments
+
+            if (alarm != other.alarm) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return alarm.hashCode()
+        }
+
+        class Builder {
+            private var alarm: AlarmValue? = null
+
+            fun setAlarm(alarm: AlarmValue): Builder = apply { this.alarm = alarm }
+
+            fun build(): Arguments = Arguments(alarm)
+        }
+    }
+    class Output internal constructor(val executionStatus: ExecutionStatus?) {
+        override fun toString(): String {
+            return "Output(executionStatus=$executionStatus)"
+        }
+
+        override fun hashCode(): Int {
+            return executionStatus.hashCode()
+        }
+
+        class Builder {
+            private var executionStatus: ExecutionStatus? = null
+
+            fun setExecutionStatus(executionStatus: ExecutionStatus): Builder = apply {
+                this.executionStatus = executionStatus
+            }
+
+            fun setExecutionStatus(successStatus: SuccessStatus) = apply {
+                this.setExecutionStatus(ExecutionStatus(successStatus))
+            }
+
+            fun setExecutionStatus(genericErrorStatus: GenericErrorStatus) = apply {
+                this.setExecutionStatus(ExecutionStatus(genericErrorStatus))
+            }
+
+            fun build(): Output = Output(executionStatus)
+        }
+    }
+
+    class ExecutionStatus {
+        private var successStatus: SuccessStatus? = null
+        private var genericErrorStatus: GenericErrorStatus? = null
+
+        constructor(successStatus: SuccessStatus) {
+            this.successStatus = successStatus
+        }
+
+        constructor(genericErrorStatus: GenericErrorStatus) {
+            this.genericErrorStatus = genericErrorStatus
+        }
+
+        internal fun toParamValue(): ParamValue {
+            var status: String = ""
+            if (successStatus != null) {
+                status = successStatus.toString()
+            }
+            if (genericErrorStatus != null) {
+                status = genericErrorStatus.toString()
+            }
+            val value: Value = Value.newBuilder().setStringValue(status).build()
+            return ParamValue.newBuilder()
+                .setStructValue(
+                    Struct.newBuilder().putFields(TypeConverters.FIELD_NAME_TYPE, value).build()
+                )
+                .build()
+        }
+    }
+
+    sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+    class Confirmation internal constructor()
+
+    companion object {
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
+                .setOutput(Output::class.java)
+                .bindParameter(
+                    SlotMetadata.ALARM.path,
+                    Arguments.Builder::setAlarm,
+                    AlarmValue.PARAM_VALUE_CONVERTER
+                )
+                .bindOutput(
+                    "executionStatus",
+                    Output::executionStatus,
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
+}
\ No newline at end of file
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 a87a028..14f1cf4c 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
@@ -22,7 +22,6 @@
 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.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -79,14 +78,14 @@
             return timerList.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var timerList: List<TimerValue>? = null
 
             fun setTimerList(timerList: List<TimerValue>): Builder = apply {
                 this.timerList = timerList
             }
 
-            override fun build(): Arguments = Arguments(timerList)
+            fun build(): Arguments = Arguments(timerList)
         }
     }
 
@@ -157,7 +156,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     SlotMetadata.TIMER.path,
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 c808cdb..d4e20ed 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
@@ -22,7 +22,6 @@
 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.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -76,14 +75,14 @@
             return timerList.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var timerList: List<TimerValue>? = null
 
             fun setTimerList(
                 timerList: List<TimerValue>
             ): Builder = apply { this.timerList = timerList }
 
-            override fun build(): Arguments = Arguments(timerList)
+            fun build(): Arguments = Arguments(timerList)
         }
     }
 
@@ -154,7 +153,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     SlotMetadata.TIMER.path,
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 46b8712..d069208 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
@@ -22,7 +22,6 @@
 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.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -76,14 +75,14 @@
             return timerList.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var timerList: List<TimerValue>? = null
 
             fun setTimerList(
                 timerList: List<TimerValue>
             ): Builder = apply { this.timerList = timerList }
 
-            override fun build(): Arguments = Arguments(timerList)
+            fun build(): Arguments = Arguments(timerList)
         }
     }
 
@@ -154,7 +153,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     SlotMetadata.TIMER.path,
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt
new file mode 100644
index 0000000..2c98081
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarm.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.appactions.interaction.capabilities.productivity
+
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.builtintypes.types.GenericErrorStatus
+import androidx.appactions.builtintypes.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.converters.EntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.serializers.types.ALARM_TYPE_SPEC
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+import java.time.Duration
+
+private const val CAPABILITY_NAME = "actions.intent.SNOOZE_ALARM"
+
+/** A capability corresponding to actions.intent.SNOOZE_ALARM */
+@CapabilityFactory(name = CAPABILITY_NAME)
+class SnoozeAlarm private constructor() {
+    internal enum class SlotMetadata(val path: String) {
+        DURATION("snoozeDuration"),
+        ALARM("alarm")
+    }
+
+    class CapabilityBuilder :
+            Capability.Builder<
+                    CapabilityBuilder,
+                    Arguments,
+                    Output,
+                    Confirmation,
+                    ExecutionSession
+                    >(ACTION_SPEC) {
+                        fun setSnoozeDurationProperty(duration: Property<Duration>):
+                                CapabilityBuilder = setProperty(
+                                        SlotMetadata.DURATION.path,
+                                        duration,
+                                        TypeConverters.DURATION_ENTITY_CONVERTER
+                                )
+                        fun setTargetAlarmProperty(alarm: Property<Alarm>):
+                                CapabilityBuilder = setProperty(
+                                        SlotMetadata.ALARM.path,
+                                        alarm,
+                                        EntityConverter.of(ALARM_TYPE_SPEC)
+                                )
+    }
+
+    class Arguments
+    internal constructor(val snoozeDuration: Duration?, val targetAlarm: AlarmValue?) {
+        override fun toString(): String {
+            return "Arguments(snoozeDuration=$snoozeDuration,targetAlarm=$targetAlarm)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as Arguments
+
+            if (snoozeDuration != other.snoozeDuration) return false
+            if (targetAlarm != other.targetAlarm) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            var result = snoozeDuration.hashCode()
+            result += 31 * targetAlarm.hashCode()
+            return result
+        }
+
+        class Builder {
+            private var snoozeDuration: Duration? = null
+            private var targetAlarm: AlarmValue? = null
+
+            fun setSnoozeDuration(snoozeDuration: Duration): Builder = apply {
+                this.snoozeDuration = snoozeDuration
+            }
+
+            fun setTargetAlarm(targetAlarm: AlarmValue): Builder = apply {
+                this.targetAlarm = targetAlarm
+            }
+
+            fun build(): Arguments = Arguments(snoozeDuration, targetAlarm)
+        }
+    }
+
+    class Output internal constructor(val executionStatus: ExecutionStatus?) {
+        override fun toString(): String {
+            return "Output(executionStatus=$executionStatus)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+
+            other as Output
+
+            if (executionStatus != other.executionStatus) return false
+
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return executionStatus.hashCode()
+        }
+
+        class Builder {
+            private var executionStatus: ExecutionStatus? = null
+
+            fun setExecutionStatus(executionStatus: ExecutionStatus): Builder = apply {
+                this.executionStatus = executionStatus
+            }
+
+            fun setExecutionStatus(successStatus: SuccessStatus) = apply {
+                this.setExecutionStatus(ExecutionStatus(successStatus))
+            }
+
+            fun setExecutionStatus(genericErrorStatus: GenericErrorStatus) = apply {
+                this.setExecutionStatus(ExecutionStatus(genericErrorStatus))
+            }
+
+            fun build(): Output = Output(executionStatus)
+        }
+    }
+
+    class ExecutionStatus {
+        private var successStatus: SuccessStatus? = null
+        private var genericErrorStatus: GenericErrorStatus? = null
+
+        constructor(successStatus: SuccessStatus) {
+            this.successStatus = successStatus
+        }
+
+        constructor(genericErrorStatus: GenericErrorStatus) {
+            this.genericErrorStatus = genericErrorStatus
+        }
+
+        internal fun toParamValue(): ParamValue {
+            var status: String = ""
+            if (successStatus != null) {
+                status = successStatus.toString()
+            }
+            if (genericErrorStatus != null) {
+                status = genericErrorStatus.toString()
+            }
+            val value: Value = Value.newBuilder().setStringValue(status).build()
+            return ParamValue.newBuilder().setStructValue(
+                    Struct.newBuilder().putFields(TypeConverters.FIELD_NAME_TYPE, value).build()
+                )
+                .build()
+        }
+    }
+
+    sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+    class Confirmation internal constructor()
+
+    companion object {
+        private val ACTION_SPEC =
+                ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                        .setArguments(Arguments::class.java,
+                            Arguments::Builder,
+                            Arguments.Builder::build
+                        )
+                        .setOutput(Output::class.java)
+                        .bindParameter(
+                                SlotMetadata.DURATION.path,
+                                Arguments.Builder::setSnoozeDuration,
+                                TypeConverters.DURATION_PARAM_VALUE_CONVERTER
+                        )
+                        .bindParameter(
+                                SlotMetadata.ALARM.path,
+                                Arguments.Builder::setTargetAlarm,
+                                AlarmValue.PARAM_VALUE_CONVERTER
+                        )
+                        .bindOutput(
+                            "executionStatus",
+                            Output::executionStatus,
+                            ExecutionStatus::toParamValue
+                        )
+                        .build()
+    }
+}
\ No newline at end of file
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 ed29c9c..3f1e930 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
@@ -21,7 +21,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.properties.Property
@@ -100,7 +99,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var identifier: String? = null
             private var name: String? = null
             private var duration: Duration? = null
@@ -111,7 +110,7 @@
 
             fun setDuration(duration: Duration): Builder = apply { this.duration = duration }
 
-            override fun build(): Arguments = Arguments(identifier, name, duration)
+            fun build(): Arguments = Arguments(identifier, name, duration)
         }
     }
     class Output internal constructor(val executionStatus: ExecutionStatus?) {
@@ -180,7 +179,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.IDENTIFIER.path,
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 1ab0f53..c41372e 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
@@ -22,7 +22,6 @@
 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.EntityConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -76,14 +75,14 @@
             return timerList.hashCode()
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var timerList: List<TimerValue>? = null
 
             fun setTimerList(
                 timerList: List<TimerValue>
             ): Builder = apply { this.timerList = timerList }
 
-            override fun build(): Arguments = Arguments(timerList)
+            fun build(): Arguments = Arguments(timerList)
         }
     }
 
@@ -154,7 +153,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindRepeatedParameter(
                     SlotMetadata.TIMER.path,
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarmTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarmTest.kt
new file mode 100644
index 0000000..5e67070
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/CreateAlarmTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.appactions.interaction.capabilities.productivity
+
+import android.util.SizeF
+import androidx.appactions.builtintypes.properties.ByDay
+import androidx.appactions.builtintypes.properties.RepeatFrequency
+import androidx.appactions.builtintypes.types.DayOfWeek
+import androidx.appactions.builtintypes.types.Schedule
+import androidx.appactions.interaction.capabilities.core.ExecutionCallback
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.HostProperties
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.productivity.CreateAlarm.Arguments
+import androidx.appactions.interaction.capabilities.productivity.CreateAlarm.Output
+import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils
+import androidx.appactions.interaction.capabilities.testing.internal.FakeCallbackInternal
+import androidx.appactions.interaction.capabilities.testing.internal.TestingUtils.awaitSync
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.proto.TaskInfo
+import androidx.appactions.interaction.protobuf.ListValue
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.time.LocalTime
+import kotlinx.coroutines.CompletableDeferred
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class CreateAlarmTest {
+    private val hostProperties =
+        HostProperties.Builder().setMaxHostSizeDp(SizeF(300f, 500f)).build()
+    @Test
+    fun createCapability_expectedResult() {
+        val argsDeferred = CompletableDeferred<Arguments>()
+        val capability =
+            CreateAlarm.CapabilityBuilder()
+                .setId("create alarm")
+                .setScheduleProperty(Property<Schedule>())
+                .setExecutionCallback(
+                    ExecutionCallback {
+                        argsDeferred.complete(it)
+                        ExecutionResult.Builder<Output>().build()
+                    })
+                .build()
+        val capabilitySession = capability.createSession("fakeSessionId", hostProperties)
+        val args: MutableMap<String, ParamValue> = mutableMapOf(
+            "alarm.name" to ParamValue.newBuilder().setStringValue("wakeup").build(),
+            "alarm.alarmSchedule" to ParamValue.newBuilder()
+                .setStructValue(
+                    Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Schedule").build())
+                        .putFields("startTime", Value.newBuilder().setStringValue("08:30").build())
+                        .putFields(
+                            "repeatFrequency",
+                            Value.newBuilder().setStringValue("PT5M").build()
+                        )
+                        .putFields(
+                            "byDays",
+                            Value.newBuilder()
+                                .setListValue(
+                                    ListValue.newBuilder()
+                                        .addValues(
+                                            Value.newBuilder()
+                                                .setStringValue("http://schema.org/Monday")
+                                        )
+                                        .addValues(
+                                            Value.newBuilder()
+                                                .setStringValue("http://schema.org/Tuesday")
+                                        )
+                                        .build())
+                                .build()))
+                .build()
+        )
+        capabilitySession.execute(ArgumentUtils.buildArgs(args), FakeCallbackInternal())
+
+        assertThat(capability.appAction)
+            .isEqualTo(
+                AppAction.newBuilder()
+                    .setIdentifier("create alarm")
+                    .setName("actions.intent.CREATE_ALARM")
+                    .addParams(IntentParameter.newBuilder().setName("alarm.alarmSchedule"))
+                    .setTaskInfo(TaskInfo.getDefaultInstance())
+                    .build()
+            )
+        assertThat(argsDeferred.awaitSync())
+            .isEqualTo(
+                Arguments.Builder()
+                    .setName("wakeup")
+                    .setSchedule(
+                        Schedule.Builder()
+                            .setStartTime(LocalTime.of(8, 30))
+                            .setRepeatFrequency(RepeatFrequency(Duration.ofMinutes(5)))
+                            .addByDay(ByDay(DayOfWeek.MONDAY))
+                            .addByDay(ByDay(DayOfWeek.TUESDAY))
+                            .build()
+                    )
+                    .build()
+            )
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarmTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarmTest.kt
new file mode 100644
index 0000000..fb88eae
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/DismissAlarmTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.appactions.interaction.capabilities.productivity
+
+import android.util.SizeF
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.interaction.capabilities.core.ExecutionCallback
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.HostProperties
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.productivity.DismissAlarm.Arguments
+import androidx.appactions.interaction.capabilities.productivity.DismissAlarm.Output
+import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils
+import androidx.appactions.interaction.capabilities.testing.internal.FakeCallbackInternal
+import androidx.appactions.interaction.capabilities.testing.internal.TestingUtils.awaitSync
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.proto.TaskInfo
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class DismissAlarmTest {
+    private val hostProperties =
+        HostProperties.Builder().setMaxHostSizeDp(SizeF(300f, 500f)).build()
+    @Test
+    fun createCapability_expectedResult() {
+        val argsDeferred = CompletableDeferred<Arguments>()
+        val capability =
+            DismissAlarm.CapabilityBuilder()
+                .setId("dismiss alarm")
+                .setAlarmProperty(Property<Alarm>(isRequiredForExecution = true))
+                .setExecutionCallback(
+                    ExecutionCallback {
+                        argsDeferred.complete(it)
+                        ExecutionResult.Builder<Output>().build()
+                    })
+                .build()
+        val capabilitySession = capability.createSession("fakeSessionId", hostProperties)
+        val args: MutableMap<String, ParamValue> = mutableMapOf(
+            "alarm" to ParamValue.newBuilder()
+                .setStructValue(
+                    Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Alarm").build())
+                        .putFields("identifier", Value.newBuilder().setStringValue("abc").build())
+                )
+                .build()
+        )
+        capabilitySession.execute(ArgumentUtils.buildArgs(args), FakeCallbackInternal())
+
+        assertThat(capability.appAction)
+            .isEqualTo(
+                AppAction.newBuilder()
+                    .setIdentifier("dismiss alarm")
+                    .setName("actions.intent.DISMISS_ALARM")
+                    .addParams(IntentParameter.newBuilder().setName("alarm").setIsRequired(true))
+                    .setTaskInfo(TaskInfo.getDefaultInstance())
+                    .build()
+            )
+        assertThat(argsDeferred.awaitSync())
+            .isEqualTo(
+                Arguments.Builder()
+                    .setAlarm(AlarmValue(Alarm.Builder().setIdentifier("abc").build()))
+                    .build()
+            )
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarmTest.kt b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarmTest.kt
new file mode 100644
index 0000000..dc15681
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-productivity/src/test/java/androidx/appactions/interaction/capabilities/productivity/SnoozeAlarmTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.productivity
+
+import android.util.SizeF
+import androidx.appactions.builtintypes.types.Alarm
+import androidx.appactions.interaction.capabilities.core.ExecutionCallback
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.HostProperties
+import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.productivity.SnoozeAlarm.Arguments
+import androidx.appactions.interaction.capabilities.productivity.SnoozeAlarm.Output
+import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils
+import androidx.appactions.interaction.capabilities.testing.internal.FakeCallbackInternal
+import androidx.appactions.interaction.capabilities.testing.internal.TestingUtils.awaitSync
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.proto.TaskInfo
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import kotlinx.coroutines.CompletableDeferred
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+@RunWith(JUnit4::class)
+class SnoozeAlarmTest {
+    private val hostProperties =
+        HostProperties.Builder().setMaxHostSizeDp(SizeF(300f, 500f)).build()
+    @Test
+    fun createCapability_expectedResult() {
+        val argsDeferred = CompletableDeferred<Arguments>()
+        val capability =
+            SnoozeAlarm.CapabilityBuilder()
+                .setId("snooze alarm")
+                .setSnoozeDurationProperty(Property<Duration>())
+                .setTargetAlarmProperty(Property<Alarm>(isRequiredForExecution = true))
+                .setExecutionCallback(
+                    ExecutionCallback {
+                        argsDeferred.complete(it)
+                        ExecutionResult.Builder<Output>().build()
+                    })
+                .build()
+        val capabilitySession = capability.createSession("fakeSessionId", hostProperties)
+        val args: MutableMap<String, ParamValue> = mutableMapOf(
+            "snoozeDuration" to ParamValue.newBuilder().setStringValue("PT5M").build(),
+            "alarm" to ParamValue.newBuilder()
+                .setStructValue(
+                    Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Alarm").build())
+                        .putFields("identifier", Value.newBuilder().setStringValue("abc").build())
+                )
+                .build()
+        )
+        capabilitySession.execute(ArgumentUtils.buildArgs(args), FakeCallbackInternal())
+
+        assertThat(capability.appAction)
+            .isEqualTo(
+                AppAction.newBuilder()
+                    .setIdentifier("snooze alarm")
+                    .setName("actions.intent.SNOOZE_ALARM")
+                    .addParams(IntentParameter.newBuilder().setName("snoozeDuration"))
+                    .addParams(IntentParameter.newBuilder().setName("alarm").setIsRequired(true))
+                    .setTaskInfo(TaskInfo.getDefaultInstance())
+                    .build()
+            )
+        assertThat(argsDeferred.awaitSync())
+            .isEqualTo(
+                Arguments.Builder()
+                    .setSnoozeDuration(Duration.ofMinutes(5))
+                    .setTargetAlarm(AlarmValue(Alarm.Builder().setIdentifier("abc").build()))
+                    .build()
+            )
+    }
+}
\ No newline at end of file
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 423715b..80e2583 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
@@ -22,7 +22,6 @@
 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
 import androidx.appactions.interaction.capabilities.safety.executionstatus.EmergencySharingInProgress
@@ -43,8 +42,8 @@
             >(ACTION_SPEC)
 
     class Arguments internal constructor() {
-        class Builder : BuilderOf<Arguments> {
-            override fun build(): Arguments = Arguments()
+        class Builder {
+            fun build(): Arguments = Arguments()
         }
     }
 
@@ -150,7 +149,8 @@
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(
                     Arguments::class.java,
-                    Arguments::Builder
+                    Arguments::Builder,
+                    Arguments.Builder::build
                 )
                 .setOutput(Output::class.java)
                 .bindOutput(
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 6e9bec6..e59813b 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
@@ -24,7 +24,6 @@
 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
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters.SAFETY_CHECK_TYPE_SPEC
@@ -94,7 +93,7 @@
             return result
         }
 
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var duration: Duration? = null
 
             private var checkInTime: ZonedDateTime? = null
@@ -105,7 +104,7 @@
             fun setCheckInTime(checkInTime: ZonedDateTime): Builder =
                 apply { this.checkInTime = checkInTime }
 
-            override fun build(): Arguments = Arguments(duration, checkInTime)
+            fun build(): Arguments = Arguments(duration, checkInTime)
         }
     }
 
@@ -228,7 +227,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     SlotMetadata.DURATION.path,
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 90b7827..7d1636a 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
@@ -23,7 +23,6 @@
 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
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyAccountNotLoggedIn
@@ -43,8 +42,8 @@
             >(ACTION_SPEC)
 
     class Arguments internal constructor() {
-        class Builder : BuilderOf<Arguments> {
-            override fun build(): Arguments = Arguments()
+        class Builder {
+            fun build(): Arguments = Arguments()
         }
     }
 
@@ -150,7 +149,8 @@
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
                 .setArguments(
                     Arguments::class.java,
-                    Arguments::Builder
+                    Arguments::Builder,
+                    Arguments.Builder::build
                 )
                 .setOutput(Output::class.java)
                 .bindOutput(
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 e8bb0a9..cc29f26 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
@@ -23,7 +23,6 @@
 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
 import androidx.appactions.interaction.capabilities.safety.executionstatus.SafetyAccountNotLoggedIn
@@ -43,8 +42,8 @@
             >(ACTION_SPEC)
 
     class Arguments internal constructor() {
-        class Builder : BuilderOf<Arguments> {
-            override fun build(): Arguments = Arguments()
+        class Builder {
+            fun build(): Arguments = Arguments()
         }
     }
 
@@ -148,7 +147,7 @@
     companion object {
         private val ACTION_SPEC =
             ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
                 .setOutput(Output::class.java)
                 .bindOutput(
                     "executionStatus",
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/interaction/interaction-service-testing/api/current.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to appactions/interaction/interaction-service-testing/api/current.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/appactions/interaction/interaction-service-testing/api/res-current.txt
similarity index 100%
copy from appactions/builtintypes/builtintypes-core/api/res-current.txt
copy to appactions/interaction/interaction-service-testing/api/res-current.txt
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/appactions/interaction/interaction-service-testing/api/restricted_current.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to appactions/interaction/interaction-service-testing/api/restricted_current.txt
diff --git a/appactions/builtintypes/builtintypes-core/samples/build.gradle b/appactions/interaction/interaction-service-testing/build.gradle
similarity index 64%
copy from appactions/builtintypes/builtintypes-core/samples/build.gradle
copy to appactions/interaction/interaction-service-testing/build.gradle
index 871e254..84844a4 100644
--- a/appactions/builtintypes/builtintypes-core/samples/build.gradle
+++ b/appactions/interaction/interaction-service-testing/build.gradle
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright (C) 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.
@@ -24,21 +24,16 @@
 
 dependencies {
     api(libs.kotlinStdlib)
-
-    compileOnly(project(":annotation:annotation-sampled"))
-    implementation(project(":appactions:builtintypes:builtintypes-core"))
+    // Add dependencies here
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core.samples"
-    defaultConfig {
-        minSdkVersion 26
-    }
+    namespace "androidx.appactions.interaction.service.testing"
 }
 
 androidx {
-    name = "Built-in Types Core Samples"
-    type = LibraryType.SAMPLES
+    name = "androidx.appactions.interaction:interaction-service-testing"
+    type = LibraryType.PUBLISHED_TEST_LIBRARY
     inceptionYear = "2023"
-    description = "Samples for AndroidX Built-in Types Core Library"
+    description = "Test integrations with App Actions Interactions service libraries"
 }
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt
index 29fc252c..3ad710f 100644
--- a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/testing/internal/FakeCapability.kt
@@ -20,7 +20,6 @@
 import androidx.appactions.interaction.capabilities.core.Capability
 import androidx.appactions.interaction.capabilities.core.HostProperties
 import androidx.appactions.interaction.capabilities.core.ValueListener
-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
 import androidx.appactions.interaction.capabilities.core.impl.task.SessionBridge
@@ -34,12 +33,12 @@
     class Arguments internal constructor(
         val fieldOne: String?,
     ) {
-        class Builder : BuilderOf<Arguments> {
+        class Builder {
             private var fieldOne: String? = null
             fun setFieldOne(value: String) = apply {
                 fieldOne = value
             }
-            override fun build() = Arguments(fieldOne)
+            fun build() = Arguments(fieldOne)
         }
     }
 
@@ -85,7 +84,7 @@
 
     companion object {
         private val ACTION_SPEC = ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-            .setArguments(Arguments::class.java, Arguments::Builder)
+            .setArguments(Arguments::class.java, Arguments::Builder, Arguments.Builder::build)
             .setOutput(Output::class.java)
             .bindParameter(
                 "fieldOne",
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/FileMover.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/FileMover.kt
index f0cdfac..3ec3e50 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/FileMover.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/FileMover.kt
@@ -28,7 +28,10 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(Build.VERSION_CODES.O)
 object FileMover {
-    fun File.moveTo(destination: File) {
+    fun File.moveTo(destination: File, overwrite: Boolean = false) {
+        if (overwrite) {
+            destination.delete()
+        }
         // Ideally we would have used File.renameTo(...)
         // On Android we cannot rename across mount points so we are using this API instead.
         Files.move(this.toPath(), destination.toPath())
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
index 2264292..979a399 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
@@ -133,7 +133,7 @@
             // `outputDirectory`.
             Log.d(BenchmarkState.TAG, "Moving $file to $destination")
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
-                file.moveTo(destination)
+                file.moveTo(destination, overwrite = true)
             } else {
                 file.copyTo(destination, overwrite = true)
                 file.delete()
diff --git a/benchmark/benchmark-macro-junit4/api/current.txt b/benchmark/benchmark-macro-junit4/api/current.txt
index 36c5c6a..d4c3495 100644
--- a/benchmark/benchmark-macro-junit4/api/current.txt
+++ b/benchmark/benchmark-macro-junit4/api/current.txt
@@ -4,13 +4,13 @@
   @RequiresApi(28) public final class BaselineProfileRule implements org.junit.rules.TestRule {
     ctor public BaselineProfileRule();
     method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, optional kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> filterPredicate, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, optional kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> filterPredicate, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
   }
 
   public final class MacrobenchmarkRule implements org.junit.rules.TestRule {
diff --git a/benchmark/benchmark-macro-junit4/api/restricted_current.txt b/benchmark/benchmark-macro-junit4/api/restricted_current.txt
index 36c5c6a..d4c3495 100644
--- a/benchmark/benchmark-macro-junit4/api/restricted_current.txt
+++ b/benchmark/benchmark-macro-junit4/api/restricted_current.txt
@@ -4,13 +4,13 @@
   @RequiresApi(28) public final class BaselineProfileRule implements org.junit.rules.TestRule {
     ctor public BaselineProfileRule();
     method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, optional kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> filterPredicate, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, optional int stableIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, optional int maxIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
-    method public void collectBaselineProfile(String packageName, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, optional boolean strictStability, optional kotlin.jvm.functions.Function1<? super java.lang.String,java.lang.Boolean> filterPredicate, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, optional boolean includeInStartupProfile, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, optional String? outputFilePrefix, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, optional int stableIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, optional int maxIterations, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
+    method public void collect(String packageName, kotlin.jvm.functions.Function1<? super androidx.benchmark.macro.MacrobenchmarkScope,kotlin.Unit> profileBlock);
   }
 
   public final class MacrobenchmarkRule implements org.junit.rules.TestRule {
diff --git a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt
index 84998e7..9857515 100644
--- a/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt
+++ b/benchmark/benchmark-macro-junit4/src/main/java/androidx/benchmark/macro/junit4/BaselineProfileRule.kt
@@ -20,7 +20,7 @@
 import androidx.annotation.RequiresApi
 import androidx.benchmark.Arguments
 import androidx.benchmark.macro.MacrobenchmarkScope
-import androidx.benchmark.macro.collectBaselineProfile
+import androidx.benchmark.macro.collect
 import androidx.test.rule.GrantPermissionRule
 import org.junit.Assume.assumeTrue
 import org.junit.rules.RuleChain
@@ -43,7 +43,7 @@
  *     val baselineProfileRule = BaselineProfileRule()
  *
  *     @Test
- *     fun startup() = baselineProfileRule.collectBaselineProfile(
+ *     fun startup() = baselineProfileRule.collect(
  *         packageName = "com.example.my.application.id"
  *     ) {
  *         pressHome()
@@ -60,7 +60,7 @@
  *
  * ```
  *     @Test
- *     fun generateLibraryRules() = baselineProfileRule.collectBaselineProfile(
+ *     fun generateLibraryRules() = baselineProfileRule.collect(
  *         // Target app is an integration test app which uses the library, but any
  *         // app code isn't relevant to store in library's Baseline Profile
  *         packageName = "com.example.testapp.id"
@@ -123,7 +123,7 @@
      * @param [profileBlock] defines the critical user journey.
      */
     @JvmOverloads
-    public fun collectBaselineProfile(
+    public fun collect(
         packageName: String,
         maxIterations: Int = 15,
         stableIterations: Int = 3,
@@ -133,7 +133,7 @@
         filterPredicate: ((String) -> Boolean) = { true },
         profileBlock: MacrobenchmarkScope.() -> Unit
     ) {
-        collectBaselineProfile(
+        collect(
             uniqueName = outputFilePrefix ?: currentDescription.toUniqueName(),
             packageName = packageName,
             stableIterations = stableIterations,
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
index b208517..4eedd27 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
@@ -81,7 +81,8 @@
         packageName = Packages.TARGET,
         sectionName = "inflate",
         expectedFirstMs = 13.318, // first inflation
-        expectedSumMs = 43.128 // total inflation
+        expectedSumMs = 43.128, // total inflation
+        expectedSumCount = 8,
     )
 
     companion object {
@@ -97,7 +98,8 @@
             packageName: String,
             sectionName: String,
             mode: TraceSectionMetric.Mode,
-            expectedMs: Double
+            expectedMs: Double,
+            expectedCount: Int,
         ) {
             assumeTrue(PerfettoHelper.isAbiSupported())
 
@@ -111,8 +113,16 @@
                 )
             }
 
+            var measurements = listOf(Metric.Measurement(sectionName + "Ms", expectedMs))
+
+            if (mode == TraceSectionMetric.Mode.Sum) {
+                measurements = measurements + listOf(
+                    Metric.Measurement(sectionName + "Count", expectedCount.toDouble())
+                )
+            }
+
             assertEqualMeasurements(
-                expected = listOf(Metric.Measurement(sectionName + "Ms", expectedMs)),
+                expected = measurements,
                 observed = result,
                 threshold = 0.001
             )
@@ -123,21 +133,24 @@
             packageName: String,
             sectionName: String,
             expectedFirstMs: Double,
-            expectedSumMs: Double = expectedFirstMs // default implies only one matching section
+            expectedSumMs: Double = expectedFirstMs, // default implies only one matching section
+            expectedSumCount: Int = 1
         ) {
             verifyMetric(
                 tracePath = tracePath,
                 packageName = packageName,
                 sectionName = sectionName,
                 mode = TraceSectionMetric.Mode.First,
-                expectedMs = expectedFirstMs
+                expectedMs = expectedFirstMs,
+                expectedCount = 1
             )
             verifyMetric(
                 tracePath = tracePath,
                 packageName = packageName,
                 sectionName = sectionName,
                 mode = TraceSectionMetric.Mode.Sum,
-                expectedMs = expectedSumMs
+                expectedMs = expectedSumMs,
+                expectedCount = expectedSumCount,
             )
         }
     }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index 5cabbc1..d81fc45 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -38,7 +38,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @RequiresApi(28)
-fun collectBaselineProfile(
+fun collect(
     uniqueName: String,
     packageName: String,
     stableIterations: Int,
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 58b51f2..248db35 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -446,6 +446,10 @@
                         name = sectionName + "Ms",
                         // note, this duration assumes non-reentrant slices
                         data = slices.sumOf { it.dur } / 1_000_000.0
+                    ),
+                    Measurement(
+                        name = sectionName + "Count",
+                        data = slices.size.toDouble()
                     )
                 )
             }
@@ -715,6 +719,7 @@
          */
         Max
     }
+
     enum class SubMetric(
         /**
          * Name of counter in trace.
diff --git a/benchmark/gradle-plugin/build.gradle b/benchmark/gradle-plugin/build.gradle
index 11bf133..30b1ddb 100644
--- a/benchmark/gradle-plugin/build.gradle
+++ b/benchmark/gradle-plugin/build.gradle
@@ -25,11 +25,11 @@
 apply from: "../../buildSrc/kotlin-dsl-dependency.gradle"
 
 dependencies {
-    implementation(findGradleKotlinDsl())
-    implementation(gradleApi())
-    implementation("com.android.tools.build:gradle:7.3.0")
-    implementation(libs.kotlinStdlib)
+    compileOnly("com.android.tools.build:gradle:7.3.0")
 
+    implementation(gradleApi())
+
+    testImplementation("com.android.tools.build:gradle:7.3.0")
     testImplementation(gradleTestKit())
     testImplementation(project(":internal-testutils-gradle-plugin"))
     testImplementation(libs.junit)
@@ -70,3 +70,4 @@
         enableStricterValidation.set(true)
     }
 }
+afterEvaluate { tasks.named("test") {it.dependsOn( tasks.named("publish")) }}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
index 65a081e..6b33bd9 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
@@ -35,10 +35,6 @@
         // required BaseExtension from AGP can be found by registering project configuration as a
         // PluginManager callback.
 
-        project.pluginManager.withPlugin("com.android.application") {
-            configureWithAndroidPlugin(project)
-        }
-
         project.pluginManager.withPlugin("com.android.library") {
             configureWithAndroidPlugin(project)
         }
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
index 339d8cec..e84bb7c 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkReportTask.kt
@@ -22,12 +22,11 @@
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.StopExecutionException
 import org.gradle.api.tasks.TaskAction
-import org.gradle.kotlin.dsl.property
 import org.gradle.work.DisableCachingByDefault
 
 @Suppress("UnstableApiUsage")
 @DisableCachingByDefault(because = "Benchmark measurements are performed each task execution.")
-open class BenchmarkReportTask : DefaultTask() {
+abstract class BenchmarkReportTask : DefaultTask() {
     private val benchmarkReportDir: File
 
     init {
@@ -47,8 +46,8 @@
         outputs.upToDateWhen { false }
     }
 
-    @Input
-    val adbPath: Property<String> = project.objects.property()
+    @get: Input
+    abstract val adbPath: Property<String>
 
     @TaskAction
     fun exec() {
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
index 89f09cb..8121959 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
@@ -24,21 +24,20 @@
 import org.gradle.api.provider.Property
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.TaskAction
-import org.gradle.kotlin.dsl.property
 import org.gradle.work.DisableCachingByDefault
 
 @Suppress("UnstableApiUsage")
 @DisableCachingByDefault(
     because = "LockClocks affects device state, and may be modified/reset outside of this task"
 )
-open class LockClocksTask : DefaultTask() {
+abstract class LockClocksTask : DefaultTask() {
     init {
         group = "Android"
         description = "locks clocks of connected, supported, rooted device"
     }
 
-    @Input
-    val adbPath: Property<String> = project.objects.property()
+    @get:Input
+    abstract val adbPath: Property<String>
 
     @Suppress("unused")
     @TaskAction
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt
index 3556e33..857b065 100644
--- a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt
@@ -21,21 +21,20 @@
 import org.gradle.api.provider.Property
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.TaskAction
-import org.gradle.kotlin.dsl.property
 import org.gradle.work.DisableCachingByDefault
 
 @Suppress("UnstableApiUsage")
 @DisableCachingByDefault(
     because = "UnlockClocks affects device state, and may be modified/reset outside of this task"
 )
-open class UnlockClocksTask : DefaultTask() {
+abstract class UnlockClocksTask : DefaultTask() {
     init {
         group = "Android"
         description = "unlocks clocks of device by rebooting"
     }
 
-    @Input
-    val adbPath: Property<String> = project.objects.property()
+    @get:Input
+    abstract val adbPath: Property<String>
 
     @TaskAction
     fun exec() {
diff --git a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
index 01f9af5..07cf4e7 100644
--- a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
+++ b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
@@ -38,6 +38,7 @@
 
 @RunWith(JUnit4::class)
 class BenchmarkPluginTest {
+
     @get:Rule
     val projectSetup = ProjectSetupRule()
 
@@ -51,30 +52,21 @@
 
         File("src/test/test-data", "app-project").copyRecursively(projectSetup.rootDir)
 
-        gradleRunner = GradleRunner.create()
-            .withProjectDir(projectSetup.rootDir)
-            .withPluginClasspath()
-    }
+        gradleRunner = GradleRunner.create().withProjectDir(projectSetup.rootDir)
 
-    @Test
-    fun applyPluginAppProject() {
-        projectSetup.writeDefaultBuildGradle(
-            prefix = """
-                plugins {
-                    id('com.android.application')
-                    id('androidx.benchmark')
+        projectSetup.testProjectDir.newFile("settings.gradle").writeText(
+            """
+            buildscript {
+                repositories {
+                    ${projectSetup.allRepositoryPaths.joinToString("\n") { """ maven { url "$it" } """ }}
                 }
-            """.trimIndent(),
-            suffix = """
-            dependencies {
-                androidTestImplementation "androidx.benchmark:benchmark:1.0.0-alpha01"
+                dependencies {
+                    classpath "com.android.tools.build:gradle:7.3.0"
+                    classpath "androidx.benchmark:androidx.benchmark.gradle.plugin:+"
+                }
             }
-            """.trimIndent()
+        """.trimIndent()
         )
-
-        val output = gradleRunner.withArguments("tasks", "--stacktrace").build()
-        assertTrue { output.output.contains("lockClocks - ") }
-        assertTrue { output.output.contains("unlockClocks - ") }
     }
 
     @Test
diff --git a/benchmark/integration-tests/baselineprofile-flavors-producer/src/free/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt b/benchmark/integration-tests/baselineprofile-flavors-producer/src/free/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt
index 29bb2d1..ff6e532 100644
--- a/benchmark/integration-tests/baselineprofile-flavors-producer/src/free/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt
+++ b/benchmark/integration-tests/baselineprofile-flavors-producer/src/free/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt
@@ -38,7 +38,7 @@
         assumeTrue(DeviceInfo.isRooted || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
 
         // Collects the baseline profile
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             profileBlock = {
                 startActivityAndWait(Intent(ACTION))
diff --git a/benchmark/integration-tests/baselineprofile-flavors-producer/src/paid/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt b/benchmark/integration-tests/baselineprofile-flavors-producer/src/paid/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt
index 382fedc..b49dde7 100644
--- a/benchmark/integration-tests/baselineprofile-flavors-producer/src/paid/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt
+++ b/benchmark/integration-tests/baselineprofile-flavors-producer/src/paid/java/androidx/benchmark/integration/baselineprofile/flavors/producer/BaselineProfileTest.kt
@@ -38,7 +38,7 @@
         assumeTrue(DeviceInfo.isRooted || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
 
         // Collects the baseline profile
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             profileBlock = {
                 startActivityAndWait(Intent(ACTION))
diff --git a/benchmark/integration-tests/baselineprofile-library-producer/src/main/java/androidx/benchmark/integration/baselineprofile/library/producer/BaselineProfileTest.kt b/benchmark/integration-tests/baselineprofile-library-producer/src/main/java/androidx/benchmark/integration/baselineprofile/library/producer/BaselineProfileTest.kt
index e491273..40e7d63 100644
--- a/benchmark/integration-tests/baselineprofile-library-producer/src/main/java/androidx/benchmark/integration/baselineprofile/library/producer/BaselineProfileTest.kt
+++ b/benchmark/integration-tests/baselineprofile-library-producer/src/main/java/androidx/benchmark/integration/baselineprofile/library/producer/BaselineProfileTest.kt
@@ -38,7 +38,7 @@
         assumeTrue(DeviceInfo.isRooted || Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
 
         // Collects the baseline profile
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             profileBlock = {
                 startActivityAndWait(Intent(ACTION))
diff --git a/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt b/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt
index 29b8503..7b55cde 100644
--- a/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt
+++ b/benchmark/integration-tests/baselineprofile-producer/src/main/java/androidx/benchmark/integration/baselineprofile/producer/BaselineProfileTest.kt
@@ -40,7 +40,7 @@
     }
 
     @Test
-    fun standardBaselineProfile() = baselineRule.collectBaselineProfile(
+    fun standardBaselineProfile() = baselineRule.collect(
         packageName = PACKAGE_NAME,
         includeInStartupProfile = false,
         profileBlock = {
@@ -50,7 +50,7 @@
     )
 
     @Test
-    fun startupBaselineProfile() = baselineRule.collectBaselineProfile(
+    fun startupBaselineProfile() = baselineRule.collect(
         packageName = PACKAGE_NAME,
         includeInStartupProfile = true,
         profileBlock = {
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
index f49f0d9..c252b60 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
@@ -44,7 +44,7 @@
     @Test
     fun appNotInstalled() {
         val error = assertFailsWith<AssertionError> {
-            baselineRule.collectBaselineProfile(
+            baselineRule.collect(
                 packageName = "fake.package.not.installed",
                 profileBlock = {
                     fail("not expected")
@@ -62,7 +62,7 @@
         assumeTrue(Build.VERSION.SDK_INT >= 33 || Shell.isSessionRooted())
 
         // Collects the baseline profile
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             filterPredicate = { it.contains(filterRegex) },
             profileBlock = {
@@ -101,7 +101,7 @@
         ).forEach { (includeInStartupProfile, outputFilename) ->
 
             // Collects the baseline profile
-            baselineRule.collectBaselineProfile(
+            baselineRule.collect(
                 packageName = PACKAGE_NAME,
                 filterPredicate = { it.contains(filterRegex) },
                 includeInStartupProfile = includeInStartupProfile,
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/GithubBrowserBaselineProfile.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/GithubBrowserBaselineProfile.kt
index 50061a3..450fa4e 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/GithubBrowserBaselineProfile.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/GithubBrowserBaselineProfile.kt
@@ -59,7 +59,7 @@
     @Test
     @Ignore
     fun githubBrowserProfiles() {
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             profileBlock = {
                 startActivityAndWait()
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/SystemUiBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/SystemUiBenchmark.kt
index d5208f6..682a58f 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/SystemUiBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/SystemUiBenchmark.kt
@@ -32,7 +32,7 @@
     @Test
     @Ignore
     fun baselineProfiles() {
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             profileBlock = {
                 pressHome()
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBaselineProfile.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBaselineProfile.kt
index 737f24e..7940642 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBaselineProfile.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialListScrollBaselineProfile.kt
@@ -45,7 +45,7 @@
 
     @Test
     fun baselineProfiles() {
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = "androidx.benchmark.integration.macrobenchmark.target",
             profileBlock = {
                 val intent = Intent()
@@ -73,7 +73,7 @@
 
     @Test
     fun stableBaselineProfiles() {
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = "androidx.benchmark.integration.macrobenchmark.target",
             stableIterations = 3,
             maxIterations = 10,
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBaselineProfile.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBaselineProfile.kt
index b773259..a59a95e 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBaselineProfile.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/TrivialStartupBaselineProfile.kt
@@ -42,7 +42,7 @@
 
     @Test
     fun baselineProfiles() {
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
         ) {
             device.pressHome()
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt
index c2c4d54..299c81c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXPlaygroundRootImplPlugin.kt
@@ -152,7 +152,7 @@
             includeGroupRegex = """com\.android\.tools\.metalava"""
         )
         val prebuilts = PlaygroundRepository(
-            "https://androidx.dev/storage/prebuilts/androidx/internal/repository",
+            INTERNAL_PREBUILTS_REPO_URL,
             includeGroupRegex = """androidx\..*"""
         )
         val dokka = PlaygroundRepository(
@@ -226,5 +226,7 @@
             }
         }
         const val SNAPSHOT_MARKER = "REPLACE_WITH_SNAPSHOT"
+        const val INTERNAL_PREBUILTS_REPO_URL =
+            "https://androidx.dev/storage/prebuilts/androidx/internal/repository"
     }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
index a5d8f99..2992d9c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
@@ -16,6 +16,7 @@
 
 package androidx.build.sbom
 
+import androidx.build.AndroidXPlaygroundRootImplPlugin
 import androidx.build.BundleInsideHelper
 import androidx.build.GMavenZipTask
 import androidx.build.ProjectLayoutType
@@ -213,11 +214,7 @@
 
     project.configurations.create(sbomEmptyConfiguration)
     project.apply(plugin = "org.spdx.sbom")
-    val repos = if (ProjectLayoutType.isPlayground(this)) {
-        emptyMap()
-    } else {
-        getRepoPublicUrls()
-    }
+    val repos = getRepoPublicUrls()
     val gitsClient = MultiGitClient.create(project)
     val supportRootDir = getSupportRootFolder()
 
@@ -319,16 +316,25 @@
     throw GradleException("Could not identify git remote url for project at $dir")
 }
 
+private const val MAVEN_CENTRAL_REPO_URL = "https://dl.google.com/android/maven2"
+private const val GMAVEN_REPO_URL = "https://dl.google.com/android/maven2"
 /**
  * Returns a mapping from local repo url to public repo url
  */
-fun Project.getRepoPublicUrls(): Map<String, String> {
-    return mapOf(
-        "file:${project.getPrebuiltsRoot()}/androidx/external"
-            to "https://repo.maven.apache.org/maven2",
-        "file:${project.getPrebuiltsRoot()}/androidx/internal"
-            to "https://dl.google.com/android/maven2"
-    )
+private fun Project.getRepoPublicUrls(): Map<String, String> {
+    return if (ProjectLayoutType.isPlayground(this)) {
+        mapOf(
+            MAVEN_CENTRAL_REPO_URL to MAVEN_CENTRAL_REPO_URL,
+            AndroidXPlaygroundRootImplPlugin.INTERNAL_PREBUILTS_REPO_URL to GMAVEN_REPO_URL
+        )
+    } else {
+        mapOf(
+            "file:${project.getPrebuiltsRoot()}/androidx/external"
+                to MAVEN_CENTRAL_REPO_URL,
+            "file:${project.getPrebuiltsRoot()}/androidx/internal"
+                to GMAVEN_REPO_URL
+        )
+    }
 }
 
 private fun Project.appliesShadowPlugin() =
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CloseCaptureSessionOnDisconnectQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CloseCaptureSessionOnDisconnectQuirk.kt
index 89bb55b3..f073a19 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CloseCaptureSessionOnDisconnectQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CloseCaptureSessionOnDisconnectQuirk.kt
@@ -22,6 +22,7 @@
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.camera.core.impl.Quirk
+import java.util.Locale
 
 /**
  * Quirk needed on devices where not closing capture session before creating a new capture session
@@ -55,11 +56,16 @@
                 //  to CameraDevice.close() stalling indefinitely. This version check might need to
                 //  be further fine-turned down the line.
                 true
-            } else {
+            } else if (Build.HARDWARE == "samsungexynos7870") {
                 // TODO(b/282871038): On some platforms, not closing the capture session before
                 //  switching to a new capture session may trigger camera HAL crashes. Add more
                 //  hardware platforms here when they're identified.
-                Build.HARDWARE == "samsungexynos7870"
+                true
+            } else {
+                // For CPH devices, the shutdown sequence oftentimes triggers ANR for the test app.
+                // As a result, we need to close the capture session to stop the captures, then
+                // release the Surfaces by FinalizeSessionOnCloseQuirk.
+                return Build.MODEL.lowercase(Locale.getDefault()).startsWith("cph")
             }
         }
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FinalizeSessionOnCloseQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FinalizeSessionOnCloseQuirk.kt
index ed333d1..eeec375 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FinalizeSessionOnCloseQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FinalizeSessionOnCloseQuirk.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraGraph.Flags.FinalizeSessionOnCloseBehavior
 import androidx.camera.core.impl.Quirk
+import java.util.Locale
 
 /**
  * A quirk that finalizes [androidx.camera.camera2.pipe.compat.CaptureSessionState] when the
@@ -47,6 +48,13 @@
             if (CameraQuirks.isImmediateSurfaceReleaseAllowed()) {
                 // Finalize immediately for devices that allow immediate Surface reuse.
                 FinalizeSessionOnCloseBehavior.IMMEDIATE
+            } else if (Build.MODEL.lowercase(Locale.getDefault()).startsWith("cph")) {
+                // During shutdown, the test app often experiences ANR which prevents us from
+                // eventually closing the camera device and releasing the Surfaces. As a workaround,
+                // we leverage CloseCaptureSessionOnDisconnectQuirk to close the capture session,
+                // before we use this workaround to finalize the capture session, and thereby
+                // releasing the Surfaces.
+                FinalizeSessionOnCloseBehavior.IMMEDIATE
             } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                 // When CloseCaptureSessionOnVideoQuirk is enabled, we close the capture session
                 // in anticipation that the onClosed() callback would finalize the session. However,
diff --git a/camera/camera-camera2/api/1.3.0-beta01.txt b/camera/camera-camera2/api/1.3.0-beta01.txt
deleted file mode 100644
index ea5cacc..0000000
--- a/camera/camera-camera2/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,54 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.camera2 {
-
-  @RequiresApi(21) public final class Camera2Config {
-    method public static androidx.camera.core.CameraXConfig defaultConfig();
-  }
-
-}
-
-package androidx.camera.camera2.interop {
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraControl {
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> addCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> clearCaptureRequestOptions();
-    method public static androidx.camera.camera2.interop.Camera2CameraControl from(androidx.camera.core.CameraControl);
-    method public androidx.camera.camera2.interop.CaptureRequestOptions getCaptureRequestOptions();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
-  }
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraInfo {
-    method public static androidx.camera.camera2.interop.Camera2CameraInfo from(androidx.camera.core.CameraInfo);
-    method public <T> T? getCameraCharacteristic(android.hardware.camera2.CameraCharacteristics.Key<T!>);
-    method public String getCameraId();
-  }
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2Interop {
-  }
-
-  @RequiresApi(21) public static final class Camera2Interop.Extender<T> {
-    ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
-    method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
-    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
-    method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
-    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
-    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
-    method @RequiresApi(33) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setStreamUseCase(long);
-  }
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public class CaptureRequestOptions {
-    method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
-  }
-
-  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions> {
-    ctor public CaptureRequestOptions.Builder();
-    method public androidx.camera.camera2.interop.CaptureRequestOptions build();
-    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
-    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCamera2Interop {
-  }
-
-}
-
diff --git a/camera/camera-camera2/api/restricted_1.3.0-beta01.txt b/camera/camera-camera2/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index ea5cacc..0000000
--- a/camera/camera-camera2/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,54 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.camera2 {
-
-  @RequiresApi(21) public final class Camera2Config {
-    method public static androidx.camera.core.CameraXConfig defaultConfig();
-  }
-
-}
-
-package androidx.camera.camera2.interop {
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraControl {
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> addCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> clearCaptureRequestOptions();
-    method public static androidx.camera.camera2.interop.Camera2CameraControl from(androidx.camera.core.CameraControl);
-    method public androidx.camera.camera2.interop.CaptureRequestOptions getCaptureRequestOptions();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
-  }
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraInfo {
-    method public static androidx.camera.camera2.interop.Camera2CameraInfo from(androidx.camera.core.CameraInfo);
-    method public <T> T? getCameraCharacteristic(android.hardware.camera2.CameraCharacteristics.Key<T!>);
-    method public String getCameraId();
-  }
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2Interop {
-  }
-
-  @RequiresApi(21) public static final class Camera2Interop.Extender<T> {
-    ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
-    method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
-    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
-    method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
-    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
-    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
-    method @RequiresApi(33) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setStreamUseCase(long);
-  }
-
-  @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public class CaptureRequestOptions {
-    method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
-  }
-
-  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions> {
-    ctor public CaptureRequestOptions.Builder();
-    method public androidx.camera.camera2.interop.CaptureRequestOptions build();
-    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
-    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCamera2Interop {
-  }
-
-}
-
diff --git a/camera/camera-core/api/1.3.0-beta01.txt b/camera/camera-core/api/1.3.0-beta01.txt
deleted file mode 100644
index 5cda9c6..0000000
--- a/camera/camera-core/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,612 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.core {
-
-  @RequiresApi(21) public class AspectRatio {
-    field public static final int RATIO_16_9 = 1; // 0x1
-    field public static final int RATIO_4_3 = 0; // 0x0
-    field public static final int RATIO_DEFAULT = -1; // 0xffffffff
-  }
-
-  @RequiresApi(21) public interface Camera {
-    method public androidx.camera.core.CameraControl getCameraControl();
-    method public androidx.camera.core.CameraInfo getCameraInfo();
-  }
-
-  @RequiresApi(21) public interface CameraControl {
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
-  }
-
-  public static final class CameraControl.OperationCanceledException extends java.lang.Exception {
-  }
-
-  @RequiresApi(21) public abstract class CameraEffect {
-    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.ImageProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
-    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.SurfaceProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
-    method public androidx.core.util.Consumer<java.lang.Throwable!> getErrorListener();
-    method public java.util.concurrent.Executor getExecutor();
-    method public androidx.camera.core.SurfaceProcessor? getSurfaceProcessor();
-    method public int getTargets();
-    field public static final int IMAGE_CAPTURE = 4; // 0x4
-    field public static final int PREVIEW = 1; // 0x1
-    field public static final int VIDEO_CAPTURE = 2; // 0x2
-  }
-
-  @RequiresApi(21) public interface CameraFilter {
-    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
-  }
-
-  @RequiresApi(21) public interface CameraInfo {
-    method public androidx.camera.core.CameraSelector getCameraSelector();
-    method public androidx.lifecycle.LiveData<androidx.camera.core.CameraState!> getCameraState();
-    method public androidx.camera.core.ExposureState getExposureState();
-    method @FloatRange(from=0, fromInclusive=false) public default float getIntrinsicZoomRatio();
-    method public default int getLensFacing();
-    method public int getSensorRotationDegrees();
-    method public int getSensorRotationDegrees(int);
-    method public default java.util.Set<android.util.Range<java.lang.Integer!>!> getSupportedFrameRateRanges();
-    method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
-    method public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
-    method public boolean hasFlashUnit();
-    method public default boolean isFocusMeteringSupported(androidx.camera.core.FocusMeteringAction);
-    method @androidx.camera.core.ExperimentalZeroShutterLag public default boolean isZslSupported();
-  }
-
-  @RequiresApi(21) public final class CameraInfoUnavailableException extends java.lang.Exception {
-  }
-
-  @RequiresApi(21) public interface CameraProvider {
-    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
-    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
-  }
-
-  @RequiresApi(21) public final class CameraSelector {
-    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
-    field public static final androidx.camera.core.CameraSelector DEFAULT_BACK_CAMERA;
-    field public static final androidx.camera.core.CameraSelector DEFAULT_FRONT_CAMERA;
-    field public static final int LENS_FACING_BACK = 1; // 0x1
-    field @androidx.camera.core.ExperimentalLensFacing public static final int LENS_FACING_EXTERNAL = 2; // 0x2
-    field public static final int LENS_FACING_FRONT = 0; // 0x0
-    field public static final int LENS_FACING_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public static final class CameraSelector.Builder {
-    ctor public CameraSelector.Builder();
-    method public androidx.camera.core.CameraSelector.Builder addCameraFilter(androidx.camera.core.CameraFilter);
-    method public androidx.camera.core.CameraSelector build();
-    method public androidx.camera.core.CameraSelector.Builder requireLensFacing(int);
-  }
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class CameraState {
-    ctor public CameraState();
-    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type);
-    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type, androidx.camera.core.CameraState.StateError?);
-    method public abstract androidx.camera.core.CameraState.StateError? getError();
-    method public abstract androidx.camera.core.CameraState.Type getType();
-    field public static final int ERROR_CAMERA_DISABLED = 5; // 0x5
-    field public static final int ERROR_CAMERA_FATAL_ERROR = 6; // 0x6
-    field public static final int ERROR_CAMERA_IN_USE = 2; // 0x2
-    field public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7; // 0x7
-    field public static final int ERROR_MAX_CAMERAS_IN_USE = 1; // 0x1
-    field public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3; // 0x3
-    field public static final int ERROR_STREAM_CONFIG = 4; // 0x4
-  }
-
-  public enum CameraState.ErrorType {
-    enum_constant public static final androidx.camera.core.CameraState.ErrorType CRITICAL;
-    enum_constant public static final androidx.camera.core.CameraState.ErrorType RECOVERABLE;
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class CameraState.StateError {
-    ctor public CameraState.StateError();
-    method public static androidx.camera.core.CameraState.StateError create(int);
-    method public static androidx.camera.core.CameraState.StateError create(int, Throwable?);
-    method public abstract Throwable? getCause();
-    method public abstract int getCode();
-    method public androidx.camera.core.CameraState.ErrorType getType();
-  }
-
-  public enum CameraState.Type {
-    enum_constant public static final androidx.camera.core.CameraState.Type CLOSED;
-    enum_constant public static final androidx.camera.core.CameraState.Type CLOSING;
-    enum_constant public static final androidx.camera.core.CameraState.Type OPEN;
-    enum_constant public static final androidx.camera.core.CameraState.Type OPENING;
-    enum_constant public static final androidx.camera.core.CameraState.Type PENDING_OPEN;
-  }
-
-  @RequiresApi(21) public class CameraUnavailableException extends java.lang.Exception {
-    ctor public CameraUnavailableException(int);
-    ctor public CameraUnavailableException(int, String?);
-    ctor public CameraUnavailableException(int, String?, Throwable?);
-    ctor public CameraUnavailableException(int, Throwable?);
-    method public int getReason();
-    field public static final int CAMERA_DISABLED = 1; // 0x1
-    field public static final int CAMERA_DISCONNECTED = 2; // 0x2
-    field public static final int CAMERA_ERROR = 3; // 0x3
-    field public static final int CAMERA_IN_USE = 4; // 0x4
-    field public static final int CAMERA_MAX_IN_USE = 5; // 0x5
-    field public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 6; // 0x6
-    field public static final int CAMERA_UNKNOWN_ERROR = 0; // 0x0
-  }
-
-  @RequiresApi(21) public final class CameraXConfig {
-    method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
-    method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
-    method public int getMinimumLoggingLevel();
-    method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
-  }
-
-  public static final class CameraXConfig.Builder {
-    method public androidx.camera.core.CameraXConfig build();
-    method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
-    method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
-    method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
-    method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
-  }
-
-  public static interface CameraXConfig.Provider {
-    method public androidx.camera.core.CameraXConfig getCameraXConfig();
-  }
-
-  @RequiresApi(21) public class ConcurrentCamera {
-    ctor public ConcurrentCamera(java.util.List<androidx.camera.core.Camera!>);
-    method public java.util.List<androidx.camera.core.Camera!> getCameras();
-  }
-
-  public static final class ConcurrentCamera.SingleCameraConfig {
-    ctor public ConcurrentCamera.SingleCameraConfig(androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup, androidx.lifecycle.LifecycleOwner);
-    method public androidx.camera.core.CameraSelector getCameraSelector();
-    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
-    method public androidx.camera.core.UseCaseGroup getUseCaseGroup();
-  }
-
-  @RequiresApi(21) public final class DisplayOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
-    ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
-  }
-
-  @RequiresApi(21) public final class DynamicRange {
-    ctor public DynamicRange(int, int);
-    method public int getBitDepth();
-    method public int getEncoding();
-    field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
-    field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
-    field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
-    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
-    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
-    field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
-    field public static final int ENCODING_HDR10 = 4; // 0x4
-    field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
-    field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
-    field public static final int ENCODING_HLG = 3; // 0x3
-    field public static final int ENCODING_SDR = 1; // 0x1
-    field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
-    field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
-    field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
-    field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
-    field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
-    field public static final androidx.camera.core.DynamicRange SDR;
-    field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
-  }
-
-  @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalZeroShutterLag {
-  }
-
-  @RequiresApi(21) public interface ExposureState {
-    method public int getExposureCompensationIndex();
-    method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
-    method public android.util.Rational getExposureCompensationStep();
-    method public boolean isExposureCompensationSupported();
-  }
-
-  @RequiresApi(21) public interface ExtendableBuilder<T> {
-    method public T build();
-  }
-
-  @RequiresApi(21) public final class FocusMeteringAction {
-    method public long getAutoCancelDurationInMillis();
-    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAe();
-    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAf();
-    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAwb();
-    method public boolean isAutoCancelEnabled();
-    field public static final int FLAG_AE = 2; // 0x2
-    field public static final int FLAG_AF = 1; // 0x1
-    field public static final int FLAG_AWB = 4; // 0x4
-  }
-
-  public static class FocusMeteringAction.Builder {
-    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint);
-    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint, int);
-    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint);
-    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint, int);
-    method public androidx.camera.core.FocusMeteringAction build();
-    method public androidx.camera.core.FocusMeteringAction.Builder disableAutoCancel();
-    method public androidx.camera.core.FocusMeteringAction.Builder setAutoCancelDuration(@IntRange(from=1) long, java.util.concurrent.TimeUnit);
-  }
-
-  @RequiresApi(21) public final class FocusMeteringResult {
-    method public boolean isFocusSuccessful();
-  }
-
-  @RequiresApi(21) public final class ImageAnalysis extends androidx.camera.core.UseCase {
-    method public void clearAnalyzer();
-    method @androidx.camera.core.ExperimentalUseCaseApi public java.util.concurrent.Executor? getBackgroundExecutor();
-    method public int getBackpressureStrategy();
-    method public int getImageQueueDepth();
-    method public int getOutputImageFormat();
-    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
-    method public int getTargetRotation();
-    method public boolean isOutputImageRotationEnabled();
-    method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
-    method public void setTargetRotation(int);
-    field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
-    field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
-    field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
-    field public static final int STRATEGY_BLOCK_PRODUCER = 1; // 0x1
-    field public static final int STRATEGY_KEEP_ONLY_LATEST = 0; // 0x0
-  }
-
-  public static interface ImageAnalysis.Analyzer {
-    method public void analyze(androidx.camera.core.ImageProxy);
-    method public default android.util.Size? getDefaultTargetResolution();
-    method public default int getTargetCoordinateSystem();
-    method public default void updateTransform(android.graphics.Matrix?);
-  }
-
-  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis> {
-    ctor public ImageAnalysis.Builder();
-    method public androidx.camera.core.ImageAnalysis build();
-    method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.core.ImageAnalysis.Builder setBackpressureStrategy(int);
-    method public androidx.camera.core.ImageAnalysis.Builder setImageQueueDepth(int);
-    method public androidx.camera.core.ImageAnalysis.Builder setOutputImageFormat(int);
-    method @RequiresApi(23) public androidx.camera.core.ImageAnalysis.Builder setOutputImageRotationEnabled(boolean);
-    method public androidx.camera.core.ImageAnalysis.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
-    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetAspectRatio(int);
-    method public androidx.camera.core.ImageAnalysis.Builder setTargetName(String);
-    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetResolution(android.util.Size);
-    method public androidx.camera.core.ImageAnalysis.Builder setTargetRotation(int);
-  }
-
-  @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
-    method public int getCaptureMode();
-    method public int getFlashMode();
-    method @IntRange(from=1, to=100) public int getJpegQuality();
-    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
-    method public int getTargetRotation();
-    method public void setCropAspectRatio(android.util.Rational);
-    method public void setFlashMode(int);
-    method public void setTargetRotation(int);
-    method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
-    field public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1; // 0x1
-    field @androidx.camera.core.ExperimentalZeroShutterLag public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2; // 0x2
-    field public static final int ERROR_CAMERA_CLOSED = 3; // 0x3
-    field public static final int ERROR_CAPTURE_FAILED = 2; // 0x2
-    field public static final int ERROR_FILE_IO = 1; // 0x1
-    field public static final int ERROR_INVALID_CAMERA = 4; // 0x4
-    field public static final int ERROR_UNKNOWN = 0; // 0x0
-    field public static final int FLASH_MODE_AUTO = 0; // 0x0
-    field public static final int FLASH_MODE_OFF = 2; // 0x2
-    field public static final int FLASH_MODE_ON = 1; // 0x1
-  }
-
-  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture> {
-    ctor public ImageCapture.Builder();
-    method public androidx.camera.core.ImageCapture build();
-    method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
-    method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
-    method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
-    method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
-    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
-    method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
-    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
-    method public androidx.camera.core.ImageCapture.Builder setTargetRotation(int);
-  }
-
-  public static final class ImageCapture.Metadata {
-    ctor public ImageCapture.Metadata();
-    method public android.location.Location? getLocation();
-    method public boolean isReversedHorizontal();
-    method public boolean isReversedVertical();
-    method public void setLocation(android.location.Location?);
-    method public void setReversedHorizontal(boolean);
-    method public void setReversedVertical(boolean);
-  }
-
-  public abstract static class ImageCapture.OnImageCapturedCallback {
-    ctor public ImageCapture.OnImageCapturedCallback();
-    method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
-    method public void onError(androidx.camera.core.ImageCaptureException);
-  }
-
-  public static interface ImageCapture.OnImageSavedCallback {
-    method public void onError(androidx.camera.core.ImageCaptureException);
-    method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
-  }
-
-  public static final class ImageCapture.OutputFileOptions {
-  }
-
-  public static final class ImageCapture.OutputFileOptions.Builder {
-    ctor public ImageCapture.OutputFileOptions.Builder(android.content.ContentResolver, android.net.Uri, android.content.ContentValues);
-    ctor public ImageCapture.OutputFileOptions.Builder(java.io.File);
-    ctor public ImageCapture.OutputFileOptions.Builder(java.io.OutputStream);
-    method public androidx.camera.core.ImageCapture.OutputFileOptions build();
-    method public androidx.camera.core.ImageCapture.OutputFileOptions.Builder setMetadata(androidx.camera.core.ImageCapture.Metadata);
-  }
-
-  public static class ImageCapture.OutputFileResults {
-    method public android.net.Uri? getSavedUri();
-  }
-
-  @RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
-    ctor public ImageCaptureException(int, String, Throwable?);
-    method public int getImageCaptureError();
-  }
-
-  @RequiresApi(21) public interface ImageInfo {
-    method public int getRotationDegrees();
-    method public default android.graphics.Matrix getSensorToBufferTransformMatrix();
-    method public long getTimestamp();
-  }
-
-  public interface ImageProcessor {
-    method public androidx.camera.core.ImageProcessor.Response process(androidx.camera.core.ImageProcessor.Request) throws androidx.camera.core.ProcessingException;
-  }
-
-  public static interface ImageProcessor.Request {
-    method public androidx.camera.core.ImageProxy getInputImage();
-    method public int getOutputFormat();
-  }
-
-  public static interface ImageProcessor.Response {
-    method public androidx.camera.core.ImageProxy getOutputImage();
-  }
-
-  @RequiresApi(21) public interface ImageProxy extends java.lang.AutoCloseable {
-    method public void close();
-    method public android.graphics.Rect getCropRect();
-    method public int getFormat();
-    method public int getHeight();
-    method @androidx.camera.core.ExperimentalGetImage public android.media.Image? getImage();
-    method public androidx.camera.core.ImageInfo getImageInfo();
-    method public androidx.camera.core.ImageProxy.PlaneProxy![] getPlanes();
-    method public int getWidth();
-    method public void setCropRect(android.graphics.Rect?);
-    method public default android.graphics.Bitmap toBitmap();
-  }
-
-  public static interface ImageProxy.PlaneProxy {
-    method public java.nio.ByteBuffer getBuffer();
-    method public int getPixelStride();
-    method public int getRowStride();
-  }
-
-  @RequiresApi(21) public class InitializationException extends java.lang.Exception {
-    ctor public InitializationException(String?);
-    ctor public InitializationException(String?, Throwable?);
-    ctor public InitializationException(Throwable?);
-  }
-
-  @RequiresApi(21) public class MeteringPoint {
-    method public float getSize();
-  }
-
-  @RequiresApi(21) public abstract class MeteringPointFactory {
-    method public final androidx.camera.core.MeteringPoint createPoint(float, float);
-    method public final androidx.camera.core.MeteringPoint createPoint(float, float, float);
-    method public static float getDefaultPointSize();
-  }
-
-  @RequiresApi(21) public class MirrorMode {
-    field public static final int MIRROR_MODE_OFF = 0; // 0x0
-    field public static final int MIRROR_MODE_ON = 1; // 0x1
-    field public static final int MIRROR_MODE_ON_FRONT_ONLY = 2; // 0x2
-  }
-
-  @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
-    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
-    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
-    method public int getTargetRotation();
-    method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
-    method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
-    method public void setTargetRotation(int);
-  }
-
-  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
-    ctor public Preview.Builder();
-    method public androidx.camera.core.Preview build();
-    method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
-    method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
-    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
-    method public androidx.camera.core.Preview.Builder setTargetName(String);
-    method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
-    method public androidx.camera.core.Preview.Builder setTargetRotation(int);
-  }
-
-  public static interface Preview.SurfaceProvider {
-    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
-  }
-
-  public class ProcessingException extends java.lang.Exception {
-    ctor public ProcessingException();
-  }
-
-  @RequiresApi(21) public class ResolutionInfo {
-    ctor public ResolutionInfo(android.util.Size, android.graphics.Rect, int);
-    method public android.graphics.Rect getCropRect();
-    method public android.util.Size getResolution();
-    method public int getRotationDegrees();
-  }
-
-  @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
-    ctor public SurfaceOrientedMeteringPointFactory(float, float);
-    ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
-  }
-
-  public interface SurfaceOutput extends java.io.Closeable {
-    method public void close();
-    method public android.util.Size getSize();
-    method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
-    method public int getTargets();
-    method public void updateTransformMatrix(float[], float[]);
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class SurfaceOutput.Event {
-    method public abstract int getEventCode();
-    method public abstract androidx.camera.core.SurfaceOutput getSurfaceOutput();
-    field public static final int EVENT_REQUEST_CLOSE = 0; // 0x0
-  }
-
-  public interface SurfaceProcessor {
-    method public void onInputSurface(androidx.camera.core.SurfaceRequest) throws androidx.camera.core.ProcessingException;
-    method public void onOutputSurface(androidx.camera.core.SurfaceOutput) throws androidx.camera.core.ProcessingException;
-  }
-
-  @RequiresApi(21) public final class SurfaceRequest {
-    method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
-    method public void clearTransformationInfoListener();
-    method public androidx.camera.core.DynamicRange getDynamicRange();
-    method public android.util.Size getResolution();
-    method public boolean invalidate();
-    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
-    method public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
-    method public boolean willNotProvideSurface();
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.Result {
-    method public abstract int getResultCode();
-    method public abstract android.view.Surface getSurface();
-    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
-    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
-    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
-    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
-    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
-    method public abstract android.graphics.Rect getCropRect();
-    method public abstract int getRotationDegrees();
-  }
-
-  public static interface SurfaceRequest.TransformationInfoListener {
-    method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
-  }
-
-  @RequiresApi(21) public class TorchState {
-    field public static final int OFF = 0; // 0x0
-    field public static final int ON = 1; // 0x1
-  }
-
-  @RequiresApi(21) public abstract class UseCase {
-    method public static int snapToSurfaceRotation(@IntRange(from=0, to=359) int);
-  }
-
-  @RequiresApi(21) public final class UseCaseGroup {
-    method public java.util.List<androidx.camera.core.CameraEffect!> getEffects();
-    method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
-    method public androidx.camera.core.ViewPort? getViewPort();
-  }
-
-  public static final class UseCaseGroup.Builder {
-    ctor public UseCaseGroup.Builder();
-    method public androidx.camera.core.UseCaseGroup.Builder addEffect(androidx.camera.core.CameraEffect);
-    method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
-    method public androidx.camera.core.UseCaseGroup build();
-    method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
-  }
-
-  @RequiresApi(21) public final class ViewPort {
-    method public android.util.Rational getAspectRatio();
-    method public int getLayoutDirection();
-    method public int getRotation();
-    method public int getScaleType();
-    field public static final int FILL_CENTER = 1; // 0x1
-    field public static final int FILL_END = 2; // 0x2
-    field public static final int FILL_START = 0; // 0x0
-    field public static final int FIT = 3; // 0x3
-  }
-
-  public static final class ViewPort.Builder {
-    ctor public ViewPort.Builder(android.util.Rational, int);
-    method public androidx.camera.core.ViewPort build();
-    method public androidx.camera.core.ViewPort.Builder setLayoutDirection(int);
-    method public androidx.camera.core.ViewPort.Builder setScaleType(int);
-  }
-
-  @RequiresApi(21) public interface ZoomState {
-    method public float getLinearZoom();
-    method public float getMaxZoomRatio();
-    method public float getMinZoomRatio();
-    method public float getZoomRatio();
-  }
-
-}
-
-package androidx.camera.core.resolutionselector {
-
-  @RequiresApi(21) public final class AspectRatioStrategy {
-    ctor public AspectRatioStrategy(int, int);
-    method public int getFallbackRule();
-    method public int getPreferredAspectRatio();
-    field public static final int FALLBACK_RULE_AUTO = 1; // 0x1
-    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
-    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_16_9_FALLBACK_AUTO_STRATEGY;
-    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_4_3_FALLBACK_AUTO_STRATEGY;
-  }
-
-  @RequiresApi(21) public interface ResolutionFilter {
-    method public java.util.List<android.util.Size!> filter(java.util.List<android.util.Size!>, int);
-  }
-
-  @RequiresApi(21) public final class ResolutionSelector {
-    method public int getAllowedResolutionMode();
-    method public androidx.camera.core.resolutionselector.AspectRatioStrategy getAspectRatioStrategy();
-    method public androidx.camera.core.resolutionselector.ResolutionFilter? getResolutionFilter();
-    method public androidx.camera.core.resolutionselector.ResolutionStrategy? getResolutionStrategy();
-    field public static final int PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION = 0; // 0x0
-    field public static final int PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE = 1; // 0x1
-  }
-
-  public static final class ResolutionSelector.Builder {
-    ctor public ResolutionSelector.Builder();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector build();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAllowedResolutionMode(int);
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAspectRatioStrategy(androidx.camera.core.resolutionselector.AspectRatioStrategy);
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter);
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionStrategy(androidx.camera.core.resolutionselector.ResolutionStrategy);
-  }
-
-  @RequiresApi(21) public final class ResolutionStrategy {
-    ctor public ResolutionStrategy(android.util.Size, int);
-    method public android.util.Size? getBoundSize();
-    method public int getFallbackRule();
-    field public static final int FALLBACK_RULE_CLOSEST_HIGHER = 2; // 0x2
-    field public static final int FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER = 1; // 0x1
-    field public static final int FALLBACK_RULE_CLOSEST_LOWER = 4; // 0x4
-    field public static final int FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER = 3; // 0x3
-    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
-    field public static final androidx.camera.core.resolutionselector.ResolutionStrategy HIGHEST_AVAILABLE_STRATEGY;
-  }
-
-}
-
diff --git a/camera/camera-core/api/res-1.3.0-beta01.txt b/camera/camera-core/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-core/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-core/api/restricted_1.3.0-beta01.txt b/camera/camera-core/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index 5cda9c6..0000000
--- a/camera/camera-core/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,612 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.core {
-
-  @RequiresApi(21) public class AspectRatio {
-    field public static final int RATIO_16_9 = 1; // 0x1
-    field public static final int RATIO_4_3 = 0; // 0x0
-    field public static final int RATIO_DEFAULT = -1; // 0xffffffff
-  }
-
-  @RequiresApi(21) public interface Camera {
-    method public androidx.camera.core.CameraControl getCameraControl();
-    method public androidx.camera.core.CameraInfo getCameraInfo();
-  }
-
-  @RequiresApi(21) public interface CameraControl {
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
-  }
-
-  public static final class CameraControl.OperationCanceledException extends java.lang.Exception {
-  }
-
-  @RequiresApi(21) public abstract class CameraEffect {
-    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.ImageProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
-    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.SurfaceProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
-    method public androidx.core.util.Consumer<java.lang.Throwable!> getErrorListener();
-    method public java.util.concurrent.Executor getExecutor();
-    method public androidx.camera.core.SurfaceProcessor? getSurfaceProcessor();
-    method public int getTargets();
-    field public static final int IMAGE_CAPTURE = 4; // 0x4
-    field public static final int PREVIEW = 1; // 0x1
-    field public static final int VIDEO_CAPTURE = 2; // 0x2
-  }
-
-  @RequiresApi(21) public interface CameraFilter {
-    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
-  }
-
-  @RequiresApi(21) public interface CameraInfo {
-    method public androidx.camera.core.CameraSelector getCameraSelector();
-    method public androidx.lifecycle.LiveData<androidx.camera.core.CameraState!> getCameraState();
-    method public androidx.camera.core.ExposureState getExposureState();
-    method @FloatRange(from=0, fromInclusive=false) public default float getIntrinsicZoomRatio();
-    method public default int getLensFacing();
-    method public int getSensorRotationDegrees();
-    method public int getSensorRotationDegrees(int);
-    method public default java.util.Set<android.util.Range<java.lang.Integer!>!> getSupportedFrameRateRanges();
-    method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
-    method public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
-    method public boolean hasFlashUnit();
-    method public default boolean isFocusMeteringSupported(androidx.camera.core.FocusMeteringAction);
-    method @androidx.camera.core.ExperimentalZeroShutterLag public default boolean isZslSupported();
-  }
-
-  @RequiresApi(21) public final class CameraInfoUnavailableException extends java.lang.Exception {
-  }
-
-  @RequiresApi(21) public interface CameraProvider {
-    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
-    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
-  }
-
-  @RequiresApi(21) public final class CameraSelector {
-    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
-    field public static final androidx.camera.core.CameraSelector DEFAULT_BACK_CAMERA;
-    field public static final androidx.camera.core.CameraSelector DEFAULT_FRONT_CAMERA;
-    field public static final int LENS_FACING_BACK = 1; // 0x1
-    field @androidx.camera.core.ExperimentalLensFacing public static final int LENS_FACING_EXTERNAL = 2; // 0x2
-    field public static final int LENS_FACING_FRONT = 0; // 0x0
-    field public static final int LENS_FACING_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public static final class CameraSelector.Builder {
-    ctor public CameraSelector.Builder();
-    method public androidx.camera.core.CameraSelector.Builder addCameraFilter(androidx.camera.core.CameraFilter);
-    method public androidx.camera.core.CameraSelector build();
-    method public androidx.camera.core.CameraSelector.Builder requireLensFacing(int);
-  }
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class CameraState {
-    ctor public CameraState();
-    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type);
-    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type, androidx.camera.core.CameraState.StateError?);
-    method public abstract androidx.camera.core.CameraState.StateError? getError();
-    method public abstract androidx.camera.core.CameraState.Type getType();
-    field public static final int ERROR_CAMERA_DISABLED = 5; // 0x5
-    field public static final int ERROR_CAMERA_FATAL_ERROR = 6; // 0x6
-    field public static final int ERROR_CAMERA_IN_USE = 2; // 0x2
-    field public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7; // 0x7
-    field public static final int ERROR_MAX_CAMERAS_IN_USE = 1; // 0x1
-    field public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3; // 0x3
-    field public static final int ERROR_STREAM_CONFIG = 4; // 0x4
-  }
-
-  public enum CameraState.ErrorType {
-    enum_constant public static final androidx.camera.core.CameraState.ErrorType CRITICAL;
-    enum_constant public static final androidx.camera.core.CameraState.ErrorType RECOVERABLE;
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class CameraState.StateError {
-    ctor public CameraState.StateError();
-    method public static androidx.camera.core.CameraState.StateError create(int);
-    method public static androidx.camera.core.CameraState.StateError create(int, Throwable?);
-    method public abstract Throwable? getCause();
-    method public abstract int getCode();
-    method public androidx.camera.core.CameraState.ErrorType getType();
-  }
-
-  public enum CameraState.Type {
-    enum_constant public static final androidx.camera.core.CameraState.Type CLOSED;
-    enum_constant public static final androidx.camera.core.CameraState.Type CLOSING;
-    enum_constant public static final androidx.camera.core.CameraState.Type OPEN;
-    enum_constant public static final androidx.camera.core.CameraState.Type OPENING;
-    enum_constant public static final androidx.camera.core.CameraState.Type PENDING_OPEN;
-  }
-
-  @RequiresApi(21) public class CameraUnavailableException extends java.lang.Exception {
-    ctor public CameraUnavailableException(int);
-    ctor public CameraUnavailableException(int, String?);
-    ctor public CameraUnavailableException(int, String?, Throwable?);
-    ctor public CameraUnavailableException(int, Throwable?);
-    method public int getReason();
-    field public static final int CAMERA_DISABLED = 1; // 0x1
-    field public static final int CAMERA_DISCONNECTED = 2; // 0x2
-    field public static final int CAMERA_ERROR = 3; // 0x3
-    field public static final int CAMERA_IN_USE = 4; // 0x4
-    field public static final int CAMERA_MAX_IN_USE = 5; // 0x5
-    field public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 6; // 0x6
-    field public static final int CAMERA_UNKNOWN_ERROR = 0; // 0x0
-  }
-
-  @RequiresApi(21) public final class CameraXConfig {
-    method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
-    method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
-    method public int getMinimumLoggingLevel();
-    method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
-  }
-
-  public static final class CameraXConfig.Builder {
-    method public androidx.camera.core.CameraXConfig build();
-    method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
-    method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
-    method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
-    method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
-  }
-
-  public static interface CameraXConfig.Provider {
-    method public androidx.camera.core.CameraXConfig getCameraXConfig();
-  }
-
-  @RequiresApi(21) public class ConcurrentCamera {
-    ctor public ConcurrentCamera(java.util.List<androidx.camera.core.Camera!>);
-    method public java.util.List<androidx.camera.core.Camera!> getCameras();
-  }
-
-  public static final class ConcurrentCamera.SingleCameraConfig {
-    ctor public ConcurrentCamera.SingleCameraConfig(androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup, androidx.lifecycle.LifecycleOwner);
-    method public androidx.camera.core.CameraSelector getCameraSelector();
-    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
-    method public androidx.camera.core.UseCaseGroup getUseCaseGroup();
-  }
-
-  @RequiresApi(21) public final class DisplayOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
-    ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
-  }
-
-  @RequiresApi(21) public final class DynamicRange {
-    ctor public DynamicRange(int, int);
-    method public int getBitDepth();
-    method public int getEncoding();
-    field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
-    field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
-    field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
-    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
-    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
-    field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
-    field public static final int ENCODING_HDR10 = 4; // 0x4
-    field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
-    field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
-    field public static final int ENCODING_HLG = 3; // 0x3
-    field public static final int ENCODING_SDR = 1; // 0x1
-    field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
-    field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
-    field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
-    field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
-    field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
-    field public static final androidx.camera.core.DynamicRange SDR;
-    field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
-  }
-
-  @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
-  }
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalZeroShutterLag {
-  }
-
-  @RequiresApi(21) public interface ExposureState {
-    method public int getExposureCompensationIndex();
-    method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
-    method public android.util.Rational getExposureCompensationStep();
-    method public boolean isExposureCompensationSupported();
-  }
-
-  @RequiresApi(21) public interface ExtendableBuilder<T> {
-    method public T build();
-  }
-
-  @RequiresApi(21) public final class FocusMeteringAction {
-    method public long getAutoCancelDurationInMillis();
-    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAe();
-    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAf();
-    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAwb();
-    method public boolean isAutoCancelEnabled();
-    field public static final int FLAG_AE = 2; // 0x2
-    field public static final int FLAG_AF = 1; // 0x1
-    field public static final int FLAG_AWB = 4; // 0x4
-  }
-
-  public static class FocusMeteringAction.Builder {
-    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint);
-    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint, int);
-    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint);
-    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint, int);
-    method public androidx.camera.core.FocusMeteringAction build();
-    method public androidx.camera.core.FocusMeteringAction.Builder disableAutoCancel();
-    method public androidx.camera.core.FocusMeteringAction.Builder setAutoCancelDuration(@IntRange(from=1) long, java.util.concurrent.TimeUnit);
-  }
-
-  @RequiresApi(21) public final class FocusMeteringResult {
-    method public boolean isFocusSuccessful();
-  }
-
-  @RequiresApi(21) public final class ImageAnalysis extends androidx.camera.core.UseCase {
-    method public void clearAnalyzer();
-    method @androidx.camera.core.ExperimentalUseCaseApi public java.util.concurrent.Executor? getBackgroundExecutor();
-    method public int getBackpressureStrategy();
-    method public int getImageQueueDepth();
-    method public int getOutputImageFormat();
-    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
-    method public int getTargetRotation();
-    method public boolean isOutputImageRotationEnabled();
-    method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
-    method public void setTargetRotation(int);
-    field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
-    field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
-    field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
-    field public static final int STRATEGY_BLOCK_PRODUCER = 1; // 0x1
-    field public static final int STRATEGY_KEEP_ONLY_LATEST = 0; // 0x0
-  }
-
-  public static interface ImageAnalysis.Analyzer {
-    method public void analyze(androidx.camera.core.ImageProxy);
-    method public default android.util.Size? getDefaultTargetResolution();
-    method public default int getTargetCoordinateSystem();
-    method public default void updateTransform(android.graphics.Matrix?);
-  }
-
-  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis> {
-    ctor public ImageAnalysis.Builder();
-    method public androidx.camera.core.ImageAnalysis build();
-    method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.core.ImageAnalysis.Builder setBackpressureStrategy(int);
-    method public androidx.camera.core.ImageAnalysis.Builder setImageQueueDepth(int);
-    method public androidx.camera.core.ImageAnalysis.Builder setOutputImageFormat(int);
-    method @RequiresApi(23) public androidx.camera.core.ImageAnalysis.Builder setOutputImageRotationEnabled(boolean);
-    method public androidx.camera.core.ImageAnalysis.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
-    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetAspectRatio(int);
-    method public androidx.camera.core.ImageAnalysis.Builder setTargetName(String);
-    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetResolution(android.util.Size);
-    method public androidx.camera.core.ImageAnalysis.Builder setTargetRotation(int);
-  }
-
-  @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
-    method public int getCaptureMode();
-    method public int getFlashMode();
-    method @IntRange(from=1, to=100) public int getJpegQuality();
-    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
-    method public int getTargetRotation();
-    method public void setCropAspectRatio(android.util.Rational);
-    method public void setFlashMode(int);
-    method public void setTargetRotation(int);
-    method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
-    field public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1; // 0x1
-    field @androidx.camera.core.ExperimentalZeroShutterLag public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2; // 0x2
-    field public static final int ERROR_CAMERA_CLOSED = 3; // 0x3
-    field public static final int ERROR_CAPTURE_FAILED = 2; // 0x2
-    field public static final int ERROR_FILE_IO = 1; // 0x1
-    field public static final int ERROR_INVALID_CAMERA = 4; // 0x4
-    field public static final int ERROR_UNKNOWN = 0; // 0x0
-    field public static final int FLASH_MODE_AUTO = 0; // 0x0
-    field public static final int FLASH_MODE_OFF = 2; // 0x2
-    field public static final int FLASH_MODE_ON = 1; // 0x1
-  }
-
-  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture> {
-    ctor public ImageCapture.Builder();
-    method public androidx.camera.core.ImageCapture build();
-    method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
-    method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
-    method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
-    method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
-    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
-    method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
-    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
-    method public androidx.camera.core.ImageCapture.Builder setTargetRotation(int);
-  }
-
-  public static final class ImageCapture.Metadata {
-    ctor public ImageCapture.Metadata();
-    method public android.location.Location? getLocation();
-    method public boolean isReversedHorizontal();
-    method public boolean isReversedVertical();
-    method public void setLocation(android.location.Location?);
-    method public void setReversedHorizontal(boolean);
-    method public void setReversedVertical(boolean);
-  }
-
-  public abstract static class ImageCapture.OnImageCapturedCallback {
-    ctor public ImageCapture.OnImageCapturedCallback();
-    method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
-    method public void onError(androidx.camera.core.ImageCaptureException);
-  }
-
-  public static interface ImageCapture.OnImageSavedCallback {
-    method public void onError(androidx.camera.core.ImageCaptureException);
-    method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
-  }
-
-  public static final class ImageCapture.OutputFileOptions {
-  }
-
-  public static final class ImageCapture.OutputFileOptions.Builder {
-    ctor public ImageCapture.OutputFileOptions.Builder(android.content.ContentResolver, android.net.Uri, android.content.ContentValues);
-    ctor public ImageCapture.OutputFileOptions.Builder(java.io.File);
-    ctor public ImageCapture.OutputFileOptions.Builder(java.io.OutputStream);
-    method public androidx.camera.core.ImageCapture.OutputFileOptions build();
-    method public androidx.camera.core.ImageCapture.OutputFileOptions.Builder setMetadata(androidx.camera.core.ImageCapture.Metadata);
-  }
-
-  public static class ImageCapture.OutputFileResults {
-    method public android.net.Uri? getSavedUri();
-  }
-
-  @RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
-    ctor public ImageCaptureException(int, String, Throwable?);
-    method public int getImageCaptureError();
-  }
-
-  @RequiresApi(21) public interface ImageInfo {
-    method public int getRotationDegrees();
-    method public default android.graphics.Matrix getSensorToBufferTransformMatrix();
-    method public long getTimestamp();
-  }
-
-  public interface ImageProcessor {
-    method public androidx.camera.core.ImageProcessor.Response process(androidx.camera.core.ImageProcessor.Request) throws androidx.camera.core.ProcessingException;
-  }
-
-  public static interface ImageProcessor.Request {
-    method public androidx.camera.core.ImageProxy getInputImage();
-    method public int getOutputFormat();
-  }
-
-  public static interface ImageProcessor.Response {
-    method public androidx.camera.core.ImageProxy getOutputImage();
-  }
-
-  @RequiresApi(21) public interface ImageProxy extends java.lang.AutoCloseable {
-    method public void close();
-    method public android.graphics.Rect getCropRect();
-    method public int getFormat();
-    method public int getHeight();
-    method @androidx.camera.core.ExperimentalGetImage public android.media.Image? getImage();
-    method public androidx.camera.core.ImageInfo getImageInfo();
-    method public androidx.camera.core.ImageProxy.PlaneProxy![] getPlanes();
-    method public int getWidth();
-    method public void setCropRect(android.graphics.Rect?);
-    method public default android.graphics.Bitmap toBitmap();
-  }
-
-  public static interface ImageProxy.PlaneProxy {
-    method public java.nio.ByteBuffer getBuffer();
-    method public int getPixelStride();
-    method public int getRowStride();
-  }
-
-  @RequiresApi(21) public class InitializationException extends java.lang.Exception {
-    ctor public InitializationException(String?);
-    ctor public InitializationException(String?, Throwable?);
-    ctor public InitializationException(Throwable?);
-  }
-
-  @RequiresApi(21) public class MeteringPoint {
-    method public float getSize();
-  }
-
-  @RequiresApi(21) public abstract class MeteringPointFactory {
-    method public final androidx.camera.core.MeteringPoint createPoint(float, float);
-    method public final androidx.camera.core.MeteringPoint createPoint(float, float, float);
-    method public static float getDefaultPointSize();
-  }
-
-  @RequiresApi(21) public class MirrorMode {
-    field public static final int MIRROR_MODE_OFF = 0; // 0x0
-    field public static final int MIRROR_MODE_ON = 1; // 0x1
-    field public static final int MIRROR_MODE_ON_FRONT_ONLY = 2; // 0x2
-  }
-
-  @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
-    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
-    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
-    method public int getTargetRotation();
-    method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
-    method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
-    method public void setTargetRotation(int);
-  }
-
-  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
-    ctor public Preview.Builder();
-    method public androidx.camera.core.Preview build();
-    method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
-    method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
-    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
-    method public androidx.camera.core.Preview.Builder setTargetName(String);
-    method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
-    method public androidx.camera.core.Preview.Builder setTargetRotation(int);
-  }
-
-  public static interface Preview.SurfaceProvider {
-    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
-  }
-
-  public class ProcessingException extends java.lang.Exception {
-    ctor public ProcessingException();
-  }
-
-  @RequiresApi(21) public class ResolutionInfo {
-    ctor public ResolutionInfo(android.util.Size, android.graphics.Rect, int);
-    method public android.graphics.Rect getCropRect();
-    method public android.util.Size getResolution();
-    method public int getRotationDegrees();
-  }
-
-  @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
-    ctor public SurfaceOrientedMeteringPointFactory(float, float);
-    ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
-  }
-
-  public interface SurfaceOutput extends java.io.Closeable {
-    method public void close();
-    method public android.util.Size getSize();
-    method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
-    method public int getTargets();
-    method public void updateTransformMatrix(float[], float[]);
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class SurfaceOutput.Event {
-    method public abstract int getEventCode();
-    method public abstract androidx.camera.core.SurfaceOutput getSurfaceOutput();
-    field public static final int EVENT_REQUEST_CLOSE = 0; // 0x0
-  }
-
-  public interface SurfaceProcessor {
-    method public void onInputSurface(androidx.camera.core.SurfaceRequest) throws androidx.camera.core.ProcessingException;
-    method public void onOutputSurface(androidx.camera.core.SurfaceOutput) throws androidx.camera.core.ProcessingException;
-  }
-
-  @RequiresApi(21) public final class SurfaceRequest {
-    method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
-    method public void clearTransformationInfoListener();
-    method public androidx.camera.core.DynamicRange getDynamicRange();
-    method public android.util.Size getResolution();
-    method public boolean invalidate();
-    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
-    method public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
-    method public boolean willNotProvideSurface();
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.Result {
-    method public abstract int getResultCode();
-    method public abstract android.view.Surface getSurface();
-    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
-    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
-    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
-    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
-    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
-  }
-
-  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
-    method public abstract android.graphics.Rect getCropRect();
-    method public abstract int getRotationDegrees();
-  }
-
-  public static interface SurfaceRequest.TransformationInfoListener {
-    method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
-  }
-
-  @RequiresApi(21) public class TorchState {
-    field public static final int OFF = 0; // 0x0
-    field public static final int ON = 1; // 0x1
-  }
-
-  @RequiresApi(21) public abstract class UseCase {
-    method public static int snapToSurfaceRotation(@IntRange(from=0, to=359) int);
-  }
-
-  @RequiresApi(21) public final class UseCaseGroup {
-    method public java.util.List<androidx.camera.core.CameraEffect!> getEffects();
-    method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
-    method public androidx.camera.core.ViewPort? getViewPort();
-  }
-
-  public static final class UseCaseGroup.Builder {
-    ctor public UseCaseGroup.Builder();
-    method public androidx.camera.core.UseCaseGroup.Builder addEffect(androidx.camera.core.CameraEffect);
-    method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
-    method public androidx.camera.core.UseCaseGroup build();
-    method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
-  }
-
-  @RequiresApi(21) public final class ViewPort {
-    method public android.util.Rational getAspectRatio();
-    method public int getLayoutDirection();
-    method public int getRotation();
-    method public int getScaleType();
-    field public static final int FILL_CENTER = 1; // 0x1
-    field public static final int FILL_END = 2; // 0x2
-    field public static final int FILL_START = 0; // 0x0
-    field public static final int FIT = 3; // 0x3
-  }
-
-  public static final class ViewPort.Builder {
-    ctor public ViewPort.Builder(android.util.Rational, int);
-    method public androidx.camera.core.ViewPort build();
-    method public androidx.camera.core.ViewPort.Builder setLayoutDirection(int);
-    method public androidx.camera.core.ViewPort.Builder setScaleType(int);
-  }
-
-  @RequiresApi(21) public interface ZoomState {
-    method public float getLinearZoom();
-    method public float getMaxZoomRatio();
-    method public float getMinZoomRatio();
-    method public float getZoomRatio();
-  }
-
-}
-
-package androidx.camera.core.resolutionselector {
-
-  @RequiresApi(21) public final class AspectRatioStrategy {
-    ctor public AspectRatioStrategy(int, int);
-    method public int getFallbackRule();
-    method public int getPreferredAspectRatio();
-    field public static final int FALLBACK_RULE_AUTO = 1; // 0x1
-    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
-    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_16_9_FALLBACK_AUTO_STRATEGY;
-    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_4_3_FALLBACK_AUTO_STRATEGY;
-  }
-
-  @RequiresApi(21) public interface ResolutionFilter {
-    method public java.util.List<android.util.Size!> filter(java.util.List<android.util.Size!>, int);
-  }
-
-  @RequiresApi(21) public final class ResolutionSelector {
-    method public int getAllowedResolutionMode();
-    method public androidx.camera.core.resolutionselector.AspectRatioStrategy getAspectRatioStrategy();
-    method public androidx.camera.core.resolutionselector.ResolutionFilter? getResolutionFilter();
-    method public androidx.camera.core.resolutionselector.ResolutionStrategy? getResolutionStrategy();
-    field public static final int PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION = 0; // 0x0
-    field public static final int PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE = 1; // 0x1
-  }
-
-  public static final class ResolutionSelector.Builder {
-    ctor public ResolutionSelector.Builder();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector build();
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAllowedResolutionMode(int);
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAspectRatioStrategy(androidx.camera.core.resolutionselector.AspectRatioStrategy);
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter);
-    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionStrategy(androidx.camera.core.resolutionselector.ResolutionStrategy);
-  }
-
-  @RequiresApi(21) public final class ResolutionStrategy {
-    ctor public ResolutionStrategy(android.util.Size, int);
-    method public android.util.Size? getBoundSize();
-    method public int getFallbackRule();
-    field public static final int FALLBACK_RULE_CLOSEST_HIGHER = 2; // 0x2
-    field public static final int FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER = 1; // 0x1
-    field public static final int FALLBACK_RULE_CLOSEST_LOWER = 4; // 0x4
-    field public static final int FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER = 3; // 0x3
-    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
-    field public static final androidx.camera.core.resolutionselector.ResolutionStrategy HIGHEST_AVAILABLE_STRATEGY;
-  }
-
-}
-
diff --git a/camera/camera-effects-still-portrait/api/res-1.3.0-beta01.txt b/camera/camera-effects-still-portrait/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-effects-still-portrait/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-effects-still-portrait/api/restricted_1.3.0-beta01.txt b/camera/camera-effects-still-portrait/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-effects-still-portrait/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-effects/api/res-1.3.0-beta01.txt b/camera/camera-effects/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-effects/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-effects/api/restricted_1.3.0-beta01.txt b/camera/camera-effects/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-effects/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-extensions/api/1.3.0-beta01.txt b/camera/camera-extensions/api/1.3.0-beta01.txt
deleted file mode 100644
index 5c6e740..0000000
--- a/camera/camera-extensions/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.extensions {
-
-  @RequiresApi(21) public final class ExtensionMode {
-    field public static final int AUTO = 5; // 0x5
-    field public static final int BOKEH = 1; // 0x1
-    field public static final int FACE_RETOUCH = 4; // 0x4
-    field public static final int HDR = 2; // 0x2
-    field public static final int NIGHT = 3; // 0x3
-    field public static final int NONE = 0; // 0x0
-  }
-
-  @RequiresApi(21) public final class ExtensionsManager {
-    method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
-    method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
-    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
-    method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
-    method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
-  }
-
-}
-
diff --git a/camera/camera-extensions/api/res-1.3.0-beta01.txt b/camera/camera-extensions/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-extensions/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-extensions/api/restricted_1.3.0-beta01.txt b/camera/camera-extensions/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index 5c6e740..0000000
--- a/camera/camera-extensions/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.extensions {
-
-  @RequiresApi(21) public final class ExtensionMode {
-    field public static final int AUTO = 5; // 0x5
-    field public static final int BOKEH = 1; // 0x1
-    field public static final int FACE_RETOUCH = 4; // 0x4
-    field public static final int HDR = 2; // 0x2
-    field public static final int NIGHT = 3; // 0x3
-    field public static final int NONE = 0; // 0x0
-  }
-
-  @RequiresApi(21) public final class ExtensionsManager {
-    method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
-    method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
-    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
-    method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
-    method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
-  }
-
-}
-
diff --git a/camera/camera-lifecycle/api/1.3.0-beta01.txt b/camera/camera-lifecycle/api/1.3.0-beta01.txt
deleted file mode 100644
index 606a816..0000000
--- a/camera/camera-lifecycle/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.lifecycle {
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
-  }
-
-  @RequiresApi(21) public final class ProcessCameraProvider implements androidx.camera.core.CameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
-    method @MainThread public androidx.camera.core.ConcurrentCamera bindToLifecycle(java.util.List<androidx.camera.core.ConcurrentCamera.SingleCameraConfig!>);
-    method @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
-    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
-    method public java.util.List<java.util.List<androidx.camera.core.CameraInfo!>!> getAvailableConcurrentCameraInfos();
-    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
-    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
-    method public boolean isBound(androidx.camera.core.UseCase);
-    method @MainThread public boolean isConcurrentCameraModeOn();
-    method @MainThread public void unbind(androidx.camera.core.UseCase!...);
-    method @MainThread public void unbindAll();
-  }
-
-}
-
diff --git a/camera/camera-lifecycle/api/res-1.3.0-beta01.txt b/camera/camera-lifecycle/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-lifecycle/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-lifecycle/api/restricted_1.3.0-beta01.txt b/camera/camera-lifecycle/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index 606a816..0000000
--- a/camera/camera-lifecycle/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.lifecycle {
-
-  @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
-  }
-
-  @RequiresApi(21) public final class ProcessCameraProvider implements androidx.camera.core.CameraProvider {
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
-    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
-    method @MainThread public androidx.camera.core.ConcurrentCamera bindToLifecycle(java.util.List<androidx.camera.core.ConcurrentCamera.SingleCameraConfig!>);
-    method @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
-    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
-    method public java.util.List<java.util.List<androidx.camera.core.CameraInfo!>!> getAvailableConcurrentCameraInfos();
-    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
-    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
-    method public boolean isBound(androidx.camera.core.UseCase);
-    method @MainThread public boolean isConcurrentCameraModeOn();
-    method @MainThread public void unbind(androidx.camera.core.UseCase!...);
-    method @MainThread public void unbindAll();
-  }
-
-}
-
diff --git a/camera/camera-mlkit-vision/api/1.3.0-beta01.txt b/camera/camera-mlkit-vision/api/1.3.0-beta01.txt
deleted file mode 100644
index 0599c25..0000000
--- a/camera/camera-mlkit-vision/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.mlkit.vision {
-
-  @RequiresApi(21) public class MlKitAnalyzer implements androidx.camera.core.ImageAnalysis.Analyzer {
-    ctor public MlKitAnalyzer(java.util.List<com.google.mlkit.vision.interfaces.Detector<?>!>, int, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.mlkit.vision.MlKitAnalyzer.Result!>);
-    method public final void analyze(androidx.camera.core.ImageProxy);
-    method public final android.util.Size getDefaultTargetResolution();
-    method public final int getTargetCoordinateSystem();
-    method public final void updateTransform(android.graphics.Matrix?);
-  }
-
-  public static final class MlKitAnalyzer.Result {
-    ctor public MlKitAnalyzer.Result(java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Object!>, long, java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Throwable!>);
-    method public Throwable? getThrowable(com.google.mlkit.vision.interfaces.Detector<?>);
-    method public long getTimestamp();
-    method public <T> T? getValue(com.google.mlkit.vision.interfaces.Detector<T!>);
-  }
-
-}
-
diff --git a/camera/camera-mlkit-vision/api/res-1.3.0-beta01.txt b/camera/camera-mlkit-vision/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-mlkit-vision/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-mlkit-vision/api/restricted_1.3.0-beta01.txt b/camera/camera-mlkit-vision/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index 0599c25..0000000
--- a/camera/camera-mlkit-vision/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.mlkit.vision {
-
-  @RequiresApi(21) public class MlKitAnalyzer implements androidx.camera.core.ImageAnalysis.Analyzer {
-    ctor public MlKitAnalyzer(java.util.List<com.google.mlkit.vision.interfaces.Detector<?>!>, int, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.mlkit.vision.MlKitAnalyzer.Result!>);
-    method public final void analyze(androidx.camera.core.ImageProxy);
-    method public final android.util.Size getDefaultTargetResolution();
-    method public final int getTargetCoordinateSystem();
-    method public final void updateTransform(android.graphics.Matrix?);
-  }
-
-  public static final class MlKitAnalyzer.Result {
-    ctor public MlKitAnalyzer.Result(java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Object!>, long, java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Throwable!>);
-    method public Throwable? getThrowable(com.google.mlkit.vision.interfaces.Detector<?>);
-    method public long getTimestamp();
-    method public <T> T? getValue(com.google.mlkit.vision.interfaces.Detector<T!>);
-  }
-
-}
-
diff --git a/camera/camera-video/api/1.3.0-beta01.txt b/camera/camera-video/api/1.3.0-beta01.txt
deleted file mode 100644
index fb0f4c7..0000000
--- a/camera/camera-video/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.video {
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
-    method public abstract int getAudioState();
-    method public abstract Throwable? getErrorCause();
-    method public boolean hasAudio();
-    method public boolean hasError();
-    field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
-    field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
-    field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
-    field public static final int AUDIO_STATE_MUTED = 5; // 0x5
-    field public static final int AUDIO_STATE_SOURCE_ERROR = 4; // 0x4
-    field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
-  }
-
-  @RequiresApi(21) public class FallbackStrategy {
-    method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
-    method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
-    method public static androidx.camera.video.FallbackStrategy lowerQualityOrHigherThan(androidx.camera.video.Quality);
-    method public static androidx.camera.video.FallbackStrategy lowerQualityThan(androidx.camera.video.Quality);
-  }
-
-  @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-  }
-
-  @RequiresApi(21) public static final class FileDescriptorOutputOptions.Builder {
-    ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.camera.video.FileDescriptorOutputOptions build();
-    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
-    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
-    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
-  }
-
-  @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
-    method public java.io.File getFile();
-  }
-
-  @RequiresApi(21) public static final class FileOutputOptions.Builder {
-    ctor public FileOutputOptions.Builder(java.io.File);
-    method public androidx.camera.video.FileOutputOptions build();
-    method public androidx.camera.video.FileOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
-    method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
-    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
-  }
-
-  @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.net.Uri getCollectionUri();
-    method public android.content.ContentResolver getContentResolver();
-    method public android.content.ContentValues getContentValues();
-    field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
-  }
-
-  public static final class MediaStoreOutputOptions.Builder {
-    ctor public MediaStoreOutputOptions.Builder(android.content.ContentResolver, android.net.Uri);
-    method public androidx.camera.video.MediaStoreOutputOptions build();
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
-  }
-
-  @RequiresApi(21) public abstract class OutputOptions {
-    method @IntRange(from=0) public long getDurationLimitMillis();
-    method @IntRange(from=0) public long getFileSizeLimit();
-    method public android.location.Location? getLocation();
-    field public static final int DURATION_UNLIMITED = 0; // 0x0
-    field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
-  }
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class OutputResults {
-    ctor public OutputResults();
-    method public abstract android.net.Uri getOutputUri();
-  }
-
-  @RequiresApi(21) public final class PendingRecording {
-    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
-  }
-
-  @RequiresApi(21) public class Quality {
-    field public static final androidx.camera.video.Quality FHD;
-    field public static final androidx.camera.video.Quality HD;
-    field public static final androidx.camera.video.Quality HIGHEST;
-    field public static final androidx.camera.video.Quality LOWEST;
-    field public static final androidx.camera.video.Quality SD;
-    field public static final androidx.camera.video.Quality UHD;
-  }
-
-  @RequiresApi(21) public final class QualitySelector {
-    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality);
-    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality, androidx.camera.video.FallbackStrategy);
-    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
-    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
-    method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
-    method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
-    method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
-  }
-
-  @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
-    method public int getAspectRatio();
-    method public java.util.concurrent.Executor? getExecutor();
-    method public androidx.camera.video.QualitySelector getQualitySelector();
-    method public int getTargetVideoEncodingBitRate();
-    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
-    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
-    method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
-    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
-    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.MediaStoreOutputOptions);
-    field public static final androidx.camera.video.QualitySelector DEFAULT_QUALITY_SELECTOR;
-  }
-
-  @RequiresApi(21) public static final class Recorder.Builder {
-    ctor public Recorder.Builder();
-    method public androidx.camera.video.Recorder build();
-    method public androidx.camera.video.Recorder.Builder setAspectRatio(int);
-    method public androidx.camera.video.Recorder.Builder setExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.video.Recorder.Builder setQualitySelector(androidx.camera.video.QualitySelector);
-    method public androidx.camera.video.Recorder.Builder setTargetVideoEncodingBitRate(@IntRange(from=1) int);
-  }
-
-  @RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
-    method public void close();
-    method public void mute(boolean);
-    method public void pause();
-    method public void resume();
-    method public void stop();
-  }
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class RecordingStats {
-    method public abstract androidx.camera.video.AudioStats getAudioStats();
-    method public abstract long getNumBytesRecorded();
-    method public abstract long getRecordedDurationNanos();
-  }
-
-  @RequiresApi(21) public interface VideoCapabilities {
-    method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
-    method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
-    method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
-  }
-
-  @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
-    method public androidx.camera.core.DynamicRange getDynamicRange();
-    method public int getMirrorMode();
-    method public T getOutput();
-    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
-    method public int getTargetRotation();
-    method public void setTargetRotation(int);
-    method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
-  }
-
-  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
-    ctor public VideoCapture.Builder(T);
-    method public androidx.camera.video.VideoCapture<T!> build();
-    method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
-    method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
-    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
-    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
-  }
-
-  @RequiresApi(21) public interface VideoOutput {
-    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
-  }
-
-  @RequiresApi(21) public abstract class VideoRecordEvent {
-    method public androidx.camera.video.OutputOptions getOutputOptions();
-    method public androidx.camera.video.RecordingStats getRecordingStats();
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Finalize extends androidx.camera.video.VideoRecordEvent {
-    method public Throwable? getCause();
-    method public int getError();
-    method public androidx.camera.video.OutputResults getOutputResults();
-    method public boolean hasError();
-    field public static final int ERROR_DURATION_LIMIT_REACHED = 9; // 0x9
-    field public static final int ERROR_ENCODING_FAILED = 6; // 0x6
-    field public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2; // 0x2
-    field public static final int ERROR_INSUFFICIENT_STORAGE = 3; // 0x3
-    field public static final int ERROR_INVALID_OUTPUT_OPTIONS = 5; // 0x5
-    field public static final int ERROR_NONE = 0; // 0x0
-    field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
-    field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
-    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
-    field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
-    field public static final int ERROR_UNKNOWN = 1; // 0x1
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Pause extends androidx.camera.video.VideoRecordEvent {
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Resume extends androidx.camera.video.VideoRecordEvent {
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Start extends androidx.camera.video.VideoRecordEvent {
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Status extends androidx.camera.video.VideoRecordEvent {
-  }
-
-}
-
diff --git a/camera/camera-video/api/res-1.3.0-beta01.txt b/camera/camera-video/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-video/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-video/api/restricted_1.3.0-beta01.txt b/camera/camera-video/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index fb0f4c7..0000000
--- a/camera/camera-video/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,202 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.video {
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
-    method public abstract int getAudioState();
-    method public abstract Throwable? getErrorCause();
-    method public boolean hasAudio();
-    method public boolean hasError();
-    field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
-    field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
-    field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
-    field public static final int AUDIO_STATE_MUTED = 5; // 0x5
-    field public static final int AUDIO_STATE_SOURCE_ERROR = 4; // 0x4
-    field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
-  }
-
-  @RequiresApi(21) public class FallbackStrategy {
-    method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
-    method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
-    method public static androidx.camera.video.FallbackStrategy lowerQualityOrHigherThan(androidx.camera.video.Quality);
-    method public static androidx.camera.video.FallbackStrategy lowerQualityThan(androidx.camera.video.Quality);
-  }
-
-  @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
-  }
-
-  @RequiresApi(21) public static final class FileDescriptorOutputOptions.Builder {
-    ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
-    method public androidx.camera.video.FileDescriptorOutputOptions build();
-    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
-    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
-    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
-  }
-
-  @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
-    method public java.io.File getFile();
-  }
-
-  @RequiresApi(21) public static final class FileOutputOptions.Builder {
-    ctor public FileOutputOptions.Builder(java.io.File);
-    method public androidx.camera.video.FileOutputOptions build();
-    method public androidx.camera.video.FileOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
-    method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
-    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
-  }
-
-  @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
-    method public android.net.Uri getCollectionUri();
-    method public android.content.ContentResolver getContentResolver();
-    method public android.content.ContentValues getContentValues();
-    field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
-  }
-
-  public static final class MediaStoreOutputOptions.Builder {
-    ctor public MediaStoreOutputOptions.Builder(android.content.ContentResolver, android.net.Uri);
-    method public androidx.camera.video.MediaStoreOutputOptions build();
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
-    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
-  }
-
-  @RequiresApi(21) public abstract class OutputOptions {
-    method @IntRange(from=0) public long getDurationLimitMillis();
-    method @IntRange(from=0) public long getFileSizeLimit();
-    method public android.location.Location? getLocation();
-    field public static final int DURATION_UNLIMITED = 0; // 0x0
-    field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
-  }
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class OutputResults {
-    ctor public OutputResults();
-    method public abstract android.net.Uri getOutputUri();
-  }
-
-  @RequiresApi(21) public final class PendingRecording {
-    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
-  }
-
-  @RequiresApi(21) public class Quality {
-    field public static final androidx.camera.video.Quality FHD;
-    field public static final androidx.camera.video.Quality HD;
-    field public static final androidx.camera.video.Quality HIGHEST;
-    field public static final androidx.camera.video.Quality LOWEST;
-    field public static final androidx.camera.video.Quality SD;
-    field public static final androidx.camera.video.Quality UHD;
-  }
-
-  @RequiresApi(21) public final class QualitySelector {
-    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality);
-    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality, androidx.camera.video.FallbackStrategy);
-    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
-    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
-    method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
-    method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
-    method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
-  }
-
-  @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
-    method public int getAspectRatio();
-    method public java.util.concurrent.Executor? getExecutor();
-    method public androidx.camera.video.QualitySelector getQualitySelector();
-    method public int getTargetVideoEncodingBitRate();
-    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
-    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
-    method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
-    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
-    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.MediaStoreOutputOptions);
-    field public static final androidx.camera.video.QualitySelector DEFAULT_QUALITY_SELECTOR;
-  }
-
-  @RequiresApi(21) public static final class Recorder.Builder {
-    ctor public Recorder.Builder();
-    method public androidx.camera.video.Recorder build();
-    method public androidx.camera.video.Recorder.Builder setAspectRatio(int);
-    method public androidx.camera.video.Recorder.Builder setExecutor(java.util.concurrent.Executor);
-    method public androidx.camera.video.Recorder.Builder setQualitySelector(androidx.camera.video.QualitySelector);
-    method public androidx.camera.video.Recorder.Builder setTargetVideoEncodingBitRate(@IntRange(from=1) int);
-  }
-
-  @RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
-    method public void close();
-    method public void mute(boolean);
-    method public void pause();
-    method public void resume();
-    method public void stop();
-  }
-
-  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class RecordingStats {
-    method public abstract androidx.camera.video.AudioStats getAudioStats();
-    method public abstract long getNumBytesRecorded();
-    method public abstract long getRecordedDurationNanos();
-  }
-
-  @RequiresApi(21) public interface VideoCapabilities {
-    method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
-    method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
-    method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
-  }
-
-  @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
-    method public androidx.camera.core.DynamicRange getDynamicRange();
-    method public int getMirrorMode();
-    method public T getOutput();
-    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
-    method public int getTargetRotation();
-    method public void setTargetRotation(int);
-    method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
-  }
-
-  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
-    ctor public VideoCapture.Builder(T);
-    method public androidx.camera.video.VideoCapture<T!> build();
-    method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
-    method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
-    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
-    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
-  }
-
-  @RequiresApi(21) public interface VideoOutput {
-    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
-  }
-
-  @RequiresApi(21) public abstract class VideoRecordEvent {
-    method public androidx.camera.video.OutputOptions getOutputOptions();
-    method public androidx.camera.video.RecordingStats getRecordingStats();
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Finalize extends androidx.camera.video.VideoRecordEvent {
-    method public Throwable? getCause();
-    method public int getError();
-    method public androidx.camera.video.OutputResults getOutputResults();
-    method public boolean hasError();
-    field public static final int ERROR_DURATION_LIMIT_REACHED = 9; // 0x9
-    field public static final int ERROR_ENCODING_FAILED = 6; // 0x6
-    field public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2; // 0x2
-    field public static final int ERROR_INSUFFICIENT_STORAGE = 3; // 0x3
-    field public static final int ERROR_INVALID_OUTPUT_OPTIONS = 5; // 0x5
-    field public static final int ERROR_NONE = 0; // 0x0
-    field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
-    field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
-    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
-    field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
-    field public static final int ERROR_UNKNOWN = 1; // 0x1
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Pause extends androidx.camera.video.VideoRecordEvent {
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Resume extends androidx.camera.video.VideoRecordEvent {
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Start extends androidx.camera.video.VideoRecordEvent {
-  }
-
-  @RequiresApi(21) public static final class VideoRecordEvent.Status extends androidx.camera.video.VideoRecordEvent {
-  }
-
-}
-
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index 70031eb..19ba003 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -75,6 +75,7 @@
 import org.junit.Assume.assumeFalse
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
@@ -916,6 +917,7 @@
         file2.delete()
     }
 
+    @Ignore("b/285940946")
     @Test
     fun canContinueRecordingAfterRebind() {
         val videoCapture = VideoCapture.withOutput(Recorder.Builder().build())
@@ -960,6 +962,7 @@
         file.delete()
     }
 
+    @Ignore("b/285940946")
     @Test
     fun canContinueRecordingPausedAfterRebind() {
         val videoCapture = VideoCapture.withOutput(Recorder.Builder().build())
diff --git a/camera/camera-view/api/1.3.0-beta01.txt b/camera/camera-view/api/1.3.0-beta01.txt
deleted file mode 100644
index d50aed9..0000000
--- a/camera/camera-view/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,173 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.view {
-
-  @RequiresApi(21) public abstract class CameraController {
-    method @MainThread public void clearEffects();
-    method @MainThread public void clearImageAnalysisAnalyzer();
-    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
-    method @MainThread public androidx.camera.core.CameraControl? getCameraControl();
-    method @MainThread public androidx.camera.core.CameraInfo? getCameraInfo();
-    method @MainThread public androidx.camera.core.CameraSelector getCameraSelector();
-    method @MainThread public java.util.concurrent.Executor? getImageAnalysisBackgroundExecutor();
-    method @MainThread public int getImageAnalysisBackpressureStrategy();
-    method @MainThread public int getImageAnalysisImageQueueDepth();
-    method @MainThread public androidx.camera.view.CameraController.OutputSize? getImageAnalysisTargetSize();
-    method @MainThread public int getImageCaptureFlashMode();
-    method @MainThread public java.util.concurrent.Executor? getImageCaptureIoExecutor();
-    method @MainThread public int getImageCaptureMode();
-    method @MainThread public androidx.camera.view.CameraController.OutputSize? getImageCaptureTargetSize();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> getInitializationFuture();
-    method @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
-    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
-    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
-    method @MainThread public androidx.camera.video.Quality? getVideoCaptureTargetQuality();
-    method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
-    method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
-    method @MainThread public boolean isImageAnalysisEnabled();
-    method @MainThread public boolean isImageCaptureEnabled();
-    method @MainThread public boolean isPinchToZoomEnabled();
-    method @MainThread public boolean isRecording();
-    method @MainThread public boolean isTapToFocusEnabled();
-    method @MainThread public boolean isVideoCaptureEnabled();
-    method @MainThread public void setCameraSelector(androidx.camera.core.CameraSelector);
-    method @MainThread public void setEffects(java.util.Set<androidx.camera.core.CameraEffect!>);
-    method @MainThread public void setEnabledUseCases(int);
-    method @MainThread public void setImageAnalysisAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
-    method @MainThread public void setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor?);
-    method @MainThread public void setImageAnalysisBackpressureStrategy(int);
-    method @MainThread public void setImageAnalysisImageQueueDepth(int);
-    method @MainThread public void setImageAnalysisTargetSize(androidx.camera.view.CameraController.OutputSize?);
-    method @MainThread public void setImageCaptureFlashMode(int);
-    method @MainThread public void setImageCaptureIoExecutor(java.util.concurrent.Executor?);
-    method @MainThread public void setImageCaptureMode(int);
-    method @MainThread public void setImageCaptureTargetSize(androidx.camera.view.CameraController.OutputSize?);
-    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
-    method @MainThread public void setPinchToZoomEnabled(boolean);
-    method @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
-    method @MainThread public void setTapToFocusEnabled(boolean);
-    method @MainThread public void setVideoCaptureTargetQuality(androidx.camera.video.Quality?);
-    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
-    method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
-    field public static final int IMAGE_ANALYSIS = 2; // 0x2
-    field public static final int IMAGE_CAPTURE = 1; // 0x1
-    field public static final int TAP_TO_FOCUS_FAILED = 4; // 0x4
-    field public static final int TAP_TO_FOCUS_FOCUSED = 2; // 0x2
-    field public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3; // 0x3
-    field public static final int TAP_TO_FOCUS_NOT_STARTED = 0; // 0x0
-    field public static final int TAP_TO_FOCUS_STARTED = 1; // 0x1
-    field public static final int VIDEO_CAPTURE = 4; // 0x4
-  }
-
-  @RequiresApi(21) public static final class CameraController.OutputSize {
-    ctor public CameraController.OutputSize(android.util.Size);
-    ctor public CameraController.OutputSize(int);
-    method public int getAspectRatio();
-    method public android.util.Size? getResolution();
-    field public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
-  }
-
-  @RequiresApi(21) public final class LifecycleCameraController extends androidx.camera.view.CameraController {
-    ctor public LifecycleCameraController(android.content.Context);
-    method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
-    method @MainThread public void unbind();
-  }
-
-  @RequiresApi(21) public final class PreviewView extends android.widget.FrameLayout {
-    ctor @UiThread public PreviewView(android.content.Context);
-    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.view.CameraController? getController();
-    method @UiThread public androidx.camera.view.PreviewView.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.core.MeteringPointFactory getMeteringPointFactory();
-    method public androidx.camera.view.transform.OutputTransform? getOutputTransform();
-    method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
-    method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
-    method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
-    method @UiThread public androidx.camera.core.ViewPort? getViewPort();
-    method @UiThread public androidx.camera.core.ViewPort? getViewPort(int);
-    method @UiThread public void setController(androidx.camera.view.CameraController?);
-    method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
-  }
-
-  @RequiresApi(21) public enum PreviewView.ImplementationMode {
-    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum PreviewView.ScaleType {
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_START;
-  }
-
-  public enum PreviewView.StreamState {
-    enum_constant public static final androidx.camera.view.PreviewView.StreamState IDLE;
-    enum_constant public static final androidx.camera.view.PreviewView.StreamState STREAMING;
-  }
-
-  @RequiresApi(21) public final class RotationProvider {
-    ctor public RotationProvider(android.content.Context);
-    method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
-    method public void removeListener(androidx.camera.view.RotationProvider.Listener);
-  }
-
-  public static interface RotationProvider.Listener {
-    method public void onRotationChanged(int);
-  }
-
-}
-
-package androidx.camera.view.transform {
-
-  @RequiresApi(21) public final class CoordinateTransform {
-    ctor public CoordinateTransform(androidx.camera.view.transform.OutputTransform, androidx.camera.view.transform.OutputTransform);
-    method public void mapPoint(android.graphics.PointF);
-    method public void mapPoints(float[]);
-    method public void mapRect(android.graphics.RectF);
-    method public void transform(android.graphics.Matrix);
-  }
-
-  @RequiresApi(21) public final class FileTransformFactory {
-    ctor public FileTransformFactory();
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(android.content.ContentResolver, android.net.Uri) throws java.io.IOException;
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.File) throws java.io.IOException;
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.InputStream) throws java.io.IOException;
-    method public boolean isUsingExifOrientation();
-    method public void setUsingExifOrientation(boolean);
-  }
-
-  @RequiresApi(21) public final class ImageProxyTransformFactory {
-    ctor public ImageProxyTransformFactory();
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(androidx.camera.core.ImageProxy);
-    method public boolean isUsingCropRect();
-    method public boolean isUsingRotationDegrees();
-    method public void setUsingCropRect(boolean);
-    method public void setUsingRotationDegrees(boolean);
-  }
-
-  @RequiresApi(21) public final class OutputTransform {
-  }
-
-}
-
-package androidx.camera.view.video {
-
-  @RequiresApi(21) public class AudioConfig {
-    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
-    method public boolean getAudioEnabled();
-    field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
-  }
-
-}
-
diff --git a/camera/camera-view/api/current.txt b/camera/camera-view/api/current.txt
index d50aed9..ed1e846 100644
--- a/camera/camera-view/api/current.txt
+++ b/camera/camera-view/api/current.txt
@@ -20,7 +20,7 @@
     method @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
     method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
     method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
-    method @MainThread public androidx.camera.video.Quality? getVideoCaptureTargetQuality();
+    method @MainThread public androidx.camera.video.QualitySelector getVideoCaptureQualitySelector();
     method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
     method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
     method @MainThread public boolean isImageAnalysisEnabled();
@@ -45,7 +45,7 @@
     method @MainThread public void setPinchToZoomEnabled(boolean);
     method @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
     method @MainThread public void setTapToFocusEnabled(boolean);
-    method @MainThread public void setVideoCaptureTargetQuality(androidx.camera.video.Quality?);
+    method @MainThread public void setVideoCaptureQualitySelector(androidx.camera.video.QualitySelector);
     method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
     method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
     method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
diff --git a/camera/camera-view/api/res-1.3.0-beta01.txt b/camera/camera-view/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-view/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-view/api/restricted_1.3.0-beta01.txt b/camera/camera-view/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index d50aed9..0000000
--- a/camera/camera-view/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,173 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.view {
-
-  @RequiresApi(21) public abstract class CameraController {
-    method @MainThread public void clearEffects();
-    method @MainThread public void clearImageAnalysisAnalyzer();
-    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
-    method @MainThread public androidx.camera.core.CameraControl? getCameraControl();
-    method @MainThread public androidx.camera.core.CameraInfo? getCameraInfo();
-    method @MainThread public androidx.camera.core.CameraSelector getCameraSelector();
-    method @MainThread public java.util.concurrent.Executor? getImageAnalysisBackgroundExecutor();
-    method @MainThread public int getImageAnalysisBackpressureStrategy();
-    method @MainThread public int getImageAnalysisImageQueueDepth();
-    method @MainThread public androidx.camera.view.CameraController.OutputSize? getImageAnalysisTargetSize();
-    method @MainThread public int getImageCaptureFlashMode();
-    method @MainThread public java.util.concurrent.Executor? getImageCaptureIoExecutor();
-    method @MainThread public int getImageCaptureMode();
-    method @MainThread public androidx.camera.view.CameraController.OutputSize? getImageCaptureTargetSize();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> getInitializationFuture();
-    method @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
-    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
-    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
-    method @MainThread public androidx.camera.video.Quality? getVideoCaptureTargetQuality();
-    method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
-    method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
-    method @MainThread public boolean isImageAnalysisEnabled();
-    method @MainThread public boolean isImageCaptureEnabled();
-    method @MainThread public boolean isPinchToZoomEnabled();
-    method @MainThread public boolean isRecording();
-    method @MainThread public boolean isTapToFocusEnabled();
-    method @MainThread public boolean isVideoCaptureEnabled();
-    method @MainThread public void setCameraSelector(androidx.camera.core.CameraSelector);
-    method @MainThread public void setEffects(java.util.Set<androidx.camera.core.CameraEffect!>);
-    method @MainThread public void setEnabledUseCases(int);
-    method @MainThread public void setImageAnalysisAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
-    method @MainThread public void setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor?);
-    method @MainThread public void setImageAnalysisBackpressureStrategy(int);
-    method @MainThread public void setImageAnalysisImageQueueDepth(int);
-    method @MainThread public void setImageAnalysisTargetSize(androidx.camera.view.CameraController.OutputSize?);
-    method @MainThread public void setImageCaptureFlashMode(int);
-    method @MainThread public void setImageCaptureIoExecutor(java.util.concurrent.Executor?);
-    method @MainThread public void setImageCaptureMode(int);
-    method @MainThread public void setImageCaptureTargetSize(androidx.camera.view.CameraController.OutputSize?);
-    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
-    method @MainThread public void setPinchToZoomEnabled(boolean);
-    method @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
-    method @MainThread public void setTapToFocusEnabled(boolean);
-    method @MainThread public void setVideoCaptureTargetQuality(androidx.camera.video.Quality?);
-    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
-    method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
-    method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
-    method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
-    field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
-    field public static final int IMAGE_ANALYSIS = 2; // 0x2
-    field public static final int IMAGE_CAPTURE = 1; // 0x1
-    field public static final int TAP_TO_FOCUS_FAILED = 4; // 0x4
-    field public static final int TAP_TO_FOCUS_FOCUSED = 2; // 0x2
-    field public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3; // 0x3
-    field public static final int TAP_TO_FOCUS_NOT_STARTED = 0; // 0x0
-    field public static final int TAP_TO_FOCUS_STARTED = 1; // 0x1
-    field public static final int VIDEO_CAPTURE = 4; // 0x4
-  }
-
-  @RequiresApi(21) public static final class CameraController.OutputSize {
-    ctor public CameraController.OutputSize(android.util.Size);
-    ctor public CameraController.OutputSize(int);
-    method public int getAspectRatio();
-    method public android.util.Size? getResolution();
-    field public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
-  }
-
-  @RequiresApi(21) public final class LifecycleCameraController extends androidx.camera.view.CameraController {
-    ctor public LifecycleCameraController(android.content.Context);
-    method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
-    method @MainThread public void unbind();
-  }
-
-  @RequiresApi(21) public final class PreviewView extends android.widget.FrameLayout {
-    ctor @UiThread public PreviewView(android.content.Context);
-    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.view.CameraController? getController();
-    method @UiThread public androidx.camera.view.PreviewView.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.core.MeteringPointFactory getMeteringPointFactory();
-    method public androidx.camera.view.transform.OutputTransform? getOutputTransform();
-    method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
-    method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
-    method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
-    method @UiThread public androidx.camera.core.ViewPort? getViewPort();
-    method @UiThread public androidx.camera.core.ViewPort? getViewPort(int);
-    method @UiThread public void setController(androidx.camera.view.CameraController?);
-    method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
-  }
-
-  @RequiresApi(21) public enum PreviewView.ImplementationMode {
-    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum PreviewView.ScaleType {
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_START;
-  }
-
-  public enum PreviewView.StreamState {
-    enum_constant public static final androidx.camera.view.PreviewView.StreamState IDLE;
-    enum_constant public static final androidx.camera.view.PreviewView.StreamState STREAMING;
-  }
-
-  @RequiresApi(21) public final class RotationProvider {
-    ctor public RotationProvider(android.content.Context);
-    method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
-    method public void removeListener(androidx.camera.view.RotationProvider.Listener);
-  }
-
-  public static interface RotationProvider.Listener {
-    method public void onRotationChanged(int);
-  }
-
-}
-
-package androidx.camera.view.transform {
-
-  @RequiresApi(21) public final class CoordinateTransform {
-    ctor public CoordinateTransform(androidx.camera.view.transform.OutputTransform, androidx.camera.view.transform.OutputTransform);
-    method public void mapPoint(android.graphics.PointF);
-    method public void mapPoints(float[]);
-    method public void mapRect(android.graphics.RectF);
-    method public void transform(android.graphics.Matrix);
-  }
-
-  @RequiresApi(21) public final class FileTransformFactory {
-    ctor public FileTransformFactory();
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(android.content.ContentResolver, android.net.Uri) throws java.io.IOException;
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.File) throws java.io.IOException;
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.InputStream) throws java.io.IOException;
-    method public boolean isUsingExifOrientation();
-    method public void setUsingExifOrientation(boolean);
-  }
-
-  @RequiresApi(21) public final class ImageProxyTransformFactory {
-    ctor public ImageProxyTransformFactory();
-    method public androidx.camera.view.transform.OutputTransform getOutputTransform(androidx.camera.core.ImageProxy);
-    method public boolean isUsingCropRect();
-    method public boolean isUsingRotationDegrees();
-    method public void setUsingCropRect(boolean);
-    method public void setUsingRotationDegrees(boolean);
-  }
-
-  @RequiresApi(21) public final class OutputTransform {
-  }
-
-}
-
-package androidx.camera.view.video {
-
-  @RequiresApi(21) public class AudioConfig {
-    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
-    method public boolean getAudioEnabled();
-    field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
-  }
-
-}
-
diff --git a/camera/camera-view/api/restricted_current.txt b/camera/camera-view/api/restricted_current.txt
index d50aed9..ed1e846 100644
--- a/camera/camera-view/api/restricted_current.txt
+++ b/camera/camera-view/api/restricted_current.txt
@@ -20,7 +20,7 @@
     method @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
     method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
     method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
-    method @MainThread public androidx.camera.video.Quality? getVideoCaptureTargetQuality();
+    method @MainThread public androidx.camera.video.QualitySelector getVideoCaptureQualitySelector();
     method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
     method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
     method @MainThread public boolean isImageAnalysisEnabled();
@@ -45,7 +45,7 @@
     method @MainThread public void setPinchToZoomEnabled(boolean);
     method @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
     method @MainThread public void setTapToFocusEnabled(boolean);
-    method @MainThread public void setVideoCaptureTargetQuality(androidx.camera.video.Quality?);
+    method @MainThread public void setVideoCaptureQualitySelector(androidx.camera.video.QualitySelector);
     method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
     method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
     method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
index ea5eb7d..fc7bc7d 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
@@ -35,11 +35,14 @@
 import androidx.camera.testing.CoreAppTestUtil.ForegroundOccupiedError
 import androidx.camera.testing.fakes.FakeActivity
 import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.camera.video.FallbackStrategy
 import androidx.camera.video.FileDescriptorOutputOptions
 import androidx.camera.video.FileOutputOptions
 import androidx.camera.video.MediaStoreOutputOptions
 import androidx.camera.video.OutputOptions
 import androidx.camera.video.Quality
+import androidx.camera.video.QualitySelector
+import androidx.camera.video.Recorder
 import androidx.camera.video.Recording
 import androidx.camera.video.VideoRecordEvent
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
@@ -83,17 +86,25 @@
      * in Parameterized tests, ref: b/37086576
      */
     enum class TargetQuality {
-        None, FHD, HD, HIGHEST, LOWEST, SD, UHD;
+        NOT_SPECIFIED, FHD, HD, HIGHEST, LOWEST, SD, UHD;
 
-        fun get(): Quality? {
+        fun getSelector(): QualitySelector {
             return when (this) {
-                None -> null
-                FHD -> Quality.FHD
-                HD -> Quality.HD
-                HIGHEST -> Quality.HIGHEST
-                LOWEST -> Quality.LOWEST
-                SD -> Quality.SD
-                UHD -> Quality.UHD
+                NOT_SPECIFIED -> toQualitySelector(null)
+                FHD -> toQualitySelector(Quality.FHD)
+                HD -> toQualitySelector(Quality.HD)
+                HIGHEST -> toQualitySelector(Quality.HIGHEST)
+                LOWEST -> toQualitySelector(Quality.LOWEST)
+                SD -> toQualitySelector(Quality.SD)
+                UHD -> toQualitySelector(Quality.UHD)
+            }
+        }
+
+        private fun toQualitySelector(quality: Quality?): QualitySelector {
+            return if (quality == null) {
+                Recorder.DEFAULT_QUALITY_SELECTOR
+            } else {
+                QualitySelector.from(quality, FallbackStrategy.lowerQualityOrHigherThan(quality))
             }
         }
     }
@@ -115,13 +126,13 @@
         @JvmStatic
         @Parameterized.Parameters(name = "initialQuality={0}, nextQuality={1}")
         fun data() = mutableListOf<Array<TargetQuality>>().apply {
-            add(arrayOf(TargetQuality.None, TargetQuality.FHD))
+            add(arrayOf(TargetQuality.NOT_SPECIFIED, TargetQuality.FHD))
             add(arrayOf(TargetQuality.FHD, TargetQuality.HD))
             add(arrayOf(TargetQuality.HD, TargetQuality.HIGHEST))
             add(arrayOf(TargetQuality.HIGHEST, TargetQuality.LOWEST))
             add(arrayOf(TargetQuality.LOWEST, TargetQuality.SD))
             add(arrayOf(TargetQuality.SD, TargetQuality.UHD))
-            add(arrayOf(TargetQuality.UHD, TargetQuality.None))
+            add(arrayOf(TargetQuality.UHD, TargetQuality.NOT_SPECIFIED))
         }
     }
 
@@ -315,7 +326,7 @@
         // Act.
         recordVideoWithInterruptAction(outputOptions, audioEnabled) {
             instrumentation.runOnMainSync {
-                cameraController.videoCaptureTargetQuality = nextQuality.get()
+                cameraController.videoCaptureQualitySelector = nextQuality.getSelector()
             }
         }
 
@@ -519,8 +530,8 @@
         cameraController = LifecycleCameraController(context)
         cameraController.initializationFuture.get()
         instrumentation.runOnMainSync {
-            if (initialQuality != TargetQuality.None) {
-                cameraController.videoCaptureTargetQuality = initialQuality.get()
+            if (initialQuality != TargetQuality.NOT_SPECIFIED) {
+                cameraController.videoCaptureQualitySelector = initialQuality.getSelector()
             }
 
             //  If the PreviewView is not attached, the enabled use cases will not be applied.
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index 27fb542..9c2f968 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -70,13 +70,11 @@
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.camera.video.FallbackStrategy;
 import androidx.camera.video.FileDescriptorOutputOptions;
 import androidx.camera.video.FileOutputOptions;
 import androidx.camera.video.MediaStoreOutputOptions;
 import androidx.camera.video.OutputOptions;
 import androidx.camera.video.PendingRecording;
-import androidx.camera.video.Quality;
 import androidx.camera.video.QualitySelector;
 import androidx.camera.video.Recorder;
 import androidx.camera.video.Recording;
@@ -263,8 +261,8 @@
     @NonNull
     Map<Consumer<VideoRecordEvent>, Recording> mRecordingMap = new HashMap<>();
 
-    @Nullable
-    Quality mVideoCaptureQuality;
+    @NonNull
+    QualitySelector mVideoCaptureQualitySelector = Recorder.DEFAULT_QUALITY_SELECTOR;
 
     // The latest bound camera.
     // Synthetic access
@@ -354,16 +352,8 @@
         };
     }
 
-    private static Recorder generateVideoCaptureRecorder(Quality videoQuality) {
-        Recorder.Builder builder = new Recorder.Builder();
-        if (videoQuality != null) {
-            builder.setQualitySelector(QualitySelector.from(
-                    videoQuality,
-                    FallbackStrategy.lowerQualityOrHigherThan(videoQuality)
-            ));
-        }
-
-        return builder.build();
+    private static Recorder generateVideoCaptureRecorder(@NonNull QualitySelector qualitySelector) {
+        return new Recorder.Builder().setQualitySelector(qualitySelector).build();
     }
 
     /**
@@ -1391,42 +1381,40 @@
     }
 
     /**
-     * Sets the intended video quality for {@code VideoCapture}.
+     * Sets the {@link QualitySelector} for {@link #VIDEO_CAPTURE}.
      *
-     * <p> The value is used as a hint when determining the resolution of the video.
-     * The actual output may differ from the requested value due to device constraints.
-     * The {@link FallbackStrategy#lowerQualityOrHigherThan(Quality)} fallback strategy
-     * will be applied when the quality is not supported.
+     * <p>The provided quality selector is used to select the resolution of the recording
+     * depending on the resolutions supported by the camera and codec capabilities.
      *
-     * <p> When set to null, the output will be based on the default config of {@link
-     * Recorder#DEFAULT_QUALITY_SELECTOR}.
+     * <p>If no quality selector is provided, the default is
+     * {@link Recorder#DEFAULT_QUALITY_SELECTOR}.
      *
-     * <p> Changing the value will reconfigure the camera which will cause video
-     * capture to stop. To avoid this, set the value before controller is bound to
-     * lifecycle.
+     * <p>Changing the value will reconfigure the camera which will cause video capture to stop.
+     * To avoid this, set the value before controller is bound to lifecycle.
      *
-     * @param targetQuality the intended video quality for {@code VideoCapture}.
+     * @param qualitySelector the quality selector for {@link #VIDEO_CAPTURE}.
+     * @see QualitySelector
      */
     @MainThread
-    public void setVideoCaptureTargetQuality(@Nullable Quality targetQuality) {
+    public void setVideoCaptureQualitySelector(@NonNull QualitySelector qualitySelector) {
         checkMainThread();
-        if (targetQuality == mVideoCaptureQuality) {
-            return;
-        }
-        mVideoCaptureQuality = targetQuality;
+        mVideoCaptureQualitySelector = qualitySelector;
         unbindVideoAndRecreate();
         startCameraAndTrackStates();
     }
 
     /**
-     * Returns the intended quality for {@code VideoCapture} set by
-     * {@link #setVideoCaptureTargetQuality(Quality)}, or null if not set.
+     * Returns the {@link QualitySelector} for {@link #VIDEO_CAPTURE}.
+     *
+     * @return the {@link QualitySelector} provided to
+     * {@link #setVideoCaptureQualitySelector(QualitySelector)} or the default value of
+     * {@link Recorder#DEFAULT_QUALITY_SELECTOR} if no quality selector was provided.
      */
     @MainThread
-    @Nullable
-    public Quality getVideoCaptureTargetQuality() {
+    @NonNull
+    public QualitySelector getVideoCaptureQualitySelector() {
         checkMainThread();
-        return mVideoCaptureQuality;
+        return mVideoCaptureQualitySelector;
     }
 
     /**
@@ -1440,7 +1428,7 @@
     }
 
     private VideoCapture<Recorder> createNewVideoCapture() {
-        return VideoCapture.withOutput(generateVideoCaptureRecorder(mVideoCaptureQuality));
+        return VideoCapture.withOutput(generateVideoCaptureRecorder(mVideoCaptureQualitySelector));
     }
 
     // -----------------
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt
index 96d107c..22a51d3 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/CameraControllerTest.kt
@@ -42,6 +42,7 @@
 import androidx.camera.testing.fakes.FakeSurfaceEffect
 import androidx.camera.testing.fakes.FakeSurfaceProcessor
 import androidx.camera.video.Quality
+import androidx.camera.video.QualitySelector
 import androidx.camera.view.CameraController.COORDINATE_SYSTEM_VIEW_REFERENCED
 import androidx.camera.view.transform.OutputTransform
 import androidx.concurrent.futures.CallbackToFutureAdapter
@@ -384,8 +385,9 @@
     @UiThreadTest
     @Test
     fun setVideoCaptureQuality() {
-        controller.videoCaptureTargetQuality = targetVideoQuality
-        assertThat(controller.videoCaptureTargetQuality).isEqualTo(targetVideoQuality)
+        val qualitySelector = QualitySelector.from(targetVideoQuality)
+        controller.videoCaptureQualitySelector = qualitySelector
+        assertThat(controller.videoCaptureQualitySelector).isEqualTo(qualitySelector)
     }
 
     @UiThreadTest
diff --git a/camera/camera-viewfinder-compose/api/1.3.0-beta01.txt b/camera/camera-viewfinder-compose/api/1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder-compose/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder-compose/api/res-1.3.0-beta01.txt b/camera/camera-viewfinder-compose/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-viewfinder-compose/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-viewfinder-compose/api/restricted_1.3.0-beta01.txt b/camera/camera-viewfinder-compose/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder-compose/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder-core/api/1.3.0-beta01.txt b/camera/camera-viewfinder-core/api/1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder-core/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder-core/api/res-1.3.0-beta01.txt b/camera/camera-viewfinder-core/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-viewfinder-core/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-viewfinder-core/api/restricted_1.3.0-beta01.txt b/camera/camera-viewfinder-core/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder-core/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder/api/1.3.0-beta01.txt b/camera/camera-viewfinder/api/1.3.0-beta01.txt
deleted file mode 100644
index a7333d7..0000000
--- a/camera/camera-viewfinder/api/1.3.0-beta01.txt
+++ /dev/null
@@ -1,58 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public final class CameraViewfinderExt {
-    method public suspend Object? requestSurface(androidx.camera.viewfinder.CameraViewfinder, androidx.camera.viewfinder.ViewfinderSurfaceRequest viewfinderSurfaceRequest, kotlin.coroutines.Continuation<? super android.view.Surface>);
-    field public static final androidx.camera.viewfinder.CameraViewfinderExt INSTANCE;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    method public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode? getImplementationMode();
-    method public int getLensFacing();
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public void markSurfaceSafeToRelease();
-  }
-
-  public static final class ViewfinderSurfaceRequest.Builder {
-    ctor public ViewfinderSurfaceRequest.Builder(android.util.Size);
-    ctor public ViewfinderSurfaceRequest.Builder(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    ctor public ViewfinderSurfaceRequest.Builder(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder);
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest build();
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode?);
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setLensFacing(int);
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setSensorOrientation(int);
-  }
-
-  public final class ViewfinderSurfaceRequestUtil {
-    method @RequiresApi(21) public static androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder populateFromCharacteristics(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder, android.hardware.camera2.CameraCharacteristics cameraCharacteristics);
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/res-1.3.0-beta01.txt b/camera/camera-viewfinder/api/res-1.3.0-beta01.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-viewfinder/api/res-1.3.0-beta01.txt
+++ /dev/null
diff --git a/camera/camera-viewfinder/api/restricted_1.3.0-beta01.txt b/camera/camera-viewfinder/api/restricted_1.3.0-beta01.txt
deleted file mode 100644
index a7333d7..0000000
--- a/camera/camera-viewfinder/api/restricted_1.3.0-beta01.txt
+++ /dev/null
@@ -1,58 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public final class CameraViewfinderExt {
-    method public suspend Object? requestSurface(androidx.camera.viewfinder.CameraViewfinder, androidx.camera.viewfinder.ViewfinderSurfaceRequest viewfinderSurfaceRequest, kotlin.coroutines.Continuation<? super android.view.Surface>);
-    field public static final androidx.camera.viewfinder.CameraViewfinderExt INSTANCE;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    method public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode? getImplementationMode();
-    method public int getLensFacing();
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public void markSurfaceSafeToRelease();
-  }
-
-  public static final class ViewfinderSurfaceRequest.Builder {
-    ctor public ViewfinderSurfaceRequest.Builder(android.util.Size);
-    ctor public ViewfinderSurfaceRequest.Builder(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    ctor public ViewfinderSurfaceRequest.Builder(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder);
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest build();
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode?);
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setLensFacing(int);
-    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setSensorOrientation(int);
-  }
-
-  public final class ViewfinderSurfaceRequestUtil {
-    method @RequiresApi(21) public static androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder populateFromCharacteristics(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder, android.hardware.camera2.CameraCharacteristics cameraCharacteristics);
-  }
-
-}
-
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
index 2ccd6f3..b2e52f4 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 import android.view.PixelCopy;
 import android.view.View;
+import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.ImageView;
@@ -62,7 +63,6 @@
 import androidx.car.app.serialization.BundlerException;
 import androidx.car.app.utils.ThreadUtils;
 import androidx.core.view.DisplayCutoutCompat;
-import androidx.core.view.WindowCompat;
 import androidx.core.view.WindowInsetsCompat;
 import androidx.fragment.app.FragmentActivity;
 import androidx.lifecycle.Lifecycle;
@@ -251,6 +251,11 @@
             return new WindowInsets.Builder(insets).setInsets(
                     WindowInsets.Type.displayCutout(), Insets.NONE).build();
         }
+
+        @DoNotInline
+        static void setDecorFitsSystemWindows(Window window, boolean decorFitsSystemWindows) {
+            window.setDecorFitsSystemWindows(decorFitsSystemWindows);
+        }
     }
 
     @Override
@@ -331,11 +336,11 @@
             return view.onApplyWindowInsets(insets);
         });
 
-        // IMPORTANT: The SystemUiVisibility applied here must match the insets provided to the
-        // host in OnApplyWindowInsetsListener above. Failing to do so would cause a mismatch
-        // between the insets applied to the content on the hosts side vs. the actual visible
-        // window available on the client side.
-        WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            Api30Impl.setDecorFitsSystemWindows(getWindow(), false);
+        } else {
+            getWindow().getDecorView().setFitsSystemWindows(false);
+        }
         mActivityContainerView.requestApplyInsets();
     }
 
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index 02804af..1d56b43 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -42,6 +42,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
@@ -74,9 +75,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 import org.robolectric.annotation.internal.DoNotInstrument;
 import org.robolectric.shadows.ShadowApplication;
 import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.shadows.ShadowPhoneWindow;
 
 /** Tests for {@link CarAppActivity}. */
 @RunWith(RobolectricTestRunner.class)
@@ -396,6 +399,41 @@
 
     }
 
+    @Test
+    @Config(maxSdk = Build.VERSION_CODES.Q)
+    public void testFitsSystemWindows_whenQAndBelow_shouldSetFitsSystemWindowsToFalse() {
+        Intent newIntent = new Intent(getApplicationContext(), CarAppActivity.class);
+        try (ActivityScenario<CarAppActivity> scenario = ActivityScenario.launch(newIntent)) {
+            scenario.onActivity(activity -> {
+                try {
+                    assertThat(
+                            activity.getWindow().getDecorView().getFitsSystemWindows()).isFalse();
+                } catch (Exception e) {
+                    fail(Log.getStackTraceString(e));
+                }
+            });
+
+        }
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R)
+    public void testDecorFitsSystemWindows_whenRAndAbove_shouldSetDecorFitsSystemWindowsToFalse() {
+        Intent newIntent = new Intent(getApplicationContext(), CarAppActivity.class);
+        try (ActivityScenario<CarAppActivity> scenario = ActivityScenario.launch(newIntent)) {
+            scenario.onActivity(activity -> {
+                try {
+                    ShadowPhoneWindow shadowPhoneWindow = (ShadowPhoneWindow) shadowOf(
+                            activity.getWindow());
+                    assertThat(shadowPhoneWindow.getDecorFitsSystemWindows()).isFalse();
+                } catch (Exception e) {
+                    fail(Log.getStackTraceString(e));
+                }
+            });
+
+        }
+    }
+
     interface CarActivityAction {
         void accept(ActivityScenario<CarAppActivity> scenario, CarAppActivity activity)
                 throws Exception;
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
index d4b84a8..b957ba4 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Intrinsic.kt
@@ -20,10 +20,13 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
-import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.constrain
@@ -43,10 +46,14 @@
  * @sample androidx.compose.foundation.layout.samples.SameWidthTextBoxes
  */
 @Stable
-fun Modifier.width(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {
-    IntrinsicSize.Min -> this.then(MinIntrinsicWidthModifier)
-    IntrinsicSize.Max -> this.then(MaxIntrinsicWidthModifier)
-}
+fun Modifier.width(intrinsicSize: IntrinsicSize) = this then IntrinsicWidthElement(
+    width = intrinsicSize,
+    enforceIncoming = true,
+    inspectorInfo = debugInspectorInfo {
+        name = "width"
+        properties["intrinsicSize"] = intrinsicSize
+    }
+)
 
 /**
  * Declare the preferred height of the content to be the same as the min or max intrinsic height of
@@ -63,10 +70,14 @@
  * @sample androidx.compose.foundation.layout.samples.MatchParentDividerForAspectRatio
  */
 @Stable
-fun Modifier.height(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {
-    IntrinsicSize.Min -> this.then(MinIntrinsicHeightModifier)
-    IntrinsicSize.Max -> this.then(MaxIntrinsicHeightModifier)
-}
+fun Modifier.height(intrinsicSize: IntrinsicSize) = this then IntrinsicHeightElement(
+    height = intrinsicSize,
+    enforceIncoming = true,
+    inspectorInfo = debugInspectorInfo {
+        name = "height"
+        properties["intrinsicSize"] = intrinsicSize
+    }
+)
 
 /**
  * Declare the width of the content to be exactly the same as the min or max intrinsic width of
@@ -81,10 +92,14 @@
  * See [requiredWidth] and [requiredWidthIn] for other options to set the required width.
  */
 @Stable
-fun Modifier.requiredWidth(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {
-    IntrinsicSize.Min -> this.then(RequiredMinIntrinsicWidthModifier)
-    IntrinsicSize.Max -> this.then(RequiredMaxIntrinsicWidthModifier)
-}
+fun Modifier.requiredWidth(intrinsicSize: IntrinsicSize) = this then IntrinsicWidthElement(
+    width = intrinsicSize,
+    enforceIncoming = false,
+    inspectorInfo = debugInspectorInfo {
+        name = "requiredWidth"
+        properties["intrinsicSize"] = intrinsicSize
+    }
+)
 
 /**
  * Declare the height of the content to be exactly the same as the min or max intrinsic height of
@@ -99,153 +114,140 @@
  * See [requiredHeight] and [requiredHeightIn] for other options to set the required height.
  */
 @Stable
-fun Modifier.requiredHeight(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {
-    IntrinsicSize.Min -> this.then(RequiredMinIntrinsicHeightModifier)
-    IntrinsicSize.Max -> this.then(RequiredMaxIntrinsicHeightModifier)
-}
+fun Modifier.requiredHeight(intrinsicSize: IntrinsicSize) = this then IntrinsicHeightElement(
+    height = intrinsicSize,
+    enforceIncoming = false,
+    inspectorInfo = debugInspectorInfo {
+        name = "requiredHeight"
+        properties["intrinsicSize"] = intrinsicSize
+    }
+)
 
 /**
  * Intrinsic size used in [width] or [height] which can refer to width or height.
  */
 enum class IntrinsicSize { Min, Max }
 
-private object MinIntrinsicWidthModifier : IntrinsicSizeModifier {
-    override fun MeasureScope.calculateContentConstraints(
-        measurable: Measurable,
-        constraints: Constraints
-    ): Constraints {
-        val width = measurable.minIntrinsicWidth(constraints.maxHeight)
-        return Constraints.fixedWidth(width)
+private class IntrinsicWidthElement(
+    val width: IntrinsicSize,
+    val enforceIncoming: Boolean,
+    val inspectorInfo: InspectorInfo.() -> Unit
+) : ModifierNodeElement<IntrinsicWidthNode>() {
+    override fun create() = IntrinsicWidthNode(width, enforceIncoming)
+
+    override fun update(node: IntrinsicWidthNode) {
+        node.width = width
+        node.enforceIncoming = enforceIncoming
     }
 
-    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ) = measurable.minIntrinsicWidth(height)
-}
-
-private object MinIntrinsicHeightModifier : IntrinsicSizeModifier {
-    override fun MeasureScope.calculateContentConstraints(
-        measurable: Measurable,
-        constraints: Constraints
-    ): Constraints {
-        val height = measurable.minIntrinsicHeight(constraints.maxWidth)
-        return Constraints.fixedHeight(height)
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        val otherModifierElement = other as? IntrinsicWidthElement ?: return false
+        return width == otherModifierElement.width &&
+            enforceIncoming == otherModifierElement.enforceIncoming
     }
 
-    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.minIntrinsicHeight(width)
+    override fun hashCode() = 31 * width.hashCode() + enforceIncoming.hashCode()
+
+    override fun InspectorInfo.inspectableProperties() {
+        inspectorInfo()
+    }
 }
 
-private object MaxIntrinsicWidthModifier : IntrinsicSizeModifier {
+private class IntrinsicWidthNode(
+    var width: IntrinsicSize,
+    override var enforceIncoming: Boolean
+) : IntrinsicSizeModifier() {
     override fun MeasureScope.calculateContentConstraints(
         measurable: Measurable,
         constraints: Constraints
     ): Constraints {
-        val width = measurable.maxIntrinsicWidth(constraints.maxHeight)
-        return Constraints.fixedWidth(width)
+        val measuredWidth = if (width == IntrinsicSize.Min) {
+            measurable.minIntrinsicWidth(constraints.maxHeight)
+        } else {
+            measurable.maxIntrinsicWidth(constraints.maxHeight)
+        }
+        return Constraints.fixedWidth(measuredWidth)
     }
 
     override fun IntrinsicMeasureScope.minIntrinsicWidth(
         measurable: IntrinsicMeasurable,
         height: Int
-    ) = measurable.maxIntrinsicWidth(height)
-}
-
-private object MaxIntrinsicHeightModifier : IntrinsicSizeModifier {
-    override fun MeasureScope.calculateContentConstraints(
-        measurable: Measurable,
-        constraints: Constraints
-    ): Constraints {
-        val height = measurable.maxIntrinsicHeight(constraints.maxWidth)
-        return Constraints.fixedHeight(height)
-    }
-
-    override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.maxIntrinsicHeight(width)
-}
-
-private object RequiredMinIntrinsicWidthModifier : IntrinsicSizeModifier {
-    override val enforceIncoming: Boolean = false
-
-    override fun MeasureScope.calculateContentConstraints(
-        measurable: Measurable,
-        constraints: Constraints
-    ): Constraints {
-        val width = measurable.minIntrinsicWidth(constraints.maxHeight)
-        return Constraints.fixedWidth(width)
-    }
+    ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else
+        measurable.maxIntrinsicWidth(height)
 
     override fun IntrinsicMeasureScope.maxIntrinsicWidth(
         measurable: IntrinsicMeasurable,
         height: Int
-    ) = measurable.minIntrinsicWidth(height)
+    ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else
+        measurable.maxIntrinsicWidth(height)
 }
 
-private object RequiredMinIntrinsicHeightModifier : IntrinsicSizeModifier {
-    override val enforceIncoming: Boolean = false
+private class IntrinsicHeightElement(
+    val height: IntrinsicSize,
+    val enforceIncoming: Boolean,
+    val inspectorInfo: InspectorInfo.() -> Unit
+) : ModifierNodeElement<IntrinsicHeightNode>() {
+    override fun create() = IntrinsicHeightNode(height, enforceIncoming)
 
-    override fun MeasureScope.calculateContentConstraints(
-        measurable: Measurable,
-        constraints: Constraints
-    ): Constraints {
-        val height = measurable.minIntrinsicHeight(constraints.maxWidth)
-        return Constraints.fixedHeight(height)
+    override fun update(node: IntrinsicHeightNode) {
+        node.height = height
+        node.enforceIncoming = enforceIncoming
     }
 
-    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.minIntrinsicHeight(width)
-}
-
-private object RequiredMaxIntrinsicWidthModifier : IntrinsicSizeModifier {
-    override val enforceIncoming: Boolean = false
-
-    override fun MeasureScope.calculateContentConstraints(
-        measurable: Measurable,
-        constraints: Constraints
-    ): Constraints {
-        val width = measurable.maxIntrinsicWidth(constraints.maxHeight)
-        return Constraints.fixedWidth(width)
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        val otherModifierElement = other as? IntrinsicHeightElement ?: return false
+        return height == otherModifierElement.height &&
+            enforceIncoming == otherModifierElement.enforceIncoming
     }
 
-    override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ) = measurable.maxIntrinsicWidth(height)
+    override fun hashCode() = 31 * height.hashCode() + enforceIncoming.hashCode()
+
+    override fun InspectorInfo.inspectableProperties() {
+        inspectorInfo()
+    }
 }
 
-private object RequiredMaxIntrinsicHeightModifier : IntrinsicSizeModifier {
-    override val enforceIncoming: Boolean = false
-
+private class IntrinsicHeightNode(
+    var height: IntrinsicSize,
+    override var enforceIncoming: Boolean
+) : IntrinsicSizeModifier() {
     override fun MeasureScope.calculateContentConstraints(
         measurable: Measurable,
         constraints: Constraints
     ): Constraints {
-        val height = measurable.maxIntrinsicHeight(constraints.maxWidth)
-        return Constraints.fixedHeight(height)
+        val measuredHeight = if (height == IntrinsicSize.Min) {
+            measurable.minIntrinsicHeight(constraints.maxWidth)
+        } else {
+            measurable.maxIntrinsicHeight(constraints.maxWidth)
+        }
+        return Constraints.fixedHeight(measuredHeight)
     }
 
     override fun IntrinsicMeasureScope.minIntrinsicHeight(
         measurable: IntrinsicMeasurable,
         width: Int
-    ) = measurable.maxIntrinsicHeight(width)
+    ) = if (height == IntrinsicSize.Min) measurable.minIntrinsicHeight(width) else
+        measurable.maxIntrinsicHeight(width)
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ) = if (height == IntrinsicSize.Min) measurable.minIntrinsicHeight(width) else
+        measurable.maxIntrinsicHeight(width)
 }
 
-private interface IntrinsicSizeModifier : LayoutModifier {
-    val enforceIncoming: Boolean get() = true
+private abstract class IntrinsicSizeModifier : LayoutModifierNode, Modifier.Node() {
 
-    fun MeasureScope.calculateContentConstraints(
+    abstract val enforceIncoming: Boolean
+
+    abstract fun MeasureScope.calculateContentConstraints(
         measurable: Measurable,
         constraints: Constraints
     ): Constraints
 
-    override fun MeasureScope.measure(
+    final override fun MeasureScope.measure(
         measurable: Measurable,
         constraints: Constraints
     ): MeasureResult {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextPreparedSelectionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextPreparedSelectionTest.kt
index 4640c7b..78956dc 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextPreparedSelectionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/TextPreparedSelectionTest.kt
@@ -109,10 +109,64 @@
     }
 
     @Test
+    fun textSelection_byParagraphMovements_nonEmptyLines() {
+        selectionTest("line1\nline2\nline3") {
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(5))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(11))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(17))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(17))
+
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(12))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(6))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(0))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(0))
+        }
+    }
+
+    @Test
     fun textSelection_byParagraphMovements_empty() {
         selectionTest("") {
             it.moveCursorNextByParagraph()
             expectedSelection(TextRange(0))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(0))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(0))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(0))
+        }
+    }
+
+    @Test
+    fun textSelection_byParagraphMovements_emptyLines() {
+        selectionTest("\n\n\n\n") {
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(1))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(2))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(3))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(4))
+            it.moveCursorNextByParagraph()
+            expectedSelection(TextRange(4))
+
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(3))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(2))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(1))
+            it.moveCursorPrevByParagraph()
+            expectedSelection(TextRange(0))
             it.moveCursorPrevByParagraph()
             expectedSelection(TextRange(0))
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
index c76de6b3..8bfcfdb 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
@@ -79,7 +79,10 @@
         var size: Size? = null
 
         rule.setContent {
-            Box(Modifier.fillMaxSize().drawBehind { drawRect(Color.Black) }) {
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .drawBehind { drawRect(Color.Black) }) {
                 Canvas(Modifier.size(canvasSize)) {
                     size = this.size
                     subject.draw(this)
@@ -110,7 +113,10 @@
         var size: Size? = null
 
         rule.setContent {
-            Box(Modifier.fillMaxSize().drawBehind { drawRect(Color.Black) }) {
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .drawBehind { drawRect(Color.Black) }) {
                 Canvas(Modifier.size(canvasSize)) {
                     size = this.size
                     drawRect(Color.Black)
@@ -179,12 +185,13 @@
     override fun notifySelectionUpdateStart(
         layoutCoordinates: LayoutCoordinates,
         startPosition: Offset,
-        adjustment: SelectionAdjustment
+        adjustment: SelectionAdjustment,
+        isInTouchMode: Boolean
     ) {
         FAKE("Selection not editable")
     }
 
-    override fun notifySelectionUpdateSelectAll(selectableId: Long) {
+    override fun notifySelectionUpdateSelectAll(selectableId: Long, isInTouchMode: Boolean) {
         FAKE()
     }
 
@@ -193,7 +200,8 @@
         newPosition: Offset,
         previousPosition: Offset,
         isStartHandle: Boolean,
-        adjustment: SelectionAdjustment
+        adjustment: SelectionAdjustment,
+        isInTouchMode: Boolean
     ): Boolean {
         FAKE("Selection not editable")
     }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegateTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegateTest.kt
index 54ed1c3..f109ae9 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegateTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegateTest.kt
@@ -68,6 +68,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = 2,
             rawEndOffset = 2,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Word
@@ -94,6 +95,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = 5,
             rawEndOffset = 5,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Word
@@ -123,6 +125,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = rawStartOffset,
             rawEndOffset = rawEndOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Word
@@ -152,6 +155,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = rawStartOffset,
             rawEndOffset = rawEndOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Word
@@ -182,6 +186,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = startOffset,
             rawEndOffset = endOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.None
@@ -212,6 +217,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = startOffset,
             rawEndOffset = endOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Character
@@ -244,6 +250,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = startOffset,
             rawEndOffset = endOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Character
@@ -274,6 +281,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = startOffset,
             rawEndOffset = endOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Character
@@ -304,6 +312,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = startOffset,
             rawEndOffset = endOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Character
@@ -336,6 +345,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = startOffset,
             rawEndOffset = endOffset,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Character,
@@ -362,6 +372,7 @@
             textLayoutResult = textLayoutResult,
             rawStartOffset = 0,
             rawEndOffset = 0,
+            previousHandleOffset = -1,
             previousSelection = null,
             isStartHandle = true,
             adjustment = SelectionAdjustment.Word
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt
new file mode 100644
index 0000000..83854f82
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/AbstractSelectionGesturesTest.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.selection.gestures.util.FakeHapticFeedback
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalHapticFeedback
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.MouseInjectionScope
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.getBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipe
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.lerp
+import kotlin.math.max
+import kotlin.math.roundToInt
+import org.junit.Before
+import org.junit.Rule
+
+@OptIn(ExperimentalTestApi::class)
+internal abstract class AbstractSelectionGesturesTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    protected abstract val pointerAreaTag: String
+
+    protected val hapticFeedback = FakeHapticFeedback()
+    protected val fontFamily = TEST_FONT_FAMILY
+    // small enough to fit in narrow screen in pre-submit,
+    // big enough that pointer movement can target a single char on center
+    protected val fontSize = 15.sp
+    protected val density = Density(1f)
+
+    protected lateinit var textToolbar: TextToolbar
+
+    @Composable
+    abstract fun Content()
+
+    @Before
+    fun setup() {
+        rule.setContent {
+            textToolbar = LocalTextToolbar.current
+            CompositionLocalProvider(
+                LocalDensity provides density,
+                LocalViewConfiguration provides TestViewConfiguration(
+                    minimumTouchTargetSize = DpSize.Zero
+                ),
+                LocalHapticFeedback provides hapticFeedback,
+            ) {
+                Content()
+            }
+        }
+    }
+
+    protected val boundsInRoot
+        get() = with(density) {
+            rule.onNodeWithTag(pointerAreaTag).getBoundsInRoot().toRect()
+        }
+
+    // TODO(b/281584353) When touch mode can be changed globally,
+    //  this should change to a single tap outside of the bounds.
+    internal fun TouchInjectionScope.enterTouchMode() {
+        swipe(
+            start = boundsInRoot.center,
+            end = boundsInRoot.bottomCenter + Offset(0f, 10f)
+        )
+    }
+
+    // TODO(b/281584353) When touch mode can be changed globally,
+    //  this should change to a mouse movement outside of the bounds.
+    internal fun enterMouseMode() {
+        mouseDragTo(boundsInRoot.centerLeft, durationMillis = 50)
+        mouseDragTo(boundsInRoot.bottomRight, durationMillis = 50)
+        mouseDragTo(boundsInRoot.center, durationMillis = 50)
+    }
+
+    protected fun performTouchGesture(block: TouchInjectionScope.() -> Unit) {
+        rule.onNodeWithTag(pointerAreaTag).performTouchInput(block)
+        rule.waitForIdle()
+    }
+
+    protected fun performMouseGesture(block: MouseInjectionScope.() -> Unit) {
+        rule.onNodeWithTag(pointerAreaTag).performMouseInput(block)
+        rule.waitForIdle()
+    }
+
+    protected fun touchDragTo(
+        position: Offset,
+        durationMillis: Long = 200L,
+    ) {
+        require(durationMillis > 0) { "Duration cannot be <= 0" }
+
+        var startVar: Offset? = null
+        var dragEventPeriodMillisVar: Long? = null
+        performTouchGesture {
+            startVar = requireNotNull(currentPosition()) { "No pointer is down to animate." }
+            dragEventPeriodMillisVar = this.eventPeriodMillis
+        }
+
+        val start = startVar!!
+        val dragEventPeriodMillis = dragEventPeriodMillisVar!!
+
+        // How many steps will we take in durationMillis?
+        // At least 1, and a number that will bring as as close to eventPeriod as possible
+        val steps = max(1, (durationMillis / dragEventPeriodMillis.toFloat()).roundToInt())
+
+        var previousTime = 0L
+        for (step in 1..steps) {
+            val progress = step / steps.toFloat()
+            val nextTime = lerp(0, stop = durationMillis, fraction = progress)
+            val nextPosition = lerp(start, position, nextTime / durationMillis.toFloat())
+            performTouchGesture {
+                moveTo(nextPosition, delayMillis = nextTime - previousTime)
+            }
+            previousTime = nextTime
+        }
+    }
+
+    protected fun touchDragBy(
+        delta: Offset,
+        durationMillis: Long = 100L
+    ) {
+        var startVar: Offset? = null
+        performTouchGesture {
+            startVar = requireNotNull(currentPosition()) { "No pointer is down to animate." }
+        }
+        val start = startVar!!
+        touchDragTo(start + delta, durationMillis)
+    }
+
+    protected fun mouseDragTo(
+        position: Offset,
+        durationMillis: Long = 200L,
+    ) {
+        require(durationMillis > 0) { "Duration cannot be <= 0" }
+
+        var startVar: Offset? = null
+        var dragEventPeriodMillisVar: Long? = null
+        performMouseGesture {
+            startVar = currentPosition
+            dragEventPeriodMillisVar = this.eventPeriodMillis
+        }
+        val start = startVar!!
+        val dragEventPeriodMillis = dragEventPeriodMillisVar!!
+
+        // How many steps will we take in durationMillis?
+        // At least 1, and a number that will bring as as close to eventPeriod as possible
+        val steps = max(1, (durationMillis / dragEventPeriodMillis.toFloat()).roundToInt())
+
+        var previousTime = 0L
+        for (step in 1..steps) {
+            val progress = step / steps.toFloat()
+            val nextTime = lerp(0, stop = durationMillis, fraction = progress)
+            val nextPosition = lerp(start, position, nextTime / durationMillis.toFloat())
+            performMouseGesture {
+                moveTo(nextPosition, delayMillis = nextTime - previousTime)
+            }
+            previousTime = nextTime
+        }
+    }
+
+    protected fun mouseDragBy(
+        delta: Offset,
+        durationMillis: Long = 100L
+    ) {
+        var startVar: Offset? = null
+        performMouseGesture {
+            startVar = currentPosition
+        }
+        val start = startVar!!
+        touchDragTo(start + delta, durationMillis)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextSelectionGesturesTest.kt
new file mode 100644
index 0000000..cc82ae2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextSelectionGesturesTest.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.MultiSelectionSubject
+import androidx.compose.foundation.text.selection.gestures.util.TextSelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.collapsed
+import androidx.compose.foundation.text.selection.gestures.util.offsetToLocalOffset
+import androidx.compose.foundation.text.selection.gestures.util.offsetToSelectableId
+import androidx.compose.foundation.text.selection.gestures.util.textContentIndices
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.util.fastForEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class MultiTextSelectionGesturesTest : TextSelectionGesturesTest() {
+
+    override lateinit var asserter: TextSelectionAsserter
+
+    override val pointerAreaTag = "selectionContainer"
+
+    private lateinit var texts: State<List<Pair<String, String>>>
+    private lateinit var textContentIndices: State<List<IntRange>>
+
+    @Before
+    fun setupAsserter() {
+        asserter = object : TextSelectionAsserter(
+            textContent = textContent.value,
+            rule = rule,
+            textToolbar = textToolbar,
+            hapticFeedback = hapticFeedback,
+            getActual = { selection.value }
+        ) {
+            override fun subAssert() {
+                Truth.assertAbout(MultiSelectionSubject.withContent(texts.value))
+                    .that(getActual())
+                    .hasSelection(selection)
+            }
+        }
+    }
+
+    @Composable
+    override fun TextContent() {
+        texts = derivedStateOf {
+            textContent.value
+                .split("\n")
+                .withIndex()
+                .map { (index, str) -> str to "testTag$index" }
+        }
+
+        textContentIndices = derivedStateOf { texts.value.textContentIndices() }
+
+        Column {
+            texts.value.fastForEach { (str, tag) ->
+                BasicText(
+                    text = str,
+                    style = TextStyle(
+                        fontFamily = fontFamily,
+                        fontSize = fontSize,
+                    ),
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .testTag(tag),
+                )
+            }
+        }
+    }
+
+    override fun characterPosition(offset: Int): Offset {
+        val selectableIndex = textContentIndices.value.offsetToSelectableId(offset)
+        val localOffset = textContentIndices.value.offsetToLocalOffset(offset)
+        val (_, tag) = texts.value[selectableIndex]
+        val nodePosition = rule.onNodeWithTag(tag).fetchSemanticsNode().positionInRoot
+        val textLayoutResult = rule.onNodeWithTag(tag).fetchTextLayoutResult()
+        return textLayoutResult.getBoundingBox(localOffset).translate(nodePosition).center
+    }
+
+    @Test
+    fun whenMouseCollapsedSelectionAcrossLines_thenTouch_noUiElements() {
+        performMouseGesture {
+            moveTo(boundsInRoot.centerRight - Offset(1f, 0f))
+            press()
+        }
+
+        asserter.applyAndAssert {
+            selection = 23.collapsed
+        }
+
+        mouseDragTo(characterPosition(24))
+
+        asserter.applyAndAssert {
+            selection = 23 to 24
+        }
+
+        performTouchGesture {
+            enterTouchMode()
+        }
+
+        asserter.assert()
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextWithSpaceSelectionGesturesRegressionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextWithSpaceSelectionGesturesRegressionTest.kt
new file mode 100644
index 0000000..fcb3e2b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiTextWithSpaceSelectionGesturesRegressionTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.MultiSelectionSubject
+import androidx.compose.foundation.text.selection.gestures.util.TextSelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.offsetToLocalOffset
+import androidx.compose.foundation.text.selection.gestures.util.offsetToSelectableId
+import androidx.compose.foundation.text.selection.gestures.util.textContentIndices
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.util.fastForEach
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class MultiTextWithSpaceSelectionGesturesRegressionTest : AbstractSelectionGesturesTest() {
+    private val textContent = "line1\nline2 text1 text2!\nline3"
+
+    private val texts = textContent
+        .split("\n")
+        .withIndex()
+        .map { (index, str) -> str to "testTag$index" }
+
+    private val textContentIndices = texts.textContentIndices()
+
+    override val pointerAreaTag = "selectionContainer"
+
+    private val selection = mutableStateOf<Selection?>(null)
+
+    private lateinit var asserter: TextSelectionAsserter
+
+    @Composable
+    override fun Content() {
+        SelectionContainer(
+            selection = selection.value,
+            onSelectionChange = { selection.value = it },
+            modifier = Modifier.testTag(pointerAreaTag)
+        ) {
+            Column {
+                texts.fastForEach { (str, tag) ->
+                    BasicText(
+                        text = str,
+                        style = TextStyle(
+                            fontFamily = fontFamily,
+                            fontSize = fontSize,
+                        ),
+                        modifier = Modifier.testTag(tag),
+                    )
+                }
+            }
+        }
+    }
+
+    @Before
+    fun setupAsserter() {
+        asserter = object : TextSelectionAsserter(
+            textContent = textContent,
+            rule = rule,
+            textToolbar = textToolbar,
+            hapticFeedback = hapticFeedback,
+            getActual = { selection.value }
+        ) {
+            override fun subAssert() {
+                Truth.assertAbout(MultiSelectionSubject.withContent(texts))
+                    .that(getActual())
+                    .hasSelection(selection)
+            }
+        }
+    }
+
+    @Suppress("SameParameterValue")
+    private fun characterPosition(offset: Int): Offset {
+        val selectableIndex = textContentIndices.offsetToSelectableId(offset)
+        val localOffset = textContentIndices.offsetToLocalOffset(offset)
+        val (_, tag) = texts[selectableIndex]
+        val nodePosition = rule.onNodeWithTag(tag).fetchSemanticsNode().positionInRoot
+        val textLayoutResult = rule.onNodeWithTag(tag).fetchTextLayoutResult()
+        return textLayoutResult.getBoundingBox(localOffset).translate(nodePosition).center
+    }
+
+    // There were cases where moving the cursor outside the bounds of any text would
+    // result in the selection being cleared. This test should catch any regression.
+    // It was fixed by changing SelectionMode bounds check to be inclusive.
+    @Suppress("SameParameterValue")
+    @Test
+    fun whenMouse_withDoubleClickThenDragUpAndDown_selectsWords() {
+        performMouseGesture {
+            moveTo(position = characterPosition(18))
+            press()
+            advanceEventTime()
+            release()
+            advanceEventTime()
+            press()
+        }
+
+        asserter.applyAndAssert {
+            selection = 18 to 23
+        }
+
+        mouseDragTo(position = boundsInRoot.topRight + Offset(-1f, 1f))
+
+        asserter.applyAndAssert {
+            selection = 24 to 6
+        }
+
+        mouseDragTo(position = boundsInRoot.bottomRight + Offset(-1f, -1f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 30
+        }
+
+        performMouseGesture {
+            release()
+        }
+
+        asserter.assert()
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/SingleTextSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/SingleTextSelectionGesturesTest.kt
new file mode 100644
index 0000000..fc5ea72
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/SingleTextSelectionGesturesTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.SelectionSubject
+import androidx.compose.foundation.text.selection.gestures.util.TextSelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.collapsed
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextStyle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class SingleTextSelectionGesturesTest : TextSelectionGesturesTest() {
+
+    private val testTag = "testTag"
+    override lateinit var asserter: TextSelectionAsserter
+
+    @Before
+    fun setupAsserter() {
+        asserter = object : TextSelectionAsserter(
+            textContent = textContent.value,
+            rule = rule,
+            textToolbar = textToolbar,
+            hapticFeedback = hapticFeedback,
+            getActual = { selection.value },
+        ) {
+            override fun subAssert() {
+                Truth.assertAbout(SelectionSubject.withContent(textContent))
+                    .that(getActual())
+                    .hasSelection(selection)
+            }
+        }
+    }
+
+    @Composable
+    override fun TextContent() {
+        BasicText(
+            text = textContent.value,
+            style = TextStyle(
+                fontFamily = fontFamily,
+                fontSize = fontSize,
+            ),
+            modifier = Modifier
+                .fillMaxWidth()
+                .testTag(testTag),
+        )
+    }
+
+    override fun characterPosition(offset: Int): Offset {
+        val nodePosition = rule.onNodeWithTag(testTag).fetchSemanticsNode().positionInRoot
+        val textLayoutResult = rule.onNodeWithTag(testTag).fetchTextLayoutResult()
+        return textLayoutResult.getBoundingBox(offset).translate(nodePosition).center
+    }
+
+    @Test
+    fun whenMouseCollapsedSelectionAcrossLines_thenTouch_showUi() {
+        performMouseGesture {
+            moveTo(boundsInRoot.centerRight - Offset(1f, 0f))
+            press()
+        }
+
+        asserter.applyAndAssert {
+            selection = 23.collapsed
+        }
+
+        mouseDragTo(characterPosition(offset = 24))
+
+        asserter.applyAndAssert {
+            selection = 23 to 24
+        }
+
+        performTouchGesture {
+            enterTouchMode()
+        }
+
+        asserter.applyAndAssert {
+            selectionHandlesShown = true
+            textToolbarShown = true
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextFieldSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextFieldSelectionGesturesTest.kt
new file mode 100644
index 0000000..e7e44a1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextFieldSelectionGesturesTest.kt
@@ -0,0 +1,933 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.TextFieldSelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.collapsed
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTestApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class TextFieldSelectionGesturesTest : AbstractSelectionGesturesTest() {
+
+    override val pointerAreaTag = "testTag"
+    private val textContent = "line1\nline2 text1 text2\nline3"
+
+    private val textFieldValue = mutableStateOf(TextFieldValue(textContent))
+
+    private lateinit var asserter: TextFieldSelectionAsserter
+
+    @Composable
+    override fun Content() {
+        BasicTextField(
+            value = textFieldValue.value,
+            onValueChange = { textFieldValue.value = it },
+            textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
+            modifier = Modifier
+                .fillMaxWidth()
+                .testTag(pointerAreaTag),
+        )
+    }
+
+    @Before
+    fun setupAsserter() {
+        asserter = TextFieldSelectionAsserter(
+            textContent = textContent,
+            rule = rule,
+            textToolbar = textToolbar,
+            hapticFeedback = hapticFeedback,
+            getActual = { textFieldValue.value }
+        )
+    }
+
+    @Test
+    fun whenTouch_withNoTextThenLongPress_noSelection() {
+        textFieldValue.value = TextFieldValue()
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longClick(boundsInRoot.center)
+        }
+
+        asserter.applyAndAssert {
+            textContent = ""
+            textToolbarShown = true // paste will show up if clipboard is not empty
+            hapticsCount++
+        }
+    }
+
+    @Test
+    fun whenTouch_withNoTextThenLongPressAndDrag_noSelection() {
+        textFieldValue.value = TextFieldValue()
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longPress(boundsInRoot.center)
+        }
+
+        asserter.applyAndAssert {
+            textContent = ""
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.centerLeft)
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true // paste will show up if clipboard is not empty
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenClear_noSelection() {
+        performTouchGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            click(characterPosition(14))
+        }
+
+        asserter.applyAndAssert {
+            selection = 14.collapsed
+            selectionHandlesShown = false
+            textToolbarShown = false
+            cursorHandleShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPress_selectsSingleWord() {
+        performTouchGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressesOnMultipleWords_selectsSingleWords() {
+        asserter.selectionHandlesShown = true
+        asserter.textToolbarShown = true
+
+        fun longClickCharacterPositionThenApplyAndAssert(offset: Int, selection: TextRange) {
+            performTouchGesture {
+                longClick(characterPosition(offset))
+            }
+
+            asserter.applyAndAssert {
+                this.selection = selection
+                hapticsCount++
+            }
+        }
+
+        longClickCharacterPositionThenApplyAndAssert(offset = 2, selection = 0 to 5)
+        longClickCharacterPositionThenApplyAndAssert(offset = 8, selection = 6 to 11)
+        longClickCharacterPositionThenApplyAndAssert(offset = 13, selection = 12 to 17)
+        longClickCharacterPositionThenApplyAndAssert(offset = 20, selection = 18 to 23)
+        longClickCharacterPositionThenApplyAndAssert(offset = 26, selection = 24 to 29)
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragLeftAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragForwardAndBackTest(
+            forwardOffset = characterPosition(8),
+            forwardSelection = 12 to 6,
+            backwardOffset = characterPosition(9),
+            backwardSelection = 12 to 9,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragUpAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragForwardAndBackTest(
+            forwardOffset = characterPosition(2),
+            forwardSelection = 12 to 0,
+            backwardOffset = characterPosition(3),
+            backwardSelection = 12 to 3,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragRightAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragForwardAndBackTest(
+            forwardOffset = characterPosition(21),
+            forwardSelection = 12 to 23,
+            backwardOffset = characterPosition(20),
+            backwardSelection = 12 to 20,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragDownAndBack_selectsWordsThenChars() {
+        // entering the bottom paragraph will select word since it is a forward selection,
+        // as we continue animating towards the forward position,
+        // it will be selecting by character because it will be backwards selecting at that point.
+        touchLongPressThenDragForwardAndBackTest(
+            forwardOffset = characterPosition(27),
+            forwardSelection = 12 to 27,
+            backwardOffset = characterPosition(26),
+            backwardSelection = 12 to 26,
+        )
+    }
+
+    private fun touchLongPressThenDragForwardAndBackTest(
+        forwardOffset: Offset,
+        forwardSelection: TextRange,
+        backwardOffset: Offset,
+        backwardSelection: TextRange,
+    ) {
+        performTouchGesture {
+            longPress(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            magnifierShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(forwardOffset)
+
+        asserter.applyAndAssert {
+            selection = forwardSelection
+            hapticsCount++
+        }
+
+        touchDragTo(backwardOffset)
+
+        asserter.applyAndAssert {
+            selection = backwardSelection
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+            magnifierShown = false
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragToUpperEndPaddingAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragToEndPaddingTest(
+            endOffset = boundsInRoot.topRight + Offset(-1f, 1f),
+            endSelection = 12 to 0,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragToMiddleEndPaddingAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragToEndPaddingTest(
+            endOffset = boundsInRoot.centerRight + Offset(-1f, 0f),
+            endSelection = 12 to 23,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragToLowerEndPaddingAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragToEndPaddingTest(
+            endOffset = boundsInRoot.bottomRight + Offset(-1f, -1f),
+            endSelection = 12 to 29,
+        )
+    }
+
+    private fun touchLongPressThenDragToEndPaddingTest(
+        endOffset: Offset,
+        endSelection: TextRange,
+    ) {
+        performTouchGesture {
+            longPress(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            magnifierShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(endOffset)
+
+        asserter.applyAndAssert {
+            selection = endSelection
+            magnifierShown = false
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPadding_entersSelectionMode() {
+        performTouchGesture {
+            longPress(boundsInRoot.topRight + Offset(-1f, 1f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 5.collapsed
+            cursorHandleShown = true
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingOfEmptyLine_entersSelectionMode() {
+        val content = "Line1\n\nLine3"
+        textFieldValue.value = TextFieldValue(content)
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight + Offset(-1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            textContent = content
+            selection = 6.collapsed
+            cursorHandleShown = true
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingThenDragToUpperEndPadding_selectsParagraphAndNewLine() {
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight + Offset(-1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 23.collapsed
+            cursorHandleShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.topRight + Offset(-1f, 1f))
+
+        asserter.applyAndAssert {
+            selection = 23 to 0
+            cursorHandleShown = false
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingThenDragToLowerEndPadding_selectsNewLineAndParagraph() {
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight + Offset(-1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 23.collapsed
+            cursorHandleShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.bottomRight + Offset(-1f, -1f))
+
+        asserter.applyAndAssert {
+            selection = 23 to 29
+            cursorHandleShown = false
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingOfFinalLine_entersSelectionMode() {
+        performTouchGesture {
+            longPress(boundsInRoot.bottomRight + Offset(-1f, -1f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 29.collapsed
+            cursorHandleShown = true
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressThanDragAcrossSingleWord_onlySelectsSingleWordAndNoOtherChanges() {
+        performTouchGesture {
+            longPress(characterPosition(15))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            magnifierShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(characterPosition(13))
+
+        asserter.applyAndAssert {
+            selection = 12 to 13
+            hapticsCount++
+        }
+
+        touchDragTo(characterPosition(15))
+
+        asserter.applyAndAssert {
+            selection = 12 to 15
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+            magnifierShown = false
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingOfEmptyFinalLine_entersSelectionMode() {
+        val content = "Line1\n\n"
+        textFieldValue.value = TextFieldValue(content)
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longPress(boundsInRoot.bottomRight + Offset(-1f, -1f))
+        }
+
+        asserter.applyAndAssert {
+            textContent = content
+            selection = 7.collapsed
+            cursorHandleShown = true
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    // Regression test for a mouse long click resulting in touch behaviors for selection.
+    @Test
+    fun whenMouse_withLongClick_collapsedSelectionAtClick() {
+        performMouseGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+    }
+
+    @Test
+    fun whenMouse_withClick_collapsedSelectionAtClick() {
+        performMouseGesture {
+            click(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+    }
+
+    @Test
+    fun whenMouse_withSingleClick_collapsedSelection() {
+        performMouseGesture {
+            moveTo(position = characterPosition(13))
+            press()
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+
+        performMouseGesture {
+            release()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragLeft_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(8),
+            endSelection = 13 to 8,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragUp_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(2),
+            endSelection = 13 to 2,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragRight_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(19),
+            endSelection = 13 to 19,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragDown_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(26),
+            endSelection = 13 to 26,
+        )
+    }
+
+    private fun mouseSingleClickThenDragTest(endOffset: Offset, endSelection: TextRange) {
+        mouseClicksThenDragTest(
+            numClicks = 1,
+            startOffset = characterPosition(13),
+            endOffset = endOffset,
+            startSelection = 13.collapsed,
+            endSelection = endSelection,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClick_selectsWord() {
+        performMouseGesture {
+            repeat(2) { click(characterPosition(13)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+        }
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragLeft_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(8),
+            endSelection = 17 to 6,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragUp_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(2),
+            endSelection = 17 to 0,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragRight_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(19),
+            endSelection = 12 to 23,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragDown_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(26),
+            endSelection = 12 to 29,
+        )
+    }
+
+    private fun mouseDoubleClickThenDragTest(endOffset: Offset, endSelection: TextRange) {
+        mouseClicksThenDragTest(
+            numClicks = 2,
+            startOffset = characterPosition(13),
+            endOffset = endOffset,
+            startSelection = 12 to 17,
+            endSelection = endSelection,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClick_selectsParagraph() {
+        performMouseGesture {
+            repeat(3) { click(characterPosition(13)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 6 to 23
+        }
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragLeft_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(8),
+            endSelection = 23 to 6,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragUp_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(2),
+            endSelection = 23 to 0,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragRight_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(19),
+            endSelection = 6 to 23,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragDown_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(26),
+            endSelection = 6 to 29,
+        )
+    }
+
+    private fun mouseTripleClickThenDragTest(endOffset: Offset, endSelection: TextRange) {
+        mouseClicksThenDragTest(
+            numClicks = 3,
+            startOffset = characterPosition(13),
+            endOffset = endOffset,
+            startSelection = 6 to 23,
+            endSelection = endSelection,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickOnFirstLetterOfLine_collapsedSelection() {
+        mouseFirstLetterOfLineClicksTest(
+            numClicks = 1,
+            selection = 6.collapsed,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickOnFirstLetterOfLine_selectsFirstWord() {
+        mouseFirstLetterOfLineClicksTest(
+            numClicks = 2,
+            selection = 6 to 11,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickOnFirstLetterOfLine_selectsParagraph() {
+        mouseFirstLetterOfLineClicksTest(
+            numClicks = 3,
+            selection = 6 to 23,
+        )
+    }
+
+    // regression test for when selections would overflow onto previous line
+    private fun mouseFirstLetterOfLineClicksTest(numClicks: Int, selection: TextRange) {
+        val initialClickOffset = characterBox(6).centerLeft + Offset(1f, 0f)
+        mouseClicksThenDragTest(
+            numClicks = numClicks,
+            startOffset = initialClickOffset,
+            endOffset = initialClickOffset + Offset(0f, 1f),
+            startSelection = selection,
+            endSelection = selection,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickInEndPaddingOfLine_collapsedSelection() {
+        mouseEndPaddingClicksTest(
+            numClicks = 1,
+            selection = 23.collapsed,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickOInEndPaddingOfLine_selectsLastWord() {
+        mouseEndPaddingClicksTest(
+            numClicks = 2,
+            selection = 18 to 23,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickInEndPaddingOfLine_selectsParagraph() {
+        mouseEndPaddingClicksTest(
+            numClicks = 3,
+            selection = 6 to 23,
+        )
+    }
+
+    // regression test for when selections would overflow onto next line
+    private fun mouseEndPaddingClicksTest(numClicks: Int, selection: TextRange) {
+        val initialClickOffset = boundsInRoot.centerRight + Offset(-1f, 0f)
+        mouseClicksThenDragTest(
+            numClicks = numClicks,
+            startOffset = initialClickOffset,
+            endOffset = initialClickOffset + Offset(0f, 1f),
+            startSelection = selection,
+            endSelection = selection,
+        )
+    }
+
+    private fun mouseClicksThenDragTest(
+        numClicks: Int,
+        startOffset: Offset,
+        endOffset: Offset,
+        startSelection: TextRange,
+        endSelection: TextRange
+    ) {
+        check(numClicks > 0) { "Must be at least one click" }
+        performMouseGesture {
+            moveTo(startOffset)
+            press()
+            repeat(numClicks - 1) {
+                advanceEventTime()
+                release()
+                advanceEventTime()
+                press()
+            }
+        }
+
+        asserter.applyAndAssert {
+            selection = startSelection
+        }
+
+        mouseDragTo(endOffset)
+
+        asserter.applyAndAssert {
+            selection = endSelection
+        }
+
+        performMouseGesture {
+            release()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenMouse_thenTouch_touchBehaviorsAppear() {
+        performMouseGesture {
+            repeat(2) { click(characterPosition(13)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+        }
+
+        performTouchGesture {
+            enterTouchMode()
+        }
+
+        asserter.applyAndAssert {
+            selectionHandlesShown = true
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_thenMouse_touchBehaviorsDisappear() {
+        performTouchGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+
+        enterMouseMode()
+
+        asserter.applyAndAssert {
+            selectionHandlesShown = false
+            textToolbarShown = false
+        }
+    }
+
+    // Regression test for when this would result in text toolbar showing instead of the cursor.
+    @Test
+    fun whenMouseCollapsedSelection_thenTouch_ToolbarAndCursorAppears() {
+        performMouseGesture {
+            click(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+
+        performTouchGesture {
+            enterTouchMode()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+            cursorHandleShown = true
+        }
+    }
+
+    @Test
+    fun whenTouchCollapsedSelection_thenMouse_noUiElements() {
+        performTouchGesture {
+            click(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+            cursorHandleShown = true
+        }
+
+        enterMouseMode()
+
+        asserter.applyAndAssert {
+            cursorHandleShown = false
+        }
+    }
+
+    // Regression test for when this instead selected the current and next (if any) paragraph.
+    @Test
+    fun whenMouse_thenTripleClickInEndPadding_selectsCurrentParagraph() {
+        performMouseGesture {
+            repeat(3) { click(boundsInRoot.centerRight - Offset(1f, 0f)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 6 to 23
+        }
+    }
+
+    private fun characterPosition(offset: Int): Offset = characterBox(offset).center
+
+    private fun characterBox(offset: Int): Rect {
+        val nodePosition = rule.onNodeWithTag(pointerAreaTag).fetchSemanticsNode().positionInRoot
+        val textLayoutResult = rule.onNodeWithTag(pointerAreaTag).fetchTextLayoutResult()
+        return textLayoutResult.getBoundingBox(offset).translate(nodePosition)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextSelectionGesturesTest.kt
new file mode 100644
index 0000000..fec14b8
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextSelectionGesturesTest.kt
@@ -0,0 +1,941 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.gestures.util.TextSelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.collapsed
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.text.TextRange
+import org.junit.Test
+
+@OptIn(ExperimentalTestApi::class)
+internal abstract class TextSelectionGesturesTest : AbstractSelectionGesturesTest() {
+
+    override val pointerAreaTag = "selectionContainer"
+
+    protected val textContent = mutableStateOf("line1\nline2 text1 text2\nline3")
+    protected val selection = mutableStateOf<Selection?>(null)
+
+    protected abstract var asserter: TextSelectionAsserter
+
+    @Composable
+    abstract fun TextContent()
+
+    @Composable
+    override fun Content() {
+        SelectionContainer(
+            selection = selection.value,
+            onSelectionChange = { selection.value = it },
+            modifier = Modifier.testTag(pointerAreaTag)
+        ) {
+            TextContent()
+        }
+    }
+
+    protected abstract fun characterPosition(offset: Int): Offset
+
+    @Test
+    fun whenTouch_withNoTextThenLongPress_noSelection() {
+        val content = ""
+        textContent.value = content
+        asserter.textContent = content
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longClick(boundsInRoot.center)
+        }
+
+        asserter.applyAndAssert {
+            selection = 0.collapsed
+            hapticsCount++
+        }
+    }
+
+    @Test
+    fun whenTouch_withNoTextThenLongPressAndDrag_noSelection() {
+        val content = ""
+        textContent.value = content
+        asserter.textContent = content
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longPress(boundsInRoot.center)
+        }
+
+        asserter.applyAndAssert {
+            selection = 0.collapsed
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.centerLeft)
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingOfEmptyLine_entersSelectionMode() {
+        val content = "Line1\n\nLine3"
+        textContent.value = content
+        asserter.textContent = content
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight + Offset(-1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 6.collapsed
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingOfEmptyFinalLine_entersSelectionMode() {
+        val content = "Line1\n\n"
+        textContent.value = content
+        asserter.textContent = content
+        rule.waitForIdle()
+
+        performTouchGesture {
+            longPress(boundsInRoot.bottomRight + Offset(-1f, -1f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 7.collapsed
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenClear_noSelection() {
+        performTouchGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            click(characterPosition(14))
+        }
+
+        asserter.applyAndAssert {
+            selection = null
+            selectionHandlesShown = false
+            textToolbarShown = false
+            hapticsCount++
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPress_selectsSingleWord() {
+        performTouchGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressesOnMultipleWords_selectsSingleWords() {
+        asserter.selectionHandlesShown = true
+        asserter.textToolbarShown = true
+
+        fun longClickCharacterPositionThenApplyAndAssert(offset: Int, selection: TextRange) {
+            performTouchGesture {
+                longClick(characterPosition(offset))
+            }
+
+            asserter.applyAndAssert {
+                this.selection = selection
+                hapticsCount++
+            }
+        }
+
+        longClickCharacterPositionThenApplyAndAssert(offset = 2, selection = 0 to 5)
+        longClickCharacterPositionThenApplyAndAssert(offset = 8, selection = 6 to 11)
+        longClickCharacterPositionThenApplyAndAssert(offset = 13, selection = 12 to 17)
+        longClickCharacterPositionThenApplyAndAssert(offset = 20, selection = 18 to 23)
+        longClickCharacterPositionThenApplyAndAssert(offset = 26, selection = 24 to 29)
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragLeftAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragForwardsAndBackTest(
+            forwardOffset = characterPosition(8),
+            forwardSelection = 12 to 6,
+            backwardOffset = characterPosition(9),
+            backwardSelection = 12 to 9,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragUpAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragForwardsAndBackTest(
+            forwardOffset = characterPosition(1),
+            forwardSelection = 12 to 0,
+            backwardOffset = characterPosition(3),
+            backwardSelection = 12 to 3,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragRightAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragForwardsAndBackTest(
+            forwardOffset = characterPosition(22),
+            forwardSelection = 12 to 23,
+            backwardOffset = characterPosition(19),
+            backwardSelection = 12 to 19,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragDownAndBack_selectsWordsThenChars() {
+        // entering the bottom paragraph will select word since it is a forward selection,
+        // as we continue animating towards the forward position,
+        // it will be selecting by character because it will be backwards selecting at that point.
+        touchLongPressThenDragForwardsAndBackTest(
+            forwardOffset = characterPosition(27),
+            forwardSelection = 12 to 27,
+            backwardOffset = characterPosition(26),
+            backwardSelection = 12 to 26,
+        )
+    }
+
+    private fun touchLongPressThenDragForwardsAndBackTest(
+        forwardOffset: Offset,
+        forwardSelection: TextRange?,
+        backwardOffset: Offset,
+        backwardSelection: TextRange?,
+    ) {
+        performTouchGesture {
+            longPress(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            magnifierShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(forwardOffset)
+
+        asserter.applyAndAssert {
+            selection = forwardSelection
+            hapticsCount++
+        }
+
+        touchDragTo(backwardOffset)
+
+        asserter.applyAndAssert {
+            selection = backwardSelection
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+            magnifierShown = false
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragToUpperEndPaddingAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragToEndPaddingTest(
+            endOffset = boundsInRoot.topRight + Offset(-1f, 1f),
+            endSelection = 12 to 0,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragToMiddleEndPaddingAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragToEndPaddingTest(
+            endOffset = boundsInRoot.centerRight + Offset(-1f, 0f),
+            endSelection = 12 to 23,
+        )
+    }
+
+    @Test
+    fun whenTouch_withLongPressThenDragToLowerEndPaddingAndBack_selectsWordsThenChars() {
+        touchLongPressThenDragToEndPaddingTest(
+            endOffset = boundsInRoot.bottomRight + Offset(-1f, -1f),
+            endSelection = 12 to 29,
+        )
+    }
+
+    private fun touchLongPressThenDragToEndPaddingTest(
+        endOffset: Offset,
+        endSelection: TextRange,
+    ) {
+        performTouchGesture {
+            longPress(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            magnifierShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(endOffset)
+
+        asserter.applyAndAssert {
+            selection = endSelection
+            magnifierShown = false
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPadding_entersSelectionMode() {
+        performTouchGesture {
+            longPress(boundsInRoot.topRight + Offset(-1f, 1f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 0 to 5
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingThenDragToUpperEndPadding_selectsParagraphAndNewLine() {
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight + Offset(-1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 18 to 23
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.topRight + Offset(-1f, 1f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 0
+            hapticsCount++
+        }
+
+        // do it again for a regression where selection was only wrong the second time
+        touchDragTo(boundsInRoot.centerRight + Offset(-1f, 0f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 23
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.topRight + Offset(-1f, 1f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 0
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingThenDragToLowerEndPadding_selectsNewLineAndParagraph() {
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight + Offset(-1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 18 to 23
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.bottomRight + Offset(-1f, -1f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 29
+            hapticsCount++
+        }
+
+        // do it again for a regression where selection was only wrong the second time
+        touchDragTo(boundsInRoot.centerRight + Offset(-1f, 0f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 23
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.bottomRight + Offset(-1f, -1f))
+
+        asserter.applyAndAssert {
+            selection = 18 to 29
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_withLongPressInEndPaddingOfFinalLine_entersSelectionMode() {
+        performTouchGesture {
+            longPress(boundsInRoot.bottomRight + Offset(-1f, -1f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 24 to 29
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        // we want to test at least one drag that shouldn't affect selection as well
+        touchDragBy(Offset(-1f, 0f))
+
+        asserter.assert()
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    // regression test for abnormal selection behavior when dragging
+    // from bottom to middle end padding
+    @Test
+    fun whenTouch_withLongPressInFinalLineEndPaddingThenDragToMidEndPadding_entersSelectionMode() {
+        performTouchGesture {
+            longPress(boundsInRoot.bottomRight + Offset(-1f, -1f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 24 to 29
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.centerRight + Offset(-1f, 0f))
+
+        asserter.applyAndAssert {
+            selection = 24 to 18
+            hapticsCount++
+        }
+
+        // do it again for a regression where selection was only wrong the second time
+        touchDragTo(boundsInRoot.bottomRight + Offset(-1f, -1f))
+
+        asserter.applyAndAssert {
+            selection = 24 to 29
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        touchDragTo(boundsInRoot.centerRight + Offset(-1f, 0f))
+
+        asserter.applyAndAssert {
+            selection = 24 to 18
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    // Regression test for a touch long press in end padding selecting the entire Text
+    @Test
+    fun whenTouch_withLongPressInEndPadding_selectsFinalWord() {
+        performTouchGesture {
+            longPress(boundsInRoot.centerRight - Offset(1f, 0f))
+        }
+
+        asserter.applyAndAssert {
+            selection = 18 to 23
+            selectionHandlesShown = true
+            hapticsCount++
+        }
+
+        performTouchGesture {
+            up()
+        }
+
+        asserter.applyAndAssert {
+            textToolbarShown = true
+        }
+    }
+
+    // Regression test for a mouse long click resulting in touch behaviors for selection.
+    @Test
+    fun whenMouse_withLongClick_collapsedSelectionAtClick() {
+        performMouseGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+    }
+
+    @Test
+    fun whenMouse_withClick_collapsedSelectionAtClick() {
+        performMouseGesture {
+            click(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+    }
+
+    @Test
+    fun whenMouse_withSingleClick_collapsedSelection() {
+        performMouseGesture {
+            moveTo(position = characterPosition(13))
+            press()
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+
+        performMouseGesture {
+            release()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragLeft_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(8),
+            endSelection = 13 to 8,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragUp_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(2),
+            endSelection = 13 to 2,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragRight_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(20),
+            endSelection = 13 to 20,
+        )
+    }
+
+    @Test
+    fun whenMouse_withSingleClickThenDragDown_selectsCharacters() {
+        mouseSingleClickThenDragTest(
+            endOffset = characterPosition(26),
+            endSelection = 13 to 26,
+        )
+    }
+
+    private fun mouseSingleClickThenDragTest(
+        endOffset: Offset,
+        endSelection: TextRange?
+    ) {
+        mouseClicksThenDragTest(
+            numClicks = 1,
+            startOffset = characterPosition(13),
+            endOffset = endOffset,
+            startSelection = 13.collapsed,
+            endSelection = endSelection,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClick_selectsWord() {
+        performMouseGesture {
+            repeat(2) { click(characterPosition(13)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+        }
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragLeft_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(8),
+            endSelection = 17 to 6,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragUp_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(2),
+            endSelection = 17 to 0,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragRight_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(20),
+            endSelection = 12 to 23,
+        )
+    }
+
+    @Test
+    fun whenMouse_withDoubleClickThenDragDown_selectsWords() {
+        mouseDoubleClickThenDragTest(
+            endOffset = characterPosition(26),
+            endSelection = 12 to 29,
+        )
+    }
+
+    private fun mouseDoubleClickThenDragTest(
+        endOffset: Offset,
+        endSelection: TextRange?
+    ) {
+        mouseClicksThenDragTest(
+            numClicks = 2,
+            startOffset = characterPosition(13),
+            endOffset = endOffset,
+            startSelection = 12 to 17,
+            endSelection = endSelection,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClick_selectsParagraph() {
+        performMouseGesture {
+            repeat(3) { click(characterPosition(13)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 6 to 23
+        }
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragLeft_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(8),
+            endSelection = 6 to 23,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragUp_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(2),
+            endSelection = 23 to 0,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragRight_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(20),
+            endSelection = 6 to 23,
+        )
+    }
+
+    @Test
+    fun whenMouse_withTripleClickThenDragDown_selectsParagraphs() {
+        mouseTripleClickThenDragTest(
+            endOffset = characterPosition(26),
+            endSelection = 6 to 29,
+        )
+    }
+
+    private fun mouseTripleClickThenDragTest(
+        endOffset: Offset,
+        endSelection: TextRange?
+    ) {
+        mouseClicksThenDragTest(
+            numClicks = 3,
+            startOffset = characterPosition(13),
+            endOffset = endOffset,
+            startSelection = 6 to 23,
+            endSelection = endSelection,
+        )
+    }
+
+    private fun mouseClicksThenDragTest(
+        numClicks: Int,
+        startOffset: Offset,
+        endOffset: Offset,
+        startSelection: TextRange?,
+        endSelection: TextRange?
+    ) {
+        check(numClicks > 0) { "Must be at least one click" }
+        performMouseGesture {
+            moveTo(startOffset)
+            press()
+            repeat(numClicks - 1) {
+                advanceEventTime()
+                release()
+                advanceEventTime()
+                press()
+            }
+        }
+
+        asserter.applyAndAssert {
+            selection = startSelection
+        }
+
+        mouseDragTo(endOffset)
+
+        asserter.applyAndAssert {
+            selection = endSelection
+        }
+
+        performMouseGesture {
+            release()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenMouse_thenSingleClickAndDragUpToEndPadding_selectsCharacters() {
+        mouseClickThenDragUpToPaddingTest(
+            numClicks = 1,
+            endSelection = 13 to 5,
+        )
+    }
+
+    @Test
+    fun whenMouse_thenDoubleClickAndDragUpToEndPadding_selectsWords() {
+        mouseClickThenDragUpToPaddingTest(
+            numClicks = 2,
+            endSelection = 17 to 0,
+        )
+    }
+
+    @Test
+    fun whenMouse_thenTripleClickAndDragUpToEndPadding_selectsParagraph() {
+        mouseClickThenDragUpToPaddingTest(
+            numClicks = 3,
+            endSelection = 23 to 0,
+        )
+    }
+
+    private fun mouseClickThenDragUpToPaddingTest(
+        numClicks: Int,
+        endSelection: TextRange?
+    ) {
+        performMouseGesture {
+            moveTo(position = characterPosition(13))
+            press()
+            repeat(numClicks - 1) {
+                advanceEventTime()
+                release()
+                advanceEventTime()
+                press()
+            }
+        }
+
+        mouseDragTo(position = boundsInRoot.topRight + Offset(-1f, 1f))
+
+        asserter.applyAndAssert {
+            selection = endSelection
+        }
+    }
+
+    @Test
+    fun whenMouse_thenTouch_touchBehaviorsAppear() {
+        performMouseGesture {
+            repeat(2) { click(characterPosition(13)) }
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+        }
+
+        performTouchGesture {
+            enterTouchMode()
+        }
+
+        asserter.applyAndAssert {
+            selectionHandlesShown = true
+            textToolbarShown = true
+        }
+    }
+
+    @Test
+    fun whenTouch_thenMouse_touchBehaviorsDisappear() {
+        performTouchGesture {
+            longClick(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+
+        enterMouseMode()
+
+        asserter.applyAndAssert {
+            selectionHandlesShown = false
+            textToolbarShown = false
+        }
+    }
+
+    // Regression test for when this would result in text toolbar showing instead of the cursor.
+    @Test
+    fun whenMouseCollapsedSelection_thenTouch_noUiElements() {
+        performMouseGesture {
+            click(characterPosition(13))
+        }
+
+        asserter.applyAndAssert {
+            selection = 13.collapsed
+        }
+
+        performTouchGesture {
+            enterTouchMode()
+        }
+
+        asserter.assert()
+    }
+
+    @Test
+    fun whenTouchCollapsedSelection_thenMouse_noUiElements() {
+        performTouchGesture {
+            click(characterPosition(13))
+        }
+
+        asserter.assert()
+        enterMouseMode()
+        asserter.assert()
+    }
+
+    @Test
+    fun whenMouse_thenTripleClickInEndPadding_selectsOnlyCurrentParagraph() {
+        performMouseGesture {
+            moveTo(position = boundsInRoot.centerRight - Offset(1f, 0f))
+            press()
+            repeat(2) {
+                advanceEventTime()
+                release()
+                advanceEventTime()
+                press()
+            }
+            release()
+        }
+
+        asserter.applyAndAssert {
+            selection = 6 to 23
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextSelectionHandlesGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextSelectionHandlesGesturesTest.kt
new file mode 100644
index 0000000..453be46
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/TextSelectionHandlesGesturesTest.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.SelectionAsserter
+import androidx.compose.foundation.text.selection.gestures.util.SelectionSubject
+import androidx.compose.foundation.text.selection.gestures.util.applyAndAssert
+import androidx.compose.foundation.text.selection.gestures.util.to
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import kotlin.math.sign
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class TextSelectionHandlesGesturesTest : AbstractSelectionGesturesTest() {
+
+    override val pointerAreaTag = "selectionContainer"
+
+    private val textContent = "line1\nline2 text1 text2\nline3"
+    private val selectionContainerTestTag = "selectionContainer"
+    private val testTag = "testTag"
+
+    private val selection = mutableStateOf<Selection?>(null)
+
+    private lateinit var asserter: SelectionAsserter<Selection?>
+
+    @Before
+    fun setupAsserter() {
+        performTouchGesture {
+            longClick(characterBox(13).center)
+        }
+
+        asserter = object : SelectionAsserter<Selection?>(
+            textContent = textContent,
+            rule = rule,
+            textToolbar = textToolbar,
+            hapticFeedback = hapticFeedback,
+            getActual = { selection.value },
+        ) {
+            var selection: TextRange? = null
+            override fun subAssert() {
+                Truth.assertAbout(SelectionSubject.withContent(textContent))
+                    .that(getActual())
+                    .hasSelection(selection)
+            }
+        }.apply {
+            selection = 12 to 17
+            selectionHandlesShown = true
+            textToolbarShown = true
+            hapticsCount++
+        }
+    }
+
+    @Composable
+    override fun Content() {
+        SelectionContainer(
+            selection = selection.value,
+            onSelectionChange = { selection.value = it },
+            modifier = Modifier
+                .fillMaxSize()
+                .testTag(selectionContainerTestTag)
+        ) {
+            BasicText(
+                text = textContent,
+                style = TextStyle(
+                    fontFamily = fontFamily,
+                    fontSize = fontSize,
+                ),
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .wrapContentHeight()
+                    .testTag(testTag),
+            )
+        }
+    }
+
+    @Test
+    fun whenOnlySetup_middleWordIsSelected() {
+        asserter.assert()
+    }
+
+    @Test
+    fun whenTouchHandle_magnifierReplacesToolbar() {
+        withHandlePressed(Handle.SelectionEnd) {
+            asserter.applyAndAssert {
+                magnifierShown = true
+                textToolbarShown = false
+            }
+        }
+
+        asserter.applyAndAssert {
+            magnifierShown = false
+            textToolbarShown = true
+        }
+
+        withHandlePressed(Handle.SelectionStart) {
+            asserter.applyAndAssert {
+                magnifierShown = true
+                textToolbarShown = false
+            }
+        }
+
+        asserter.applyAndAssert {
+            magnifierShown = false
+            textToolbarShown = true
+        }
+    }
+
+    private fun withHandlePressed(
+        handle: Handle,
+        block: SemanticsNodeInteraction.() -> Unit
+    ) = rule.onNode(isSelectionHandle(handle)).run {
+        performTouchInput { down(center) }
+        block()
+        performTouchInput { up() }
+    }
+
+    private fun SemanticsNodeInteraction.moveHandleToCharacter(characterOffset: Int) {
+        val selectionHandleInfo = fetchSemanticsNode().config[SelectionHandleInfoKey]
+        val destinationPosition = characterBox(characterOffset).run {
+            if (selectionHandleInfo.handle == Handle.SelectionStart) bottomLeft else bottomRight
+        }
+        val delta = destinationPosition - selectionHandleInfo.position
+
+        var slop: Offset? = null
+        performTouchInput {
+            slop = Offset(
+                x = viewConfiguration.touchSlop * delta.x.sign,
+                y = viewConfiguration.touchSlop * delta.y.sign
+            )
+        }
+        touchDragBy(delta + slop!!)
+    }
+
+    private fun characterBox(offset: Int): Rect {
+        val nodePosition = rule.onNodeWithTag(testTag).fetchSemanticsNode().positionInRoot
+        val textLayoutResult = rule.onNodeWithTag(testTag).fetchTextLayoutResult()
+        return textLayoutResult.getBoundingBox(offset).translate(nodePosition)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/MultiTextSelectionTestUtils.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/MultiTextSelectionTestUtils.kt
new file mode 100644
index 0000000..e6b00b1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/MultiTextSelectionTestUtils.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.foundation.text.selection.gestures.util
+
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.style.ResolvedTextDirection.Ltr
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+import kotlin.math.max
+import kotlin.math.min
+
+internal class MultiSelectionSubject(
+    failureMetadata: FailureMetadata?,
+    private val subject: Selection?,
+    private val texts: List<Pair<String, String>>,
+) : Subject(failureMetadata, subject) {
+
+    private val textContentIndices = texts.textContentIndices()
+
+    companion object {
+        fun withContent(
+            texts: List<Pair<String, String>>
+        ): Factory<MultiSelectionSubject, Selection?> =
+            Factory { failureMetadata, subject ->
+                MultiSelectionSubject(failureMetadata, subject, texts)
+            }
+    }
+
+    fun hasSelection(expected: TextRange?) {
+        if (expected == null) {
+            Truth.assertThat(subject).isNull()
+            return
+        }
+
+        check("selection").that(subject).isNotNull()
+
+        val startSelectableId = textContentIndices.offsetToSelectableId(expected.start) + 1
+        val startOffset = textContentIndices.offsetToLocalOffset(expected.start)
+        val endSelectableId = textContentIndices.offsetToSelectableId(expected.end) + 1
+        val endOffset = textContentIndices.offsetToLocalOffset(expected.end)
+
+        val expectedSelection = Selection(
+            start = Selection.AnchorInfo(Ltr, startOffset, startSelectableId.toLong()),
+            end = Selection.AnchorInfo(Ltr, endOffset, endSelectableId.toLong()),
+            handlesCrossed = startSelectableId > endSelectableId ||
+                (startSelectableId == endSelectableId && startOffset > endOffset),
+        )
+
+        if (subject!! != expectedSelection) {
+            failWithActual(
+                Fact.simpleFact("expected equal selections"),
+                Fact.fact("expected", expectedSelection.multiTextToString(texts)),
+            )
+        }
+    }
+
+    override fun actualCustomStringRepresentation(): String =
+        subject?.multiTextToString(texts) ?: "null"
+
+    private val Selection.AnchorInfo.stringIndex
+        get() = textContentIndices[selectableId.toInt() - 1].first + offset
+
+    private val Selection.minStringIndex get() = min(start.stringIndex, end.stringIndex)
+    private val Selection.maxStringIndex get() = max(start.stringIndex, end.stringIndex)
+
+    private fun Selection.multiTextToString(texts: List<Pair<String, String>>): String {
+        val content = texts.joinToString(separator = "\n") { it.first }
+        val collapsedSelection = start.stringIndex == end.stringIndex
+        val selectionString = content
+            .map { if (it == '\n') '\n' else '.' }
+            .joinToString("")
+            .let {
+                if (collapsedSelection) {
+                    if (start.stringIndex == content.length) {
+                        // edge case of selection being at end of text,
+                        // so append the marker instead of replacing
+                        "$it|"
+                    } else if (content[start.stringIndex] == '\n') {
+                        it.replaceRange(start.stringIndex..start.stringIndex, "|\n")
+                    } else {
+                        it.replaceRange(start.stringIndex..start.stringIndex, "|")
+                    }
+                } else {
+                    val selectionRange = minStringIndex until maxStringIndex
+                    it.replaceRange(selectionRange, content.substring(selectionRange))
+                }
+            }
+        return """
+                |Collapsed = $collapsedSelection
+                |Selection = $this
+                |$selectionString
+            """.trimMargin().trim()
+    }
+}
+
+internal fun List<Pair<String, String>>.textContentIndices() =
+    runningFold(0) { runningIndex, (str, _) -> runningIndex + str.length + 1 }
+        .zipWithNext()
+        .map { (prev, next) -> prev until next }
+
+internal fun List<IntRange>.offsetToSelectableId(i: Int) = getIndexRange(i).index
+internal fun List<IntRange>.offsetToLocalOffset(i: Int): Int = i - getIndexRange(i).value.first
+
+private fun List<IntRange>.getIndexRange(i: Int): IndexedValue<IntRange> =
+    withIndex().first { (_, range) -> i in range }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/SingleTextSelectionTestUtils.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/SingleTextSelectionTestUtils.kt
new file mode 100644
index 0000000..187c080
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/SingleTextSelectionTestUtils.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.foundation.text.selection.gestures.util
+
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+import kotlin.math.max
+import kotlin.math.min
+
+private val Selection.min get() = min(start.offset, end.offset)
+private val Selection.max get() = max(start.offset, end.offset)
+
+internal class SelectionSubject constructor(
+    failureMetadata: FailureMetadata?,
+    private val subject: Selection?,
+    private val content: String,
+) : Subject(failureMetadata, subject) {
+
+    companion object {
+        fun withContent(content: String): Factory<SelectionSubject, Selection?> =
+            Factory { failureMetadata, subject ->
+                SelectionSubject(failureMetadata, subject, content)
+            }
+    }
+
+    fun hasSelection(expected: TextRange?) {
+        if (expected == null) {
+            Truth.assertThat(subject).isNull()
+            return
+        }
+
+        check("selection").that(subject).isNotNull()
+        subject!! // smart cast to non-nullable
+
+        val startHandle = Selection.AnchorInfo(ResolvedTextDirection.Ltr, expected.start, 1)
+        val endHandle = Selection.AnchorInfo(ResolvedTextDirection.Ltr, expected.end, 1)
+        val expectedSelection = Selection(
+            start = startHandle,
+            end = endHandle,
+            handlesCrossed = expected.start > expected.end,
+        )
+        if (subject != expectedSelection) {
+            failWithActual(
+                Fact.simpleFact("expected equal selections"),
+                Fact.fact("expected", expectedSelection.textToString(content)),
+            )
+        }
+    }
+
+    override fun actualCustomStringRepresentation(): String =
+        subject?.textToString(content) ?: "null"
+}
+
+private fun Selection.textToString(content: String): String {
+    val collapsedSelection = start.offset == end.offset
+    val selectionString = content
+        .map { if (it == '\n') '\n' else '.' }
+        .joinToString("")
+        .let {
+            if (collapsedSelection) {
+                if (start.offset == content.length) {
+                    // edge case of selection being at end of text,
+                    // so append the marker instead of replacing
+                    "$it|"
+                } else if (content[start.offset] == '\n') {
+                    it.replaceRange(start.offset..start.offset, "|\n")
+                } else {
+                    it.replaceRange(start.offset..start.offset, "|")
+                }
+            } else {
+                val selectionRange = min until max
+                it.replaceRange(selectionRange, content.substring(selectionRange))
+            }
+        }
+    return """
+                |Collapsed = $collapsedSelection
+                |Selection = $this
+                |$selectionString
+            """.trimMargin().trim()
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextFieldSelectionTestUtils.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextFieldSelectionTestUtils.kt
new file mode 100644
index 0000000..3e5f50f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextFieldSelectionTestUtils.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.foundation.text.selection.gestures.util
+
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.getSelectedText
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+
+internal class TextFieldValueSubject constructor(
+    failureMetadata: FailureMetadata?,
+    private val subject: TextFieldValue,
+    private val textContent: String,
+) : Subject(failureMetadata, subject) {
+
+    companion object {
+        fun withContent(content: String): Factory<TextFieldValueSubject, TextFieldValue> =
+            Factory { failureMetadata, subject ->
+                TextFieldValueSubject(failureMetadata, subject, content)
+            }
+    }
+
+    fun hasSelection(textRange: TextRange) {
+        check("text").that(subject.text).isEqualTo(textContent)
+        if (subject.selection != textRange) {
+            failWithActual(
+                Fact.simpleFact("expected equal selections"),
+                Fact.fact("expected", TextFieldValue(textContent, textRange).customToString()),
+            )
+        }
+    }
+
+    override fun actualCustomStringRepresentation(): String = subject.customToString()
+
+    private fun TextFieldValue.customToString(): String {
+        val selectionString = text
+            .map { if (it == '\n') '\n' else '.' }
+            .joinToString("")
+            .let {
+                if (selection.collapsed) {
+                    if (selection.start == text.length) {
+                        // edge case of selection being at end of text,
+                        // so append the marker instead of replacing
+                        "$it|"
+                    } else if (it[selection.start] == '\n') {
+                        // edge case of selection being at end of a line,
+                        // so append the marker to the EOL and then add the new line
+                        it.replaceRange(selection.start..selection.start, "|\n")
+                    } else {
+                        it.replaceRange(selection.start..selection.start, "|")
+                    }
+                } else {
+                    it.replaceRange(selection.min until selection.max, getSelectedText().text)
+                }
+            }
+        return """
+                /Selection = ${selection.start} to ${selection.end}
+                /$selectionString
+            """.trimMargin(marginPrefix = "/").trim()
+    }
+}
+
+internal class TextFieldSelectionAsserter(
+    textContent: String,
+    private val rule: ComposeTestRule,
+    textToolbar: TextToolbar,
+    hapticFeedback: FakeHapticFeedback,
+    getActual: () -> TextFieldValue,
+) : SelectionAsserter<TextFieldValue>(textContent, rule, textToolbar, hapticFeedback, getActual) {
+    var selection: TextRange = 0.collapsed
+    var cursorHandleShown = false
+
+    override fun subAssert() {
+        Truth.assertAbout(TextFieldValueSubject.withContent(textContent))
+            .that(getActual())
+            .hasSelection(selection)
+        assertCursorHandleShown(cursorHandleShown)
+    }
+
+    private fun assertCursorHandleShown(shown: Boolean) {
+        val cursorHandle = rule.onNode(isSelectionHandle(Handle.Cursor))
+        if (shown) cursorHandle.assertExists() else cursorHandle.assertDoesNotExist()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt
new file mode 100644
index 0000000..bb2e1a5
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/util/TextSelectionTestUtils.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.foundation.text.selection.gestures.util
+
+import androidx.compose.foundation.MagnifierPositionInRoot
+import androidx.compose.foundation.isPlatformMagnifierSupported
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth
+
+private fun ComposeTestRule.assertSelectionHandlesShown(shown: Boolean) {
+    listOf(Handle.SelectionStart, Handle.SelectionEnd)
+        .map { onNode(isSelectionHandle(it)) }
+        .forEach { if (shown) it.assertExists() else it.assertDoesNotExist() }
+}
+
+private fun ComposeTestRule.assertMagnifierShown(shown: Boolean) {
+    if (!isPlatformMagnifierSupported()) return
+
+    val magShown = onAllNodes(SemanticsMatcher.keyIsDefined(MagnifierPositionInRoot))
+        .fetchSemanticsNodes(atLeastOneRootRequired = false)
+        .also { waitForIdle() }
+        .any {
+            it.config[MagnifierPositionInRoot]
+                .invoke()
+                .isSpecified
+        }
+
+    Truth.assertWithMessage("Magnifier should${if (shown) " " else " not "}be shown.")
+        .that(magShown)
+        .isEqualTo(shown)
+}
+
+private fun TextToolbar.assertShown(shown: Boolean = true) {
+    Truth.assertWithMessage("Text toolbar status was not as expected.")
+        .that(status)
+        .isEqualTo(if (shown) TextToolbarStatus.Shown else TextToolbarStatus.Hidden)
+}
+
+private fun FakeHapticFeedback.assertPerformedAtLeastThenClear(times: Int) {
+    Truth.assertThat(invocationCountMap[HapticFeedbackType.TextHandleMove] ?: 0).isAtLeast(times)
+    invocationCountMap.clear()
+}
+
+internal class FakeHapticFeedback : HapticFeedback {
+    val invocationCountMap = mutableMapOf<HapticFeedbackType, Int>().withDefault { 0 }
+    override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) {
+        invocationCountMap[hapticFeedbackType] = 1 + (invocationCountMap[hapticFeedbackType] ?: 0)
+    }
+}
+
+internal abstract class SelectionAsserter<S>(
+    var textContent: String,
+    private val rule: ComposeTestRule,
+    private val textToolbar: TextToolbar,
+    private val hapticFeedback: FakeHapticFeedback,
+    protected val getActual: () -> S,
+) {
+    var selectionHandlesShown = false
+    var textToolbarShown = false
+    var magnifierShown = false
+    var hapticsCount = 0
+
+    fun assert() {
+        subAssert()
+        rule.assertSelectionHandlesShown(selectionHandlesShown)
+        textToolbar.assertShown(textToolbarShown)
+        rule.assertMagnifierShown(magnifierShown)
+        // reset haptics every assert.
+        // with gestures, we could get multiple haptics per tested move
+        hapticFeedback.assertPerformedAtLeastThenClear(hapticsCount).also { hapticsCount = 0 }
+    }
+
+    protected abstract fun subAssert()
+}
+
+internal abstract class TextSelectionAsserter(
+    textContent: String,
+    rule: ComposeTestRule,
+    textToolbar: TextToolbar,
+    hapticFeedback: FakeHapticFeedback,
+    getActual: () -> Selection?,
+) : SelectionAsserter<Selection?>(textContent, rule, textToolbar, hapticFeedback, getActual) {
+    var selection: TextRange? = null
+}
+
+internal fun <S, T : SelectionAsserter<S>> T.applyAndAssert(block: T.() -> Unit) {
+    block()
+    assert()
+}
+
+internal fun TouchInjectionScope.longPress(offset: Offset) {
+    down(offset)
+    move(delayMillis = viewConfiguration.longPressTimeoutMillis + 10)
+}
+
+internal infix fun Int.to(other: Int): TextRange = TextRange(this, other)
+internal val Int.collapsed: TextRange get() = TextRange(this, this)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt
index d012665..c313f67 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt
@@ -405,6 +405,12 @@
             }
             pressKey(Key.Zero)
             expectedText("hello world0\n0hi")
+            withKeyDown(Key.CtrlLeft) {
+                pressKey(Key.DirectionUp)
+                pressKey(Key.DirectionUp)
+            }
+            pressKey(Key.Zero)
+            expectedText("0hello world0\n0hi")
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt
index 523cd75..688c2e8 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/HardwareKeyboardTest.kt
@@ -374,6 +374,10 @@
             Key.DirectionUp.downAndUp(META_CTRL_ON)
             Key.Zero.downAndUp()
             expectedText("hello world0\n0hi")
+            Key.DirectionUp.downAndUp(META_CTRL_ON)
+            Key.DirectionUp.downAndUp(META_CTRL_ON)
+            Key.Zero.downAndUp()
+            expectedText("0hello world0\n0hi")
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt
index b2182be..cdc626d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt
@@ -239,7 +239,7 @@
         assertThat(textFieldValue.value.selection.start).isEqualTo(0)
         assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
 
-        performHandleDrag(Handle.Cursor, false)
+        performHandleDrag(Handle.Cursor, toLeft = false, swipeFraction = 1f)
 
         assertThat(cursorPositions).isEqualTo(expectedCursorPositions)
         assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
@@ -251,7 +251,7 @@
         textField_extendsSelection(
             text = "text".repeat(5),
             visualTransformation = VisualTransformation.None,
-            expectedSelectionRanges = (1..20).map { TextRange(0, it) }.toList(),
+            expectedSelectionRanges = (11..19).map { TextRange(0, it) }.toList(),
             toLeft = false
         )
     }
@@ -262,7 +262,7 @@
         textField_extendsSelection(
             text = "text".repeat(5),
             visualTransformation = PasswordVisualTransformation(),
-            expectedSelectionRanges = (1..20).map { TextRange(0, it) }.toList(),
+            expectedSelectionRanges = (11..19).map { TextRange(0, it) }.toList(),
             toLeft = false
         )
     }
@@ -273,7 +273,7 @@
         textField_extendsSelection(
             text = "text".repeat(10),
             visualTransformation = ReducedVisualTransformation(),
-            expectedSelectionRanges = (1..40)
+            expectedSelectionRanges = (21..39)
                 .filter { it % 2 == 0 }
                 .map { TextRange(0, it) }
                 .toList(),
@@ -287,7 +287,7 @@
         textField_extendsSelection(
             text = "text".repeat(5),
             visualTransformation = VisualTransformation.None,
-            expectedSelectionRanges = (19 downTo 1).map { TextRange(it, 20) }.toList(),
+            expectedSelectionRanges = (9 downTo 1).map { TextRange(it, 20) }.toList(),
             toLeft = true
         )
     }
@@ -298,7 +298,7 @@
         textField_extendsSelection(
             text = "text".repeat(5),
             visualTransformation = PasswordVisualTransformation(),
-            expectedSelectionRanges = (19 downTo 1).map { TextRange(it, 20) }.toList(),
+            expectedSelectionRanges = (9 downTo 1).map { TextRange(it, 20) }.toList(),
             toLeft = true
         )
     }
@@ -308,7 +308,7 @@
         textField_extendsSelection(
             text = "text".repeat(10),
             visualTransformation = ReducedVisualTransformation(),
-            expectedSelectionRanges = (39 downTo 1)
+            expectedSelectionRanges = (19 downTo 1)
                 .filter { it % 2 == 0 }
                 .map { TextRange(it, 40) }
                 .toList(),
@@ -371,17 +371,19 @@
         }
     }
 
-    private fun performHandleDrag(handle: Handle, toLeft: Boolean) {
+    private fun performHandleDrag(handle: Handle, toLeft: Boolean, swipeFraction: Float = 0.5f) {
         val handleNode = rule.onNode(isSelectionHandle(handle))
         val fieldWidth = rule.onNodeWithTag(testTag)
             .fetchSemanticsNode()
-            .boundsInRoot.width.roundToInt()
+            .boundsInRoot.width
+
+        val swipeDistance = (fieldWidth * swipeFraction).roundToInt()
 
         handleNode.performTouchInput {
             if (toLeft) {
-                swipeLeft(startX = centerX, endX = left - fieldWidth, durationMillis = 1000)
+                swipeLeft(startX = centerX, endX = left - swipeDistance, durationMillis = 1000)
             } else {
-                swipeRight(startX = centerX, endX = right + fieldWidth, durationMillis = 1000)
+                swipeRight(startX = centerX, endX = right + swipeDistance, durationMillis = 1000)
             }
         }
     }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationCursorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationCursorTest.kt
new file mode 100644
index 0000000..f8299ae
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationCursorTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.foundation.textfield
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.Handle
+import androidx.compose.foundation.text.TEST_FONT_FAMILY
+import androidx.compose.foundation.text.selection.isSelectionHandle
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.TransformedText
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldVisualTransformationCursorTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    // small enough to fit in narrow screen in pre-submit,
+    // big enough that pointer movement can target a single char on center
+    private val fontSize = 15.sp
+    private val fontFamily = TEST_FONT_FAMILY
+    private val testTag = "testTag"
+    private val defaultText = "text"
+
+    private val zeroedOffsetMapping = object : OffsetMapping {
+        override fun originalToTransformed(offset: Int): Int = 0
+        override fun transformedToOriginal(offset: Int): Int = 0
+    }
+
+    private fun runTest(
+        text: String = defaultText,
+        visualTransformation: VisualTransformation = VisualTransformation.None,
+        block: (MutableState<TextFieldValue>) -> Unit
+    ) {
+        val textFieldValue = mutableStateOf(TextFieldValue(text))
+        rule.setContent {
+            BasicTextField(
+                value = textFieldValue.value,
+                onValueChange = { textFieldValue.value = it },
+                visualTransformation = visualTransformation,
+                textStyle = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .testTag(testTag),
+            )
+        }
+        block(textFieldValue)
+    }
+
+    @Test
+    fun longPressOnNonEmpty_showsCursor() = runTest {
+        rule.onNodeWithTag(testTag).performTouchInput { longClick() }
+        assertCursorHandleShown(shown = true)
+    }
+
+    @Test
+    fun longPressOnEmpty_doesNotShowCursor() = runTest(
+        text = "",
+    ) {
+        rule.onNodeWithTag(testTag).performTouchInput { longClick() }
+        assertCursorHandleShown(shown = false)
+    }
+
+    @Test
+    fun longPressOnEmptyAfterVisualTransformation_doesNotShowCursor() = runTest(
+        visualTransformation = {
+            TransformedText(AnnotatedString(text = ""), zeroedOffsetMapping)
+        },
+    ) {
+        rule.onNodeWithTag(testTag).performTouchInput { longClick() }
+        assertCursorHandleShown(shown = false)
+    }
+
+    @Test
+    fun longPressOnNonEmptyAfterVisualTransformation_showsCursor() = runTest(
+        text = "",
+        visualTransformation = {
+            TransformedText(AnnotatedString(text = defaultText), zeroedOffsetMapping)
+        },
+    ) {
+        rule.onNodeWithTag(testTag).performTouchInput { longClick() }
+        assertCursorHandleShown(shown = true)
+    }
+
+    private fun assertCursorHandleShown(shown: Boolean) {
+        val cursorHandle = rule.onNode(isSelectionHandle(Handle.Cursor))
+        if (shown) cursorHandle.assertExists() else cursorHandle.assertDoesNotExist()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/TouchMode.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/TouchMode.android.kt
index bd1aeb1..b421472 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/TouchMode.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/TouchMode.android.kt
@@ -16,4 +16,18 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+
+/**
+ * A boolean representing whether or not we are in touch mode or not.
+ *
+ * This is a temporary workaround and should be removed after proper mouse handling is settled
+ * (b/171402426).
+ *
+ * Until then, it is recommended to use [Modifier.pointerInput] to read whether an event comes
+ * from a touch or mouse source. A more global way of determining this mode will also be added
+ * as part of b/171402426.
+ */
+@Deprecated(message = "Avoid using if possible, see kdoc.", level = DeprecationLevel.WARNING)
 internal actual val isInTouchMode = true
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt
index 9bc6d85..9942759 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt
@@ -284,11 +284,19 @@
     }
 
     fun moveCursorPrevByParagraph() = applyIfNotEmpty {
-        setCursor(getParagraphStart())
+        var paragraphStart = text.findParagraphStart(selection.min)
+        if (paragraphStart == selection.min && paragraphStart != 0) {
+            paragraphStart = text.findParagraphStart(paragraphStart - 1)
+        }
+        setCursor(paragraphStart)
     }
 
     fun moveCursorNextByParagraph() = applyIfNotEmpty {
-        setCursor(getParagraphEnd())
+        var paragraphEnd = text.findParagraphEnd(selection.max)
+        if (paragraphEnd == selection.max && paragraphEnd != text.length) {
+            paragraphEnd = text.findParagraphEnd(paragraphEnd + 1)
+        }
+        setCursor(paragraphEnd)
     }
 
     fun moveCursorUpByLine() = applyIfNotEmpty(false) {
@@ -411,10 +419,6 @@
 
     private fun charOffset(offset: Int) = offset.coerceAtMost(text.length - 1)
 
-    private fun getParagraphStart() = text.findParagraphStart(selection.min)
-
-    private fun getParagraphEnd() = text.findParagraphEnd(selection.max)
-
     companion object {
         /**
          * Value returned by [getNextCharacterIndex] and [getPrecedingCharacterIndex] when no valid
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt
index 962b9d9..251cd9b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt
@@ -61,14 +61,15 @@
         }
     }
 
-    private val singleAxisItems: List<LazyGridItemInfo>
-        get() = lazyGridState.layoutInfo.visibleItemsInfo.fastFilter {
+    private fun singleAxisItems(): List<LazyGridItemInfo> {
+        return lazyGridState.layoutInfo.visibleItemsInfo.fastFilter {
             if (lazyGridState.layoutInfo.orientation == Orientation.Horizontal) {
                 it.row == 0
             } else {
                 it.column == 0
             }
         }
+    }
 
     override fun Density.calculateSnappingOffset(
         currentVelocity: Float
@@ -107,13 +108,14 @@
     }
 
     override fun Density.calculateSnapStepSize(): Float {
-        return if (singleAxisItems.isNotEmpty()) {
+        val items = singleAxisItems()
+        return if (items.isNotEmpty()) {
             val size = if (layoutInfo.orientation == Orientation.Vertical) {
-                singleAxisItems.fastSumBy { it.size.height }
+                items.fastSumBy { it.size.height }
             } else {
-                singleAxisItems.fastSumBy { it.size.width }
+                items.fastSumBy { it.size.width }
             }
-            size / singleAxisItems.size.toFloat()
+            size / items.size.toFloat()
         } else {
             0f
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index 5a25fa6..e5eb2d3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -32,7 +32,9 @@
 import androidx.compose.foundation.text.selection.TextFieldSelectionHandle
 import androidx.compose.foundation.text.selection.TextFieldSelectionManager
 import androidx.compose.foundation.text.selection.isSelectionHandleInVisibleBound
+import androidx.compose.foundation.text.selection.selectionGestureInput
 import androidx.compose.foundation.text.selection.textFieldMagnifier
+import androidx.compose.foundation.text.selection.updateSelectionTouchMode
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.MutableState
@@ -336,42 +338,35 @@
         }
     }
 
-    val pointerModifier = if (isInTouchMode) {
-        val selectionModifier =
-            Modifier.longPressDragGestureFilter(manager.touchSelectionObserver, enabled)
-        Modifier
-            .tapPressTextFieldModifier(interactionSource, enabled) { offset ->
-                tapToFocus(state, focusRequester, !readOnly)
-                if (state.hasFocus) {
-                    if (state.handleState != HandleState.Selection) {
-                        state.layoutResult?.let { layoutResult ->
-                            TextFieldDelegate.setCursorOffset(
-                                offset,
-                                layoutResult,
-                                state.processor,
-                                offsetMapping,
-                                state.onValueChange
-                            )
-                            // Won't enter cursor state when text is empty.
-                            if (state.textDelegate.text.isNotEmpty()) {
-                                state.handleState = HandleState.Cursor
-                            }
+    val pointerModifier = Modifier
+        .updateSelectionTouchMode { state.isInTouchMode = it }
+        .tapPressTextFieldModifier(interactionSource, enabled) { offset ->
+            tapToFocus(state, focusRequester, !readOnly)
+            if (state.hasFocus) {
+                if (state.handleState != HandleState.Selection) {
+                    state.layoutResult?.let { layoutResult ->
+                        TextFieldDelegate.setCursorOffset(
+                            offset,
+                            layoutResult,
+                            state.processor,
+                            offsetMapping,
+                            state.onValueChange
+                        )
+                        // Won't enter cursor state when text is empty.
+                        if (state.textDelegate.text.isNotEmpty()) {
+                            state.handleState = HandleState.Cursor
                         }
-                    } else {
-                        manager.deselect(offset)
                     }
+                } else {
+                    manager.deselect(offset)
                 }
             }
-            .then(selectionModifier)
-            .pointerHoverIcon(textPointerIcon)
-    } else {
-        Modifier
-            .mouseDragGestureDetector(
-                observer = manager.mouseSelectionObserver,
-                enabled = enabled
-            )
-            .pointerHoverIcon(textPointerIcon)
-    }
+        }
+        .selectionGestureInput(
+            mouseSelectionObserver = manager.mouseSelectionObserver,
+            textDragObserver = manager.touchSelectionObserver,
+        )
+        .pointerHoverIcon(textPointerIcon)
 
     val drawModifier = Modifier.drawBehind {
         state.layoutResult?.let { layoutResult ->
@@ -400,6 +395,7 @@
                     manager.isSelectionHandleInVisibleBound(isStartHandle = true)
                 state.showSelectionHandleEnd =
                     manager.isSelectionHandleInVisibleBound(isStartHandle = false)
+                state.showCursorHandle = value.selection.collapsed
             } else if (state.handleState == HandleState.Cursor) {
                 state.showCursorHandle =
                     manager.isSelectionHandleInVisibleBound(isStartHandle = true)
@@ -589,7 +585,7 @@
             state.layoutResult?.decorationBoxCoordinates = it
         }
 
-    val showHandleAndMagnifier = enabled && state.hasFocus && isInTouchMode
+    val showHandleAndMagnifier = enabled && state.hasFocus && state.isInTouchMode
     val magnifierModifier = if (showHandleAndMagnifier) {
         Modifier.textFieldMagnifier(manager)
     } else {
@@ -610,10 +606,10 @@
                     maxLines = maxLines
                 )
                 .textFieldScroll(
-                    scrollerPosition,
-                    value,
-                    visualTransformation,
-                    { state.layoutResult }
+                    scrollerPosition = scrollerPosition,
+                    textFieldValue = value,
+                    visualTransformation = visualTransformation,
+                    textLayoutResultProvider = { state.layoutResult },
                 )
                 .then(cursorModifier)
                 .then(drawModifier)
@@ -683,11 +679,8 @@
                         state.layoutCoordinates!!.isAttached &&
                         showHandleAndMagnifier
                 )
-                if (
-                    state.handleState == HandleState.Cursor &&
-                    !readOnly &&
-                    showHandleAndMagnifier
-                ) {
+
+                if (!readOnly && showHandleAndMagnifier) {
                     TextFieldCursorHandle(manager = manager)
                 }
             }
@@ -876,6 +869,8 @@
     var isLayoutResultStale: Boolean = true
         private set
 
+    var isInTouchMode: Boolean by mutableStateOf(true)
+
     private val keyboardActionRunner: KeyboardActionRunner = KeyboardActionRunner()
 
     /**
@@ -952,7 +947,6 @@
     }
 }
 
-@OptIn(InternalFoundationTextApi::class)
 private fun notifyTextInputServiceOnFocusChange(
     textInputService: TextInputService,
     state: TextFieldState,
@@ -1077,7 +1071,7 @@
 
 @Composable
 internal fun TextFieldCursorHandle(manager: TextFieldSelectionManager) {
-    if (manager.state?.showCursorHandle == true) {
+    if (manager.state?.showCursorHandle == true && manager.transformedText?.isNotEmpty() == true) {
         val observer = remember(manager) { manager.cursorDragObserver() }
         val position = manager.getCursorPosition(LocalDensity.current)
         CursorHandle(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/StringHelpers.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/StringHelpers.kt
index 26eb20b..0cf888e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/StringHelpers.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/StringHelpers.kt
@@ -33,7 +33,7 @@
 internal expect fun String.findFollowingBreak(index: Int): Int
 
 internal fun CharSequence.findParagraphStart(startIndex: Int): Int {
-    for (index in startIndex - 1 downTo 1) {
+    for (index in startIndex downTo 1) {
         if (this[index - 1] == '\n') {
             return index
         }
@@ -42,7 +42,7 @@
 }
 
 internal fun CharSequence.findParagraphEnd(startIndex: Int): Int {
-    for (index in startIndex + 1 until this.length) {
+    for (index in startIndex until this.length) {
         if (this[index] == '\n') {
             return index
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldGestureModifiers.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldGestureModifiers.kt
index ebadd59..fc713f4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldGestureModifiers.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldGestureModifiers.kt
@@ -18,24 +18,11 @@
 
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.text.selection.MouseSelectionObserver
-import androidx.compose.foundation.text.selection.mouseSelectionDetector
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.input.pointer.pointerInput
-
-// Touch selection
-internal fun Modifier.longPressDragGestureFilter(
-    observer: TextDragObserver,
-    enabled: Boolean
-) = if (enabled) {
-    this.pointerInput(observer) { detectDragGesturesAfterLongPressWithObserver(observer) }
-} else {
-    this
-}
 
 // Focus modifiers
 internal fun Modifier.textFieldFocusModifier(
@@ -47,11 +34,3 @@
     .focusRequester(focusRequester)
     .onFocusChanged(onFocusChanged)
     .focusable(interactionSource = interactionSource, enabled = enabled)
-
-// Mouse
-internal fun Modifier.mouseDragGestureDetector(
-    observer: MouseSelectionObserver,
-    enabled: Boolean
-) = if (enabled) Modifier.pointerInput(observer) {
-    mouseSelectionDetector(observer)
-} else this
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
index 7a9c5c2..34a9f2c9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
@@ -17,17 +17,14 @@
 package androidx.compose.foundation.text.modifiers
 
 import androidx.compose.foundation.text.TextDragObserver
-import androidx.compose.foundation.text.detectDragGesturesAfterLongPressWithObserver
-import androidx.compose.foundation.text.isInTouchMode
 import androidx.compose.foundation.text.selection.MouseSelectionObserver
 import androidx.compose.foundation.text.selection.MultiWidgetSelectionDelegate
 import androidx.compose.foundation.text.selection.Selectable
 import androidx.compose.foundation.text.selection.SelectionAdjustment
 import androidx.compose.foundation.text.selection.SelectionRegistrar
 import androidx.compose.foundation.text.selection.hasSelection
-import androidx.compose.foundation.text.selection.mouseSelectionDetector
+import androidx.compose.foundation.text.selection.selectionGestureInput
 import androidx.compose.foundation.text.textPointerHoverIcon
-import androidx.compose.foundation.text.textPointerIcon
 import androidx.compose.runtime.RememberObserver
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -35,8 +32,6 @@
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.drawscope.clipRect
-import androidx.compose.ui.input.pointer.pointerHoverIcon
-import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.style.TextOverflow
@@ -81,12 +76,12 @@
     private var selectable: Selectable? = null
     private val selectableId = selectionRegistrar.nextSelectableId()
 
-    val modifier: Modifier = selectionRegistrar.makeSelectionModifier(
-        selectableId = selectableId,
-        layoutCoordinates = { params.layoutCoordinates },
-        textLayoutResult = { params.textLayoutResult },
-        isInTouchMode = isInTouchMode
-    ).textPointerHoverIcon(selectionRegistrar)
+    val modifier: Modifier = selectionRegistrar
+        .makeSelectionModifier(
+            selectableId = selectableId,
+            layoutCoordinates = { params.layoutCoordinates },
+        )
+        .textPointerHoverIcon(selectionRegistrar)
 
     override fun onRemembered() {
         selectable = selectionRegistrar.subscribe(
@@ -161,10 +156,7 @@
 private fun SelectionRegistrar.makeSelectionModifier(
     selectableId: Long,
     layoutCoordinates: () -> LayoutCoordinates?,
-    textLayoutResult: () -> TextLayoutResult?,
-    isInTouchMode: Boolean
 ): Modifier {
-    return if (isInTouchMode) {
         val longPressDragObserver = object : TextDragObserver {
             /**
              * The beginning position of the drag gesture. Every time a new drag gesture starts, it wil be
@@ -190,17 +182,12 @@
                 layoutCoordinates()?.let {
                     if (!it.isAttached) return
 
-                    if (textLayoutResult().outOfBoundary(startPoint, startPoint)) {
-                        notifySelectionUpdateSelectAll(
-                            selectableId = selectableId
-                        )
-                    } else {
-                        notifySelectionUpdateStart(
-                            layoutCoordinates = it,
-                            startPosition = startPoint,
-                            adjustment = SelectionAdjustment.Word
-                        )
-                    }
+                    notifySelectionUpdateStart(
+                        layoutCoordinates = it,
+                        startPosition = startPoint,
+                        adjustment = SelectionAdjustment.Word,
+                        isInTouchMode = true
+                    )
 
                     lastPosition = startPoint
                 }
@@ -219,23 +206,22 @@
                     dragTotalDistance += delta
                     val newPosition = lastPosition + dragTotalDistance
 
-                    if (!textLayoutResult().outOfBoundary(lastPosition, newPosition)) {
-                        // Notice that only the end position needs to be updated here.
-                        // Start position is left unchanged. This is typically important when
-                        // long-press is using SelectionAdjustment.WORD or
-                        // SelectionAdjustment.PARAGRAPH that updates the start handle position from
-                        // the dragBeginPosition.
-                        val consumed = notifySelectionUpdate(
-                            layoutCoordinates = it,
-                            previousPosition = lastPosition,
-                            newPosition = newPosition,
-                            isStartHandle = false,
-                            adjustment = SelectionAdjustment.CharacterWithWordAccelerate
-                        )
-                        if (consumed) {
-                            lastPosition = newPosition
-                            dragTotalDistance = Offset.Zero
-                        }
+                    // Notice that only the end position needs to be updated here.
+                    // Start position is left unchanged. This is typically important when
+                    // long-press is using SelectionAdjustment.WORD or
+                    // SelectionAdjustment.PARAGRAPH that updates the start handle position from
+                    // the dragBeginPosition.
+                    val consumed = notifySelectionUpdate(
+                        layoutCoordinates = it,
+                        previousPosition = lastPosition,
+                        newPosition = newPosition,
+                        isStartHandle = false,
+                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
+                        isInTouchMode = true
+                    )
+                    if (consumed) {
+                        lastPosition = newPosition
+                        dragTotalDistance = Offset.Zero
                     }
                 }
             }
@@ -252,12 +238,7 @@
                 }
             }
         }
-        Modifier.pointerInput(longPressDragObserver) {
-            detectDragGesturesAfterLongPressWithObserver(
-                longPressDragObserver
-            )
-        }
-    } else {
+
         val mouseSelectionObserver = object : MouseSelectionObserver {
             var lastPosition = Offset.Zero
 
@@ -269,7 +250,8 @@
                         newPosition = downPosition,
                         previousPosition = lastPosition,
                         isStartHandle = false,
-                        adjustment = SelectionAdjustment.None
+                        adjustment = SelectionAdjustment.None,
+                        isInTouchMode = false
                     )
                     if (consumed) {
                         lastPosition = downPosition
@@ -289,7 +271,8 @@
                         newPosition = dragPosition,
                         previousPosition = lastPosition,
                         isStartHandle = false,
-                        adjustment = SelectionAdjustment.None
+                        adjustment = SelectionAdjustment.None,
+                        isInTouchMode = false
                     )
 
                     if (consumed) {
@@ -309,7 +292,8 @@
                     notifySelectionUpdateStart(
                         layoutCoordinates = it,
                         startPosition = downPosition,
-                        adjustment = adjustment
+                        adjustment = adjustment,
+                        isInTouchMode = false
                     )
 
                     lastPosition = downPosition
@@ -332,7 +316,8 @@
                         previousPosition = lastPosition,
                         newPosition = dragPosition,
                         isStartHandle = false,
-                        adjustment = adjustment
+                        adjustment = adjustment,
+                        isInTouchMode = false
                     )
                     if (consumed) {
                         lastPosition = dragPosition
@@ -340,20 +325,11 @@
                 }
                 return true
             }
+
+            override fun onDragDone() {
+                notifySelectionUpdateEnd()
+            }
         }
-        Modifier.pointerInput(mouseSelectionObserver) {
-            mouseSelectionDetector(mouseSelectionObserver)
-        }.pointerHoverIcon(textPointerIcon)
-    }
-}
 
-private fun TextLayoutResult?.outOfBoundary(start: Offset, end: Offset): Boolean {
-    this ?: return false
-
-    val lastOffset = layoutInput.text.text.length
-    val rawStartOffset = getOffsetForPosition(start)
-    val rawEndOffset = getOffsetForPosition(end)
-
-    return rawStartOffset >= lastOffset - 1 && rawEndOffset >= lastOffset - 1 ||
-        rawStartOffset < 0 && rawEndOffset < 0
+    return Modifier.selectionGestureInput(mouseSelectionObserver, longPressDragObserver)
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
index 4d59f31..3791520 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
@@ -22,6 +22,7 @@
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.unit.toSize
 import kotlin.math.max
 
 internal class MultiWidgetSelectionDelegate(
@@ -199,6 +200,7 @@
     isStartHandle: Boolean = true
 ): Pair<Selection?, Boolean> {
 
+    val currentHandlePosition = if (isStartHandle) startHandlePosition else endHandlePosition
     val bounds = Rect(
         0.0f,
         0.0f,
@@ -213,8 +215,20 @@
         return Pair(null, false)
     }
 
+    // check if going horizontally on the same line/Text and getting out of horizontal bounds.
+    // if so reject the change.
+    if (isMovingOutOfBoundsOnTheSameLineInCurrentText(
+            previousHandlePosition,
+            currentHandlePosition,
+            textLayoutResult
+        )
+    ) {
+        return Pair(previousSelection, false)
+    }
+
     val rawStartHandleOffset = getOffsetForPosition(textLayoutResult, bounds, startHandlePosition)
     val rawEndHandleOffset = getOffsetForPosition(textLayoutResult, bounds, endHandlePosition)
+    val rawCurrentHandleOffset = if (isStartHandle) rawStartHandleOffset else rawEndHandleOffset
     val rawPreviousHandleOffset = previousHandlePosition?.let {
         getOffsetForPosition(textLayoutResult, bounds, it)
     } ?: -1
@@ -226,9 +240,26 @@
         isStartHandle = isStartHandle,
         previousSelectionRange = previousSelection?.toTextRange()
     )
+
+    // Edge case where it isn't clear whether our selection should be reversed,
+    // Our offsets (previous, start, and end) are the same.
+    // If our current position is in bounds,
+    // but our previous position out of bounds and forwards,
+    // then we want to make sure that our selection is reversed
+    // because it is a backwards selection
+    val shouldReverseRange = rawCurrentHandleOffset == rawPreviousHandleOffset &&
+        rawStartHandleOffset == rawEndHandleOffset &&
+        previousHandlePosition != null &&
+        SelectionMode.Vertical.compare(currentHandlePosition, bounds) == 0 &&
+        SelectionMode.Vertical.compare(previousHandlePosition, bounds) > 0
+
+    val selectionRange = adjustedTextRange.run {
+        if (shouldReverseRange) TextRange(max, min) else this
+    }
+
     val newSelection = getAssembledSelectionInfo(
-        newSelectionRange = adjustedTextRange,
-        handlesCrossed = adjustedTextRange.reversed,
+        newSelectionRange = selectionRange,
+        handlesCrossed = selectionRange.reversed,
         selectableId = selectableId,
         textLayoutResult = textLayoutResult
     )
@@ -239,16 +270,47 @@
     // offset has changed.(Usually this happen because of adjustment like SelectionAdjustment.Word)
     // In this case we also consider the movement being consumed.
     val selectionUpdated = newSelection != previousSelection
-    val handleUpdated = if (isStartHandle) {
-        rawStartHandleOffset != rawPreviousHandleOffset
-    } else {
-        rawEndHandleOffset != rawPreviousHandleOffset
-    }
+    val handleUpdated = rawCurrentHandleOffset != rawPreviousHandleOffset
     val consumed = handleUpdated || selectionUpdated
     return Pair(newSelection, consumed)
 }
 
-internal fun getOffsetForPosition(
+/**
+ * Returns true if the handle is moving horizontally on the same line/text and getting out of
+ * horizontal bounds on left or right.
+ */
+private fun isMovingOutOfBoundsOnTheSameLineInCurrentText(
+    previousHandlePosition: Offset?,
+    currentHandlePosition: Offset,
+    textLayoutResult: TextLayoutResult
+): Boolean {
+    if (previousHandlePosition == null) {
+        return false
+    }
+
+    val bounds = Rect(Offset.Zero, textLayoutResult.size.toSize())
+    if (
+        !bounds.containsInclusive(previousHandlePosition) ||
+        !bounds.containsInclusive(currentHandlePosition)
+    ) {
+        return false
+    }
+
+    val previousHandleLine = textLayoutResult.getLineForVerticalPosition(previousHandlePosition.y)
+    val currentHandleLine = textLayoutResult.getLineForVerticalPosition(currentHandlePosition.y)
+    if (currentHandleLine != previousHandleLine) return false
+
+    val lineRight = textLayoutResult.getLineRight(currentHandleLine)
+    val lineLeft = textLayoutResult.getLineLeft(currentHandleLine)
+
+    // When x is equal to the line sides,
+    // it still can trigger a selection change that we want to avoid
+    // (selecting the whitespace at the ends),
+    // so return true for those as well.
+    return currentHandlePosition.x <= lineLeft || lineRight <= currentHandlePosition.x
+}
+
+private fun getOffsetForPosition(
     textLayoutResult: TextLayoutResult,
     bounds: Rect,
     position: Offset
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt
index 4ec4b4c..9efe95d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt
@@ -70,14 +70,17 @@
     fun merge(other: Selection?): Selection {
         if (other == null) return this
 
-        var selection = this
-        selection = if (handlesCrossed) {
-            selection.copy(start = other.start)
+        val selection = this
+
+        return if (handlesCrossed || other.handlesCrossed) {
+            Selection(
+                start = if (other.handlesCrossed) other.start else other.end,
+                end = if (handlesCrossed) end else start,
+                handlesCrossed = true
+            )
         } else {
             selection.copy(end = other.end)
         }
-
-        return selection
     }
 
     /**
@@ -86,4 +89,4 @@
     fun toTextRange(): TextRange {
         return TextRange(start.offset, end.offset)
     }
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt
index 17eb2e4..833fa7b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt
@@ -40,6 +40,7 @@
      * @param previousHandleOffset the previous offset of the moving handle. When isStartHandle is
      * true, it's the previous offset of the start handle before the movement, and vice versa.
      * When there isn't a valid previousHandleOffset, previousHandleOffset should be -1.
+     * Necessary for adjustment to tell whether selection is expanding or shrinking.
      * @param isStartHandle whether the moving handle is the start handle.
      * @param previousSelectionRange the previous selection range, or the selection range to be
      * updated.
@@ -155,7 +156,7 @@
             if (textLayoutResult.layoutInput.text.isEmpty()) {
                 return TextRange.Zero
             }
-            val maxOffset = textLayoutResult.layoutInput.text.lastIndex
+            val maxOffset = textLayoutResult.layoutInput.text.length
             val startBoundary = boundaryFun(newRawSelection.start.coerceIn(0, maxOffset))
             val endBoundary = boundaryFun(newRawSelection.end.coerceIn(0, maxOffset))
 
@@ -202,19 +203,19 @@
                         newRawSelectionRange = newRawSelectionRange,
                         previousHandleOffset = previousHandleOffset,
                         isStartHandle = isStartHandle,
-                        previousSelectionRange = previousSelectionRange
+                        previousSelectionRange = null
                     )
                 }
 
-                // The new selection is collapsed, ensure at least one char is selected.
-                if (newRawSelectionRange.collapsed) {
-                    return ensureAtLeastOneChar(
-                        text = textLayoutResult.layoutInput.text.text,
-                        offset = newRawSelectionRange.start,
-                        lastOffset = textLayoutResult.layoutInput.text.lastIndex,
-                        isStartHandle = isStartHandle,
-                        previousHandlesCrossed = previousSelectionRange.reversed
-                    )
+                // if previous is collapsed, allow the current to continue to be collapsed.
+                // Otherwise, starting a selection may have a collapsed selection,
+                // but moving even a pixel will result in a different selection
+                // because the following code will ensure at least one character is selected.
+                if (
+                    previousSelectionRange.collapsed &&
+                    newRawSelectionRange == previousSelectionRange
+                ) {
+                    return previousSelectionRange
                 }
 
                 val start: Int
@@ -242,6 +243,7 @@
                         isReversed = newRawSelectionRange.reversed
                     )
                 }
+
                 return TextRange(start, end)
             }
 
@@ -293,9 +295,7 @@
 
                 // Check if the start or end selection boundary is expanding. If it's shrinking,
                 // use character based selection.
-                val isExpanding =
-                    isExpanding(newRawOffset, previousRawOffset, isStart, isReversed)
-                if (!isExpanding) {
+                if (!isExpanding(newRawOffset, previousRawOffset, isStart, isReversed)) {
                     return newRawOffset
                 }
 
@@ -356,25 +356,16 @@
                     return start
                 }
 
-                val threshold = (start + end) / 2
                 return if (isStart xor isReversed) {
                     // In this branch when:
                     // 1. selection is updating the start offset, and selection is not reversed.
                     // 2. selection is updating the end offset, and selection is reversed.
-                    if (newRawOffset <= threshold) {
-                        start
-                    } else {
-                        end
-                    }
+                    if (newRawOffset <= end) start else end
                 } else {
                     // In this branch when:
                     // 1. selection is updating the end offset, and selection is not reversed.
                     // 2. selection is updating the start offset, and selection is reversed.
-                    if (newRawOffset >= threshold) {
-                        end
-                    } else {
-                        start
-                    }
+                    if (newRawOffset >= start) end else start
                 }
             }
 
@@ -437,18 +428,20 @@
     // When offset is at the boundary, the handle that is not dragged should be at [offset]. Here
     // the other handle's position is computed accordingly.
     if (offset == 0) {
+        val followingBreak = text.findFollowingBreak(0)
         return if (isStartHandle) {
-            TextRange(text.findFollowingBreak(0), 0)
+            TextRange(followingBreak, 0)
         } else {
-            TextRange(0, text.findFollowingBreak(0))
+            TextRange(0, followingBreak)
         }
     }
 
     if (offset == lastOffset) {
+        val precedingBreak = text.findPrecedingBreak(lastOffset)
         return if (isStartHandle) {
-            TextRange(text.findPrecedingBreak(lastOffset), lastOffset)
+            TextRange(precedingBreak, lastOffset)
         } else {
-            TextRange(lastOffset, text.findPrecedingBreak(lastOffset))
+            TextRange(lastOffset, precedingBreak)
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
index e2e8484..059d464 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.text.ContextMenuArea
 import androidx.compose.foundation.text.detectDownAndDragGesturesWithObserver
-import androidx.compose.foundation.text.isInTouchMode
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -90,7 +89,6 @@
     manager.textToolbar = LocalTextToolbar.current
     manager.onSelectionChange = onSelectionChange
     manager.selection = selection
-    manager.touchMode = isInTouchMode
 
     ContextMenuArea(manager) {
         CompositionLocalProvider(LocalSelectionRegistrar provides registrarImpl) {
@@ -98,7 +96,7 @@
             // cross-composable selection.
             SimpleLayout(modifier = modifier.then(manager.modifier)) {
                 children()
-                if (isInTouchMode && manager.hasFocus) {
+                if (manager.isInTouchMode && manager.hasFocus && manager.isNonEmptySelection()) {
                     manager.selection?.let {
                         listOf(true, false).fastForEach { isStartHandle ->
                             val observer = remember(isStartHandle) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionGestures.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionGestures.kt
new file mode 100644
index 0000000..2364270
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionGestures.kt
@@ -0,0 +1,239 @@
+/*
+ * 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.foundation.text.selection
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
+import androidx.compose.foundation.gestures.drag
+import androidx.compose.foundation.text.TextDragObserver
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.isPrimaryPressed
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.util.fastAll
+import androidx.compose.ui.util.fastForEach
+import kotlinx.coroutines.CancellationException
+
+/**
+ * Without shift it starts the new selection from scratch.
+ * With shift it expands/shrinks existing selection.
+ * A click sets the start and end of the selection,
+ * but shift click only sets the end of the selection.
+ */
+internal interface MouseSelectionObserver {
+    /**
+     * Invoked on click (with shift).
+     * @return if event will be consumed
+     */
+    fun onExtend(downPosition: Offset): Boolean
+
+    /**
+     * Invoked on drag after shift click.
+     * @return if event will be consumed
+     */
+    fun onExtendDrag(dragPosition: Offset): Boolean
+
+    /**
+     * Invoked on first click (without shift).
+     * @return if event will be consumed
+     */
+    // if returns true event will be consumed
+    fun onStart(downPosition: Offset, adjustment: SelectionAdjustment): Boolean
+
+    /**
+     * Invoked when dragging (without shift).
+     * @return if event will be consumed
+     */
+    fun onDrag(dragPosition: Offset, adjustment: SelectionAdjustment): Boolean
+
+    /**
+     * Invoked when finishing a selection mouse gesture.
+     */
+    fun onDragDone()
+}
+
+// TODO(b/281584353) This is a stand in for updating the state in some global way.
+//  For example, any touch/click in compose should change touch mode.
+//  This only updates when the pointer is within the bounds of what it is modifying,
+//  thus it is a placeholder until the other functionality is implemented.
+private const val STATIC_KEY = 867_5309 // unique key to not clash with other global pointer inputs
+internal fun Modifier.updateSelectionTouchMode(
+    updateTouchMode: (Boolean) -> Unit
+): Modifier = this.pointerInput(STATIC_KEY) {
+    awaitPointerEventScope {
+        while (true) {
+            val event = awaitPointerEvent(PointerEventPass.Initial)
+            updateTouchMode(!event.isPrecisePointer)
+        }
+    }
+}
+
+internal fun Modifier.selectionGestureInput(
+    mouseSelectionObserver: MouseSelectionObserver,
+    textDragObserver: TextDragObserver,
+) = this.pointerInput(mouseSelectionObserver, textDragObserver) {
+    val clicksCounter = ClicksCounter(viewConfiguration)
+    awaitEachGesture {
+        val down = awaitDown()
+        if (
+            down.isPrecisePointer &&
+            down.buttons.isPrimaryPressed &&
+            down.changes.fastAll { !it.isConsumed }
+        ) {
+            mouseSelection(mouseSelectionObserver, clicksCounter, down)
+        } else if (!down.isPrecisePointer) {
+            touchSelection(textDragObserver, down)
+        }
+    }
+}
+
+private suspend fun AwaitPointerEventScope.touchSelection(
+    observer: TextDragObserver,
+    down: PointerEvent
+) {
+    try {
+        val firstDown = down.changes.first()
+        val drag = awaitLongPressOrCancellation(firstDown.id)
+        if (drag != null && distanceIsTolerable(firstDown.position, drag.position)) {
+            observer.onStart(drag.position)
+            if (
+                drag(drag.id) {
+                    observer.onDrag(it.positionChange())
+                    it.consume()
+                }
+            ) {
+                // consume up if we quit drag gracefully with the up
+                currentEvent.changes.fastForEach {
+                    if (it.changedToUp()) it.consume()
+                }
+                observer.onStop()
+            } else {
+                observer.onCancel()
+            }
+        }
+    } catch (c: CancellationException) {
+        observer.onCancel()
+        throw c
+    }
+}
+
+private suspend fun AwaitPointerEventScope.mouseSelection(
+    observer: MouseSelectionObserver,
+    clicksCounter: ClicksCounter,
+    down: PointerEvent
+) {
+    clicksCounter.update(down)
+    val downChange = down.changes[0]
+    if (down.isShiftPressed) {
+        val started = observer.onExtend(downChange.position)
+        if (started) {
+            val shouldConsumeUp = drag(downChange.id) {
+                if (observer.onExtendDrag(it.position)) {
+                    it.consume()
+                }
+            }
+
+            if (shouldConsumeUp) {
+                currentEvent.changes.fastForEach {
+                    if (it.changedToUp()) it.consume()
+                }
+            }
+
+            observer.onDragDone()
+        }
+    } else {
+        val selectionMode = when (clicksCounter.clicks) {
+            // TODO(b/281585400) switch 1 to Character adjustment.
+            //     This will result in multi text bugs,
+            //     like a blank line selection resulting in a single char being selected.
+            1 -> SelectionAdjustment.None
+            2 -> SelectionAdjustment.Word
+            else -> SelectionAdjustment.Paragraph
+        }
+        val started = observer.onStart(downChange.position, selectionMode)
+        if (started) {
+            val shouldConsumeUp = drag(downChange.id) {
+                if (observer.onDrag(it.position, selectionMode)) {
+                    it.consume()
+                }
+            }
+
+            if (shouldConsumeUp) {
+                currentEvent.changes.fastForEach {
+                    if (it.changedToUp()) it.consume()
+                }
+            }
+
+            observer.onDragDone()
+        }
+    }
+}
+
+internal const val ClicksSlop = 100.0
+
+private class ClicksCounter(
+    private val viewConfiguration: ViewConfiguration
+) {
+    var clicks = 0
+    var prevClick: PointerInputChange? = null
+
+    fun update(event: PointerEvent) {
+        val currentPrevClick = prevClick
+        val newClick = event.changes[0]
+        if (currentPrevClick != null &&
+            timeIsTolerable(currentPrevClick, newClick) &&
+            positionIsTolerable(currentPrevClick, newClick)
+        ) {
+            clicks += 1
+        } else {
+            clicks = 1
+        }
+        prevClick = newClick
+    }
+
+    fun timeIsTolerable(prevClick: PointerInputChange, newClick: PointerInputChange): Boolean =
+        newClick.uptimeMillis - prevClick.uptimeMillis < viewConfiguration.doubleTapTimeoutMillis
+
+    fun positionIsTolerable(prevClick: PointerInputChange, newClick: PointerInputChange): Boolean =
+        (newClick.position - prevClick.position).getDistance() < ClicksSlop
+}
+
+private suspend fun AwaitPointerEventScope.awaitDown(): PointerEvent {
+    var event: PointerEvent
+    do {
+        event = awaitPointerEvent(PointerEventPass.Main)
+    } while (!event.changes.fastAll { it.changedToDownIgnoreConsumed() })
+    return event
+}
+
+private fun AwaitPointerEventScope.distanceIsTolerable(offset1: Offset, offset2: Offset): Boolean =
+    (offset1 - offset2).getDistance() < viewConfiguration.touchSlop
+
+// TODO(b/281585410) this does not support touch pads as they have a pointer type of Touch
+//             Supporting that will require public api changes
+//             since the necessary info is in the ui module.
+private val PointerEvent.isPrecisePointer
+    get() = this.changes.fastAll { it.type == PointerType.Mouse }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 7e1069b..bb0671b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:Suppress("DEPRECATION")
-
 package androidx.compose.foundation.text.selection
 
 import androidx.compose.foundation.fastFold
@@ -51,6 +49,7 @@
 import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastForEach
 import kotlin.math.absoluteValue
 import kotlin.math.max
 import kotlin.math.min
@@ -77,7 +76,15 @@
     /**
      * Is touch mode active
      */
-    var touchMode: Boolean = true
+    private val _isInTouchMode = mutableStateOf(true)
+    var isInTouchMode: Boolean
+        get() = _isInTouchMode.value
+        set(value) {
+            if (_isInTouchMode.value != value) {
+                _isInTouchMode.value = value
+                if (value && showToolbar) showSelectionToolbar() else hideSelectionToolbar()
+            }
+        }
 
     /**
      * The manager will invoke this every time it comes to the conclusion that the selection should
@@ -126,6 +133,7 @@
                 hasFocus = focusState.isFocused
             }
             .focusable()
+            .updateSelectionTouchMode { isInTouchMode = it }
             .onKeyEvent {
                 if (isCopyKeyEvent(it)) {
                     copy()
@@ -200,7 +208,8 @@
     var currentDragPosition: Offset? by mutableStateOf(null)
         private set
 
-    private val shouldShowMagnifier get() = draggingHandle != null
+    private val shouldShowMagnifier
+        get() = draggingHandle != null && isInTouchMode && isNonEmptySelection()
 
     init {
         selectionRegistrar.onPositionChangeCallback = { selectableId ->
@@ -214,13 +223,14 @@
         }
 
         selectionRegistrar.onSelectionUpdateStartCallback =
-            { layoutCoordinates, position, selectionMode ->
+            { isInTouchMode, layoutCoordinates, position, selectionMode ->
                 val positionInContainer = convertToContainerCoordinates(
                     layoutCoordinates,
                     position
                 )
 
                 if (positionInContainer != null) {
+                    this.isInTouchMode = isInTouchMode
                     startSelection(
                         position = positionInContainer,
                         isStartHandle = false,
@@ -228,12 +238,12 @@
                     )
 
                     focusRequester.requestFocus()
-                    hideSelectionToolbar()
+                    showToolbar = false
                 }
             }
 
         selectionRegistrar.onSelectionUpdateSelectAll =
-            { selectableId ->
+            { isInTouchMode, selectableId ->
                 val (newSelection, newSubselection) = selectAll(
                     selectableId = selectableId,
                     previousSelection = selection,
@@ -243,17 +253,24 @@
                     onSelectionChange(newSelection)
                 }
 
+                this.isInTouchMode = isInTouchMode
                 focusRequester.requestFocus()
-                hideSelectionToolbar()
+                showToolbar = false
             }
 
         selectionRegistrar.onSelectionUpdateCallback =
-            { layoutCoordinates, newPosition, previousPosition, isStartHandle, selectionMode ->
+            { isInTouchMode,
+                layoutCoordinates,
+                newPosition,
+                previousPosition,
+                isStartHandle,
+                selectionMode ->
                 val newPositionInContainer =
                     convertToContainerCoordinates(layoutCoordinates, newPosition)
                 val previousPositionInContainer =
                     convertToContainerCoordinates(layoutCoordinates, previousPosition)
 
+                this.isInTouchMode = isInTouchMode
                 updateSelection(
                     newPosition = newPositionInContainer,
                     previousPosition = previousPositionInContainer,
@@ -263,7 +280,7 @@
             }
 
         selectionRegistrar.onSelectionUpdateEndCallback = {
-            showSelectionToolbar()
+            showToolbar = true
             // This property is set by updateSelection while dragging, so we need to clear it after
             // the original selection drag.
             draggingHandle = null
@@ -371,12 +388,39 @@
                 selection?.let { subselections[selectable.selectableId] = it }
                 merge(mergedSelection, selection)
             }
-        if (newSelection != previousSelection) {
+        if (isInTouchMode && newSelection != previousSelection) {
             hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
         }
         return Pair(newSelection, subselections)
     }
 
+    internal fun isNonEmptySelection(): Boolean {
+        val selection = selection ?: return false
+
+        var betweenSelectables = false
+        selectionRegistrar.sort(requireContainerCoordinates()).fastForEach {
+            if (
+                it.selectableId != selection.start.selectableId &&
+                it.selectableId != selection.end.selectableId &&
+                !betweenSelectables
+            ) {
+                // haven't found our selection yet, continue
+                return@fastForEach
+            }
+
+            betweenSelectables = true
+            if (!isCurrentSelectionEmpty(selection = selection, selectable = it)) {
+                return true
+            }
+
+            // short-circuit if this is the last selectable
+            if (it.selectableId == selection.end.selectableId && !selection.handlesCrossed ||
+                it.selectableId == selection.start.selectableId && selection.handlesCrossed
+            ) return false
+        }
+        return false
+    }
+
     internal fun getSelectedText(): AnnotatedString? {
         val selectables = selectionRegistrar.sort(requireContainerCoordinates())
         var selectedText: AnnotatedString? = null
@@ -407,29 +451,39 @@
 
     internal fun copy() {
         val selectedText = getSelectedText()
-        selectedText?.let { clipboardManager?.setText(it) }
+        selectedText?.takeIf { it.isNotEmpty() }?.let { clipboardManager?.setText(it) }
     }
 
     /**
+     * Whether toolbar should be shown right now.
+     * Examples: Show toolbar after user finishes selection.
+     * Hide it during selection.
+     * Hide it when no selection exists.
+     */
+    internal var showToolbar = false
+        internal set(value) {
+            field = value
+            if (value && isInTouchMode) showSelectionToolbar() else hideSelectionToolbar()
+        }
+
+    /**
      * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
      * to make the FloatingToolbar show up in the proper place. In addition, this function passes
      * the copy method as a callback when "copy" is clicked.
      */
-    internal fun showSelectionToolbar() {
-        if (hasFocus) {
-            selection?.let {
-                textToolbar?.showMenu(
-                    getContentRect(),
-                    onCopyRequested = {
-                        copy()
-                        onRelease()
-                    }
-                )
-            }
+    private fun showSelectionToolbar() {
+        if (hasFocus && isNonEmptySelection()) {
+            textToolbar?.showMenu(
+                getContentRect(),
+                onCopyRequested = {
+                    copy()
+                    onRelease()
+                }
+            )
         }
     }
 
-    internal fun hideSelectionToolbar() {
+    private fun hideSelectionToolbar() {
         if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
             textToolbar?.hide()
         }
@@ -511,10 +565,12 @@
     // This is for PressGestureDetector to cancel the selection.
     fun onRelease() {
         selectionRegistrar.subselections = emptyMap()
-        hideSelectionToolbar()
+        showToolbar = false
         if (selection != null) {
             onSelectionChange(null)
-            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+            if (isInTouchMode) {
+                hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+            }
         }
     }
 
@@ -543,15 +599,10 @@
                 beginCoordinates
             )
             draggingHandle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
-        }
-
-        override fun onUp() {
-            draggingHandle = null
-            currentDragPosition = null
+            showToolbar = false
         }
 
         override fun onStart(startPoint: Offset) {
-            hideSelectionToolbar()
             val selection = selection!!
             val startSelectable =
                 selectionRegistrar.selectableMap[selection.start.selectableId]
@@ -606,17 +657,15 @@
             }
         }
 
-        override fun onStop() {
-            showSelectionToolbar()
+        private fun done() {
+            showToolbar = true
             draggingHandle = null
             currentDragPosition = null
         }
 
-        override fun onCancel() {
-            showSelectionToolbar()
-            draggingHandle = null
-            currentDragPosition = null
-        }
+        override fun onUp() = done()
+        override fun onStop() = done()
+        override fun onCancel() = done()
     }
 
     /**
@@ -750,7 +799,7 @@
         var moveConsumed = false
         val newSelection = selectionRegistrar.sort(requireContainerCoordinates())
             .fastFold(null) { mergedSelection: Selection?, selectable: Selectable ->
-                val previousSubselection =
+                val previousSubSelection =
                     selectionRegistrar.subselections[selectable.selectableId]
                 val (selection, consumed) = selectable.updateSelection(
                     startHandlePosition = startHandlePosition,
@@ -759,17 +808,23 @@
                     isStartHandle = isStartHandle,
                     containerLayoutCoordinates = requireContainerCoordinates(),
                     adjustment = adjustment,
-                    previousSelection = previousSubselection,
+                    previousSelection = previousSubSelection,
                 )
 
                 moveConsumed = moveConsumed || consumed
                 selection?.let { newSubselections[selectable.selectableId] = it }
                 merge(mergedSelection, selection)
             }
+
         if (newSelection != selection) {
-            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+            if (isInTouchMode) {
+                hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+            }
             selectionRegistrar.subselections = newSubselections
             onSelectionChange(newSelection)
+            // always consume if selection changed, it is possible that it is false at this
+            // point if selectables were only removed from the selection
+            moveConsumed = true
         }
         return moveConsumed
     }
@@ -777,7 +832,7 @@
     fun contextMenuOpenAdjustment(position: Offset) {
         val isEmptySelection = selection?.toTextRange()?.collapsed ?: true
         // TODO(b/209483184) the logic should be more complex here, it should check that current
-        // selection doesn't include click position
+        //  selection doesn't include click position
         if (isEmptySelection) {
             startSelection(
                 position = position,
@@ -854,6 +909,27 @@
     }
 }
 
+private fun isCurrentSelectionEmpty(
+    selectable: Selectable,
+    selection: Selection
+): Boolean {
+    val selectableId = selectable.selectableId
+    val startSelectableId = selection.start.selectableId
+    val endSelectableId = selection.end.selectableId
+
+    if (selectableId == startSelectableId && selectableId == endSelectableId) {
+        return selection.start.offset == selection.end.offset
+    }
+
+    val text = selectable.getText()
+    val handlesCrossed = selection.handlesCrossed
+    return when (selectableId) {
+        startSelectableId -> selection.start.offset == if (handlesCrossed) 0 else text.length
+        endSelectableId -> selection.end.offset == if (handlesCrossed) text.length else 0
+        else -> text.isEmpty()
+    }
+}
+
 internal fun getCurrentSelectedText(
     selectable: Selectable,
     selection: Selection
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt
index 23c749d..e1f38fd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt
@@ -30,7 +30,7 @@
      */
     Vertical {
         override fun compare(position: Offset, bounds: Rect): Int {
-            if (bounds.contains(position)) return 0
+            if (bounds.containsInclusive(position)) return 0
 
             // When the position of the selection handle is on the top of the composable, and the
             // not on the right of the composable, it's considered as start.
@@ -52,7 +52,7 @@
      */
     Horizontal {
         override fun compare(position: Offset, bounds: Rect): Int {
-            if (bounds.contains(position)) return 0
+            if (bounds.containsInclusive(position)) return 0
 
             // When the end of the selection is on the left of the composable, the composable is
             // outside of the selection range.
@@ -95,7 +95,7 @@
         end: Offset
     ): Boolean {
         // If either of the start or end is contained by bounds, the composable is selected.
-        if (bounds.contains(start) || bounds.contains(end)) {
+        if (bounds.containsInclusive(start) || bounds.containsInclusive(end)) {
             return true
         }
         // Compare the location of start and end to the bound. If both are on the same side, return
@@ -104,4 +104,13 @@
         val compareEnd = compare(end, bounds)
         return (compareStart > 0) xor (compareEnd > 0)
     }
+
+    /**
+     * The regular contains function ([Rect.contains]) is only inclusive of left and top,
+     * but there are many times where we want to include when the offset is on the right/bottom
+     * bounds. This commonly happens when the selection handle is placed at the right of the bounds
+     * and then that offset is used in this function.
+     */
+    private fun Rect.containsInclusive(offset: Offset): Boolean =
+        offset.x in left..right && offset.y in top..bottom
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt
index 3fd8608..4aa9753 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt
@@ -73,6 +73,7 @@
      * @param layoutCoordinates [LayoutCoordinates] of the [Selectable].
      * @param startPosition coordinates of where the selection is initiated.
      * @param adjustment selection should be adjusted according to this param
+     * @param isInTouchMode whether the update is from a touch pointer
      *
      * @see notifySelectionUpdate
      * @see notifySelectionUpdateEnd
@@ -80,7 +81,8 @@
     fun notifySelectionUpdateStart(
         layoutCoordinates: LayoutCoordinates,
         startPosition: Offset,
-        adjustment: SelectionAdjustment
+        adjustment: SelectionAdjustment,
+        isInTouchMode: Boolean
     )
 
     /**
@@ -88,8 +90,9 @@
      * with selectAll [Selection].
      *
      * @param selectableId [selectableId] of the [Selectable]
+     * @param isInTouchMode whether the update is from a touch pointer
      */
-    fun notifySelectionUpdateSelectAll(selectableId: Long)
+    fun notifySelectionUpdateSelectAll(selectableId: Long, isInTouchMode: Boolean)
 
     /**
      * Call this method to notify the [SelectionContainer] that one of the selection handle has
@@ -103,6 +106,7 @@
      * @param newPosition coordinates of where the selection ends.
      * @param isStartHandle whether the moving selection handle the start handle.
      * @param adjustment selection should be adjusted according to this parameter
+     * @param isInTouchMode whether the update is from a touch pointer
      *
      * @return true if the selection handle movement is consumed. This function acts like a
      * pointer input consumer when a selection handle is dragged. It expects the caller to
@@ -117,7 +121,8 @@
         newPosition: Offset,
         previousPosition: Offset,
         isStartHandle: Boolean,
-        adjustment: SelectionAdjustment
+        adjustment: SelectionAdjustment,
+        isInTouchMode: Boolean
     ): Boolean
 
     /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
index 64de7d8..c4671e8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
@@ -67,14 +67,14 @@
      */
     @Suppress("PrimitiveInLambda")
     internal var onSelectionUpdateStartCallback:
-        ((LayoutCoordinates, Offset, SelectionAdjustment) -> Unit)? = null
+        ((Boolean, LayoutCoordinates, Offset, SelectionAdjustment) -> Unit)? = null
 
     /**
      * The callback to be invoked when the selection is initiated with selectAll [Selection].
      */
     @Suppress("PrimitiveInLambda")
     internal var onSelectionUpdateSelectAll: (
-        (Long) -> Unit
+        (Boolean, Long) -> Unit
     )? = null
 
     /**
@@ -83,7 +83,8 @@
      */
     @Suppress("PrimitiveInLambda")
     internal var onSelectionUpdateCallback:
-        ((LayoutCoordinates, Offset, Offset, Boolean, SelectionAdjustment) -> Boolean)? = null
+        ((Boolean, LayoutCoordinates, Offset, Offset, Boolean, SelectionAdjustment) -> Boolean)? =
+        null
 
     /**
      * The callback to be invoked when selection update finished.
@@ -176,13 +177,19 @@
     override fun notifySelectionUpdateStart(
         layoutCoordinates: LayoutCoordinates,
         startPosition: Offset,
-        adjustment: SelectionAdjustment
+        adjustment: SelectionAdjustment,
+        isInTouchMode: Boolean
     ) {
-        onSelectionUpdateStartCallback?.invoke(layoutCoordinates, startPosition, adjustment)
+        onSelectionUpdateStartCallback?.invoke(
+            isInTouchMode,
+            layoutCoordinates,
+            startPosition,
+            adjustment
+        )
     }
 
-    override fun notifySelectionUpdateSelectAll(selectableId: Long) {
-        onSelectionUpdateSelectAll?.invoke(selectableId)
+    override fun notifySelectionUpdateSelectAll(selectableId: Long, isInTouchMode: Boolean) {
+        onSelectionUpdateSelectAll?.invoke(isInTouchMode, selectableId)
     }
 
     override fun notifySelectionUpdate(
@@ -190,9 +197,11 @@
         newPosition: Offset,
         previousPosition: Offset,
         isStartHandle: Boolean,
-        adjustment: SelectionAdjustment
+        adjustment: SelectionAdjustment,
+        isInTouchMode: Boolean
     ): Boolean {
         return onSelectionUpdateCallback?.invoke(
+            isInTouchMode,
             layoutCoordinates,
             newPosition,
             previousPosition,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegate.kt
index 2609eb1..b59398c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionDelegate.kt
@@ -25,6 +25,9 @@
  * @param textLayoutResult a result of the text layout.
  * @param rawStartOffset unprocessed start offset calculated directly from input position
  * @param rawEndOffset unprocessed end offset calculated directly from input position
+ * @param previousHandleOffset the previous offset of the moving handle. When isStartHandle is
+ * true, it's the previous offset of the start handle before the movement, and vice versa.
+ * When there isn't a valid previousHandleOffset, previousHandleOffset should be -1.
  * @param previousSelection previous selection result
  * @param isStartHandle true if the start handle is being dragged
  * @param adjustment selection is adjusted according to this param
@@ -35,6 +38,7 @@
     textLayoutResult: TextLayoutResult?,
     rawStartOffset: Int,
     rawEndOffset: Int,
+    previousHandleOffset: Int,
     previousSelection: TextRange?,
     isStartHandle: Boolean,
     adjustment: SelectionAdjustment
@@ -51,7 +55,7 @@
         return adjustment.adjust(
             textLayoutResult = textLayoutResult,
             newRawSelectionRange = textRange,
-            previousHandleOffset = -1,
+            previousHandleOffset = previousHandleOffset,
             isStartHandle = isStartHandle,
             previousSelectionRange = previousSelection
         )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index 25557de..f7e330b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -82,10 +82,18 @@
 
     /**
      * The current [TextFieldValue]. This contains the original text, not the transformed text.
+     * Transformed text can be found with [transformedText].
      */
     internal var value: TextFieldValue by mutableStateOf(TextFieldValue())
 
     /**
+     * The current transformed text from the [TextFieldState].
+     * The original text can be found in [value].
+     */
+    @OptIn(InternalFoundationTextApi::class)
+    internal val transformedText get() = state?.textDelegate?.text
+
+    /**
      * Visual transformation of the text field's text. Used to check if certain toolbar options
      * are permitted. For example, 'cut' will not be available is it is password transformation.
      */
@@ -151,6 +159,11 @@
         private set
 
     /**
+     * The previous offset of the drag, before selection adjustments.
+     */
+    private var previousRawDragOffset: Int = -1
+
+    /**
      * The old [TextFieldValue] before entering the selection mode on long press. Used to exit
      * the selection mode.
      */
@@ -180,37 +193,42 @@
             // Long Press at the blank area, the cursor should show up at the end of the line.
             if (state?.layoutResult?.isPositionOnText(startPoint) != true) {
                 state?.layoutResult?.let { layoutResult ->
-                    val offset = offsetMapping.transformedToOriginal(
-                        layoutResult.getLineEnd(
-                            layoutResult.getLineForVerticalPosition(startPoint.y)
-                        )
-                    )
-                    hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+                    val transformedOffset = layoutResult.getOffsetForPosition(startPoint)
+                    val offset = offsetMapping.transformedToOriginal(transformedOffset)
 
                     val newValue = createTextFieldValue(
                         annotatedString = value.annotatedString,
                         selection = TextRange(offset, offset)
                     )
-                    enterSelectionMode()
+
+                    enterSelectionMode(showFloatingToolbar = false)
+                    hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
                     onValueChange(newValue)
-                    return
+                    previousRawDragOffset = offset
+                }
+            } else {
+                if (value.text.isEmpty()) return
+                enterSelectionMode(showFloatingToolbar = false)
+                state?.layoutResult?.let { layoutResult ->
+                    val offset = layoutResult.getOffsetForPosition(startPoint)
+                    val adjustedStartSelection = updateSelection(
+                        // reset selection, otherwise a previous selection may be used
+                        // as context for creating the next selection
+                        value = value.copy(selection = TextRange.Zero),
+                        transformedStartOffset = offset,
+                        transformedEndOffset = offset,
+                        isStartHandle = false,
+                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
+                        isTouchBasedSelection = true,
+                    )
+                    // For touch, set the begin offset to the adjusted selection.
+                    // When char based selection is used, we want to ensure we snap the
+                    // beginning offset to the start word boundary of the first selected word.
+                    dragBeginOffsetInText = adjustedStartSelection.start
+                    previousRawDragOffset = offset
                 }
             }
 
-            // selection never started
-            if (value.text.isEmpty()) return
-            enterSelectionMode()
-            state?.layoutResult?.let { layoutResult ->
-                val offset = layoutResult.getOffsetForPosition(startPoint)
-                updateSelection(
-                    value = value,
-                    transformedStartOffset = offset,
-                    transformedEndOffset = offset,
-                    isStartHandle = false,
-                    adjustment = SelectionAdjustment.Word
-                )
-                dragBeginOffsetInText = offset
-            }
             dragBeginPosition = startPoint
             currentDragPosition = dragBeginPosition
             dragTotalDistance = Offset.Zero
@@ -223,21 +241,64 @@
             dragTotalDistance += delta
             state?.layoutResult?.let { layoutResult ->
                 currentDragPosition = dragBeginPosition + dragTotalDistance
-                val startOffset = dragBeginOffsetInText ?: layoutResult.getOffsetForPosition(
-                    position = dragBeginPosition,
-                    coerceInVisibleBounds = false
-                )
-                val endOffset = layoutResult.getOffsetForPosition(
-                    position = currentDragPosition!!,
-                    coerceInVisibleBounds = false
-                )
-                updateSelection(
-                    value = value,
-                    transformedStartOffset = startOffset,
-                    transformedEndOffset = endOffset,
-                    isStartHandle = false,
-                    adjustment = SelectionAdjustment.Word
-                )
+
+                if (
+                    dragBeginOffsetInText == null &&
+                    !layoutResult.isPositionOnText(currentDragPosition!!)
+                ) {
+                    // both start and end of drag is in end padding.
+                    val startOffset = offsetMapping.transformedToOriginal(
+                        layoutResult.getOffsetForPosition(dragBeginPosition)
+                    )
+
+                    val endOffset = offsetMapping.transformedToOriginal(
+                        layoutResult.getOffsetForPosition(currentDragPosition!!)
+                    )
+
+                    val adjustment = if (startOffset == endOffset) {
+                        // start and end is in the same end padding, keep the collapsed selection
+                        SelectionAdjustment.None
+                    } else {
+                        SelectionAdjustment.CharacterWithWordAccelerate
+                    }
+
+                    updateSelection(
+                        value = value,
+                        transformedStartOffset = startOffset,
+                        transformedEndOffset = endOffset,
+                        isStartHandle = false,
+                        adjustment = adjustment,
+                        isTouchBasedSelection = true,
+                        allowPreviousSelectionCollapsed = true,
+                    )
+                    previousRawDragOffset = endOffset
+                } else {
+                    val startOffset = dragBeginOffsetInText ?: layoutResult.getOffsetForPosition(
+                        position = dragBeginPosition,
+                        coerceInVisibleBounds = false
+                    )
+                    val endOffset = layoutResult.getOffsetForPosition(
+                        position = currentDragPosition!!,
+                        coerceInVisibleBounds = false
+                    )
+
+                    if (dragBeginOffsetInText == null && startOffset == endOffset) {
+                        // if we are selecting starting from end padding,
+                        // don't start selection until we have and un-collapsed selection.
+                        return
+                    }
+
+                    updateSelection(
+                        value = value,
+                        transformedStartOffset = startOffset,
+                        transformedEndOffset = endOffset,
+                        isStartHandle = false,
+                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
+                        isTouchBasedSelection = true,
+                        allowPreviousSelectionCollapsed = dragBeginOffsetInText == null,
+                    )
+                    previousRawDragOffset = endOffset
+                }
             }
             state?.showFloatingToolbar = false
         }
@@ -263,7 +324,8 @@
                     transformedStartOffset = startOffset,
                     transformedEndOffset = clickOffset,
                     isStartHandle = false,
-                    adjustment = SelectionAdjustment.None
+                    adjustment = SelectionAdjustment.None,
+                    isTouchBasedSelection = false,
                 )
                 return true
             }
@@ -286,7 +348,8 @@
                     transformedStartOffset = startOffset,
                     transformedEndOffset = dragOffset,
                     isStartHandle = false,
-                    adjustment = SelectionAdjustment.None
+                    adjustment = SelectionAdjustment.None,
+                    isTouchBasedSelection = false,
                 )
                 return true
             }
@@ -302,6 +365,7 @@
             dragBeginPosition = downPosition
 
             state?.layoutResult?.let { layoutResult ->
+                enterSelectionMode()
                 dragBeginOffsetInText = layoutResult.getOffsetForPosition(downPosition)
                 val clickOffset = layoutResult.getOffsetForPosition(dragBeginPosition)
                 updateSelection(
@@ -309,7 +373,8 @@
                     transformedStartOffset = clickOffset,
                     transformedEndOffset = clickOffset,
                     isStartHandle = false,
-                    adjustment = adjustment
+                    adjustment = adjustment,
+                    isTouchBasedSelection = false,
                 )
                 return true
             }
@@ -331,12 +396,15 @@
                     transformedStartOffset = dragBeginOffsetInText!!,
                     transformedEndOffset = dragOffset,
                     isStartHandle = false,
-                    adjustment = adjustment
+                    adjustment = adjustment,
+                    isTouchBasedSelection = false,
                 )
                 return true
             }
             return false
         }
+
+        override fun onDragDone() { /* Nothing to do */ }
     }
 
     /**
@@ -347,6 +415,7 @@
             override fun onDown(point: Offset) {
                 draggingHandle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
                 currentDragPosition = getAdjustedCoordinates(getHandlePosition(isStartHandle))
+                state?.isInTouchMode = true
             }
 
             override fun onUp() {
@@ -362,6 +431,8 @@
                 // Zero out the total distance that being dragged.
                 dragTotalDistance = Offset.Zero
                 draggingHandle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
+                previousRawDragOffset = state?.layoutResult?.value
+                    ?.getOffsetForPosition(currentDragPosition!!) ?: -1
                 state?.showFloatingToolbar = false
             }
 
@@ -387,8 +458,10 @@
                         transformedStartOffset = startOffset,
                         transformedEndOffset = endOffset,
                         isStartHandle = isStartHandle,
-                        adjustment = SelectionAdjustment.Character
+                        adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
+                        isTouchBasedSelection = true, // handle drag infers touch
                     )
+                    previousRawDragOffset = if (isStartHandle) startOffset else endOffset
                 }
                 state?.showFloatingToolbar = false
             }
@@ -441,7 +514,10 @@
                 // Nothing changed, skip onValueChange hand hapticFeedback.
                 if (newSelection == value.selection) return
 
-                hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+                if (state?.isInTouchMode != false) {
+                    hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+                }
+
                 onValueChange(
                     createTextFieldValue(
                         annotatedString = value.annotatedString,
@@ -463,13 +539,15 @@
      * The method to record the required state values on entering the selection mode.
      *
      * Is triggered on long press or accessibility action.
+     *
+     * @param showFloatingToolbar whether to show the floating toolbar when entering selection mode
      */
-    internal fun enterSelectionMode() {
+    internal fun enterSelectionMode(showFloatingToolbar: Boolean = true) {
         if (state?.hasFocus == false) {
             focusRequester?.requestFocus()
         }
         oldValue = value
-        state?.showFloatingToolbar = true
+        state?.showFloatingToolbar = showFloatingToolbar
         setHandleState(HandleState.Selection)
     }
 
@@ -630,6 +708,7 @@
      * the copy, paste and cut method as callbacks when "copy", "cut" or "paste" is clicked.
      */
     internal fun showSelectionToolbar() {
+        if (state?.isInTouchMode == false) return
         val isPassword = visualTransformation is PasswordVisualTransformation
         val copy: (() -> Unit)? = if (!value.selection.collapsed && !isPassword) {
             {
@@ -682,7 +761,8 @@
                     transformedStartOffset = offset,
                     transformedEndOffset = offset,
                     isStartHandle = false,
-                    adjustment = SelectionAdjustment.Word
+                    adjustment = SelectionAdjustment.Word,
+                    isTouchBasedSelection = false // context menu implies non-touch
                 )
             }
         }
@@ -742,14 +822,29 @@
         return Rect.Zero
     }
 
+    /**
+     * Update the text field's selection based on new offsets.
+     *
+     * @param value the current [TextFieldValue]
+     * @param transformedStartOffset the start offset to use
+     * @param transformedEndOffset the end offset to use
+     * @param isStartHandle whether the start handle is being updated
+     * @param adjustment The selection adjustment to use
+     * @param allowPreviousSelectionCollapsed Allow a collapsed selection to be passed to selection
+     * adjustment. In most cases, a collapsed selection should be considered "no previous
+     * selection" for selection adjustment. However, in some cases - like starting a selection in
+     * end padding - a collapsed selection may be necessary context to avoid selection flickering.
+     */
     private fun updateSelection(
         value: TextFieldValue,
         transformedStartOffset: Int,
         transformedEndOffset: Int,
         isStartHandle: Boolean,
-        adjustment: SelectionAdjustment
-    ) {
-        val transformedSelection = TextRange(
+        adjustment: SelectionAdjustment,
+        isTouchBasedSelection: Boolean,
+        allowPreviousSelectionCollapsed: Boolean = false,
+    ): TextRange {
+        val oldTransformedSelection = TextRange(
             offsetMapping.originalToTransformed(value.selection.start),
             offsetMapping.originalToTransformed(value.selection.end)
         )
@@ -758,31 +853,43 @@
             textLayoutResult = state?.layoutResult?.value,
             rawStartOffset = transformedStartOffset,
             rawEndOffset = transformedEndOffset,
-            previousSelection = if (transformedSelection.collapsed) null else transformedSelection,
+            previousHandleOffset = previousRawDragOffset,
+            previousSelection = oldTransformedSelection
+                .takeIf { allowPreviousSelectionCollapsed || !it.collapsed },
             isStartHandle = isStartHandle,
-            adjustment = adjustment
+            adjustment = adjustment,
         )
 
-        val originalSelection = TextRange(
+        val newSelection = TextRange(
             start = offsetMapping.transformedToOriginal(newTransformedSelection.start),
             end = offsetMapping.transformedToOriginal(newTransformedSelection.end)
         )
 
-        if (originalSelection == value.selection) return
+        if (newSelection == value.selection) return value.selection
 
-        hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        val onlyChangeIsReversed = newSelection.reversed != value.selection.reversed &&
+            newSelection.run { TextRange(end, start) } == value.selection
+
+        // don't haptic if we are using a mouse or if we aren't moving the selection bounds
+        if (isTouchBasedSelection && !onlyChangeIsReversed) {
+            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        }
 
         val newValue = createTextFieldValue(
             annotatedString = value.annotatedString,
-            selection = originalSelection
+            selection = newSelection
         )
         onValueChange(newValue)
 
+        state?.isInTouchMode = isTouchBasedSelection
+
         // showSelectionHandleStart/End might be set to false when scrolled out of the view.
         // When the selection is updated, they must also be updated so that handles will be shown
         // or hidden correctly.
         state?.showSelectionHandleStart = isSelectionHandleInVisibleBound(true)
         state?.showSelectionHandleEnd = isSelectionHandleInVisibleBound(false)
+
+        return newSelection
     }
 
     private fun setHandleState(handleState: HandleState) {
@@ -852,7 +959,7 @@
     magnifierSize: IntSize
 ): Offset {
     // Never show the magnifier in an empty text field.
-    if (manager.value.text.isEmpty()) return Offset.Unspecified
+    if (manager.transformedText?.isEmpty() != false) return Offset.Unspecified
     val rawTextOffset = when (manager.draggingHandle) {
         null -> return Offset.Unspecified
         Handle.Cursor,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextPreparedSelection.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextPreparedSelection.kt
index fc9ef47..523054e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextPreparedSelection.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextPreparedSelection.kt
@@ -202,11 +202,19 @@
     }
 
     fun moveCursorPrevByParagraph() = apply {
-        setCursor(getParagraphStart())
+        var paragraphStart = text.findParagraphStart(selection.min)
+        if (paragraphStart == selection.min && paragraphStart != 0) {
+            paragraphStart = text.findParagraphStart(paragraphStart - 1)
+        }
+        setCursor(paragraphStart)
     }
 
     fun moveCursorNextByParagraph() = apply {
-        setCursor(getParagraphEnd())
+        var paragraphEnd = text.findParagraphEnd(selection.max)
+        if (paragraphEnd == selection.max && paragraphEnd != text.length) {
+            paragraphEnd = text.findParagraphEnd(paragraphEnd + 1)
+        }
+        setCursor(paragraphEnd)
     }
 
     fun moveCursorUpByLine() = apply(false) {
@@ -345,10 +353,6 @@
     private fun charOffset(offset: Int) =
         offset.coerceAtMost(text.length - 1)
 
-    private fun getParagraphStart() = text.findParagraphStart(selection.min)
-
-    private fun getParagraphEnd() = text.findParagraphEnd(selection.max)
-
     companion object {
         /**
          * Value returned by [getNextCharacterIndex] and [getPrecedingCharacterIndex] when no valid
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt
deleted file mode 100644
index c154770..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionMouseDetector.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.text.selection
-
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.drag
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.pointer.AwaitPointerEventScope
-import androidx.compose.ui.input.pointer.PointerEvent
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputChange
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.input.pointer.PointerType
-import androidx.compose.ui.input.pointer.changedToDown
-import androidx.compose.ui.input.pointer.isPrimaryPressed
-import androidx.compose.ui.platform.ViewConfiguration
-import androidx.compose.ui.util.fastAll
-
-// * Without shift it starts the new selection from the scratch.
-// * With shift expand / shrink existed selection.
-// * Click sets start and end of the selection, but shift click only the end of
-// selection.
-// * The specific case of it when selection is collapsed, but the same logic is
-// applied for not collapsed selection too.
-internal interface MouseSelectionObserver {
-    // on start of shift click. if returns true event will be consumed
-    fun onExtend(downPosition: Offset): Boolean
-    // on drag after shift click. if returns true event will be consumed
-    fun onExtendDrag(dragPosition: Offset): Boolean
-
-    // if returns true event will be consumed
-    fun onStart(downPosition: Offset, adjustment: SelectionAdjustment): Boolean
-    fun onDrag(dragPosition: Offset, adjustment: SelectionAdjustment): Boolean
-}
-
-// Distance in pixels between consecutive click positions to be considered them as clicks sequence
-internal const val ClicksSlop = 100.0
-
-private class ClicksCounter(
-    private val viewConfiguration: ViewConfiguration
-) {
-    var clicks = 0
-    var prevClick: PointerInputChange? = null
-    fun update(event: PointerEvent) {
-        val currentPrevClick = prevClick
-        val newClick = event.changes[0]
-        if (currentPrevClick != null &&
-            timeIsTolerable(currentPrevClick, newClick) &&
-            positionIsTolerable(currentPrevClick, newClick)
-        ) {
-            clicks += 1
-        } else {
-            clicks = 1
-        }
-        prevClick = newClick
-    }
-
-    fun timeIsTolerable(prevClick: PointerInputChange, newClick: PointerInputChange): Boolean {
-        val diff = newClick.uptimeMillis - prevClick.uptimeMillis
-        return diff < viewConfiguration.doubleTapTimeoutMillis
-    }
-
-    fun positionIsTolerable(prevClick: PointerInputChange, newClick: PointerInputChange): Boolean {
-        val diff = newClick.position - prevClick.position
-        return diff.getDistance() < ClicksSlop
-    }
-}
-
-internal suspend fun PointerInputScope.mouseSelectionDetector(
-    observer: MouseSelectionObserver
-) {
-    awaitEachGesture {
-        val clicksCounter = ClicksCounter(viewConfiguration)
-        while (true) {
-            val down = awaitMouseEventDown()
-            clicksCounter.update(down)
-            val downChange = down.changes[0]
-            if (down.isShiftPressed) {
-                val started = observer.onExtend(downChange.position)
-                if (started) {
-                    downChange.consume()
-                    drag(downChange.id) {
-                        if (observer.onExtendDrag(it.position)) {
-                            it.consume()
-                        }
-                    }
-                }
-            } else {
-                val selectionMode = when (clicksCounter.clicks) {
-                    1 -> SelectionAdjustment.None
-                    2 -> SelectionAdjustment.Word
-                    else -> SelectionAdjustment.Paragraph
-                }
-                val started = observer.onStart(downChange.position, selectionMode)
-                if (started) {
-                    downChange.consume()
-                    drag(downChange.id) {
-                        if (observer.onDrag(it.position, selectionMode)) {
-                            it.consume()
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-private suspend fun AwaitPointerEventScope.awaitMouseEventDown(): PointerEvent {
-    var event: PointerEvent
-    do {
-        event = awaitPointerEvent(PointerEventPass.Main)
-    } while (
-        !(
-            event.buttons.isPrimaryPressed && event.changes.fastAll {
-                it.type == PointerType.Mouse && it.changedToDown()
-            }
-            )
-    )
-    return event
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustmentTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustmentTest.kt
index 15f2365..52ffbd9 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustmentTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustmentTest.kt
@@ -572,7 +572,7 @@
                 TextRange(18, 23)
             )
         )
-        // The and previous selection is null, it should use word based
+        // The previous selection is null, it should use word based
         // selection in this case.
         val rawSelection = TextRange(3, 3)
         val isStartHandle = false
@@ -699,7 +699,7 @@
     }
 
     @Test
-    fun adjustment_characterWithWordAccelerate_expandEndOutOfWord_notExceedThreshold() {
+    fun adjustment_characterWithWordAccelerate_expandEndOutOfWord() {
         val textLayoutResult = mockTextLayoutResult(
             text = "hello world hello world",
             wordBoundaries = listOf(
@@ -709,11 +709,11 @@
                 TextRange(18, 23)
             )
         )
-        // The previous selection is [6, 11) and the new selection expand the end to 13.
+        // The previous selection is [6, 11) and the new selection expand the end to 12.
         // Because the previous selection end is at word boundary, it will use word selection mode.
-        // However, the end is didn't exceed the middle of the next word(offset = 14), the adjusted
-        // selection end will be 12, which is the start of the next word.
-        val rawSelection = TextRange(6, 13)
+        // The end did exceed start of the next word(offset = 12), the adjusted
+        // selection end will be 17, which is the end of the next word.
+        val rawSelection = TextRange(6, 12)
         val previousSelection = TextRange(6, 11)
         val isStartHandle = false
 
@@ -725,11 +725,11 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(6, 12))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(6, 17))
     }
 
     @Test
-    fun adjustment_characterWithWordAccelerate_expandStartOutOfWord_notExceedThreshold_reversed() {
+    fun adjustment_characterWithWordAccelerate_expandStartOutOfWord_reversed() {
         val textLayoutResult = mockTextLayoutResult(
             text = "hello world hello world",
             wordBoundaries = listOf(
@@ -752,11 +752,11 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(12, 6))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(17, 6))
     }
 
     @Test
-    fun adjustment_characterWithWordAccelerate_expandStartOutOfWord_notExceedThreshold() {
+    fun adjustment_characterWithWordAccelerate_expandStartOutOfWord() {
         val textLayoutResult = mockTextLayoutResult(
             text = "hello world hello world",
             wordBoundaries = listOf(
@@ -769,8 +769,8 @@
         // The previous selection is [6, 11) and the new selection expand the start to 5.
         // Because the previous selection start is at word boundary, it will use word selection
         // mode.
-        // However, the start is didn't exceed the middle of the previous word(offset = 2), the
-        // adjusted selection end will be 5, which is the end of the previous word.
+        // The start did exceed the end of the previous word(offset = 5), the
+        // adjusted selection end will be 0, which is the start of the previous word.
         val rawSelection = TextRange(5, 11)
         val previousSelection = TextRange(6, 11)
         val isStartHandle = true
@@ -783,11 +783,11 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(5, 11))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(0, 11))
     }
 
     @Test
-    fun adjustment_characterWithWordAccelerate_expandEndOutOfWord_notExceedThreshold_reversed() {
+    fun adjustment_characterWithWordAccelerate_expandEndOutOfWord_reversed() {
         val textLayoutResult = mockTextLayoutResult(
             text = "hello world hello world",
             wordBoundaries = listOf(
@@ -798,6 +798,7 @@
             )
         )
 
+        // expands to first word boundary, so will select the first word.
         val rawSelection = TextRange(11, 5)
         val previousSelection = TextRange(11, 6)
         val isStartHandle = false
@@ -810,7 +811,7 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(11, 5))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(11, 0))
     }
 
     @Test
@@ -1011,8 +1012,8 @@
         //   world_
         // The previous selection is [6, 8) and new selection expand the start to 3. Because offset
         // 3 is at the previous line, it will use word based selection strategy. And because 3
-        // doesn't exceed the middle of the previous word(offset: 2), the end will be adjusted to
-        // word end: 5.
+        // does exceed the end of the previous word(offset: 5), the end will be adjusted to
+        // word start: 0.
         val rawSelection = TextRange(3, 8)
         val previousSelection = TextRange(7, 8)
         val isStartHandle = true
@@ -1025,7 +1026,7 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(5, 8))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(0, 8))
     }
 
     @Test
@@ -1041,6 +1042,7 @@
             lineLength = 6
         )
 
+        // expands into first word so will select first word
         val rawSelection = TextRange(8, 3)
         val previousSelection = TextRange(8, 7)
         val isStartHandle = false
@@ -1053,7 +1055,7 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(8, 5))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(8, 0))
     }
 
     @Test
@@ -1139,9 +1141,9 @@
         // The previous selection is [16, 17) and the start is expanded to 15, which is at the
         // previous line.
         // Because start offset is moving between lines, it will use word based selection. In this
-        // case the word "hello" crosses 2 lines, so the candidate values for the adjusted start
-        // offset are 12(word start) and 16(last character of the line). Since 15 is closer to
-        // 16(word end), the end offset will be adjusted to 16.
+        // case the word "hello" crosses 2 lines. The candidate values for the adjusted start
+        // offset are 12(word start) and 16(last character of the line). Since we are expanding
+        // back, the end offset will be adjusted to the word start at 12.
         val rawSelection = TextRange(15, 17)
         val previousSelection = TextRange(16, 17)
         val isStartHandle = true
@@ -1154,7 +1156,7 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(16, 17))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(12, 17))
     }
 
     @Test
@@ -1170,6 +1172,7 @@
             lineLength = 8
         )
 
+        // crosses line, then uses word based selection which selects the rest of the word
         val rawSelection = TextRange(17, 15)
         val previousSelection = TextRange(17, 16)
         val isStartHandle = false
@@ -1182,7 +1185,7 @@
             previousSelectionRange = previousSelection
         )
 
-        assertThat(adjustedTextRange).isEqualTo(TextRange(17, 16))
+        assertThat(adjustedTextRange).isEqualTo(TextRange(17, 12))
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
index a14887a..80bc00d 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
@@ -314,7 +314,7 @@
     @Test
     fun updateSelection_notConsumeDrag_return_false() {
         selectionRegistrar.subscribe(startSelectable)
-        // The start selectable returns true and consumes the drag.
+        // The start selectable returns false and does not consume the drag.
         whenever(
             startSelectable.updateSelection(
                 anyOffset(), anyOffset(), anyOffset(), any(), any(), any(), any()
@@ -322,6 +322,10 @@
         ).thenReturn(Pair(null, false))
         whenever(startSelectable.getLayoutCoordinates()).thenReturn(mock())
 
+        // selection cannot change, else consumed will be true.
+        // the updated selection is null, so set the initial selection to null as well.
+        selectionManager.selection = null
+
         val previousStartHandlePosition = Offset(3f, 300f)
         val newStartHandlePosition = Offset(3f, 600f)
 
@@ -336,6 +340,31 @@
     }
 
     @Test
+    fun updateSelection_notConsumeDrag_butSelectionChange_return_true() {
+        selectionRegistrar.subscribe(startSelectable)
+        // The start selectable returns false and does not consume the drag.
+        whenever(
+            startSelectable.updateSelection(
+                anyOffset(), anyOffset(), anyOffset(), any(), any(), any(), any()
+            )
+        ).thenReturn(Pair(null, false))
+        whenever(startSelectable.getLayoutCoordinates()).thenReturn(mock())
+
+        val previousStartHandlePosition = Offset(3f, 300f)
+        val newStartHandlePosition = Offset(3f, 600f)
+
+        // new selection is null, so it will be counted as a change
+        val consumed = selectionManager.updateSelection(
+            newPosition = newStartHandlePosition,
+            previousPosition = previousStartHandlePosition,
+            isStartHandle = false,
+            adjustment = SelectionAdjustment.None
+        )
+
+        assertThat(consumed).isTrue()
+    }
+
+    @Test
     fun mergeSelections_selectAll() {
         val anotherSelectableId = 100L
         val selectableAnother = mock<Selectable>()
@@ -356,6 +385,149 @@
     }
 
     @Test
+    fun isNonEmptySelection_whenNonEmptySelection_sameLine_returnsTrue() {
+        val text = "Text Demo"
+        val annotatedString = AnnotatedString(text)
+        val startOffset = text.indexOf('e')
+        val endOffset = text.indexOf('m')
+        selectable.textToReturn = annotatedString
+        selectionManager.selection = Selection(
+            start = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectableId = selectableId
+            ),
+            end = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = endOffset,
+                selectableId = selectableId
+            ),
+            handlesCrossed = false
+        )
+
+        assertThat(selectionManager.isNonEmptySelection()).isTrue()
+    }
+
+    @Test
+    fun isNonEmptySelection_whenEmptySelection_sameLine_returnsFalse() {
+        val text = "Text Demo"
+        val annotatedString = AnnotatedString(text)
+        val startOffset = text.indexOf('e')
+        selectable.textToReturn = annotatedString
+        selectionManager.selection = Selection(
+            start = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectableId = selectableId
+            ),
+            end = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectableId = selectableId
+            ),
+            handlesCrossed = false
+        )
+
+        assertThat(selectionManager.isNonEmptySelection()).isFalse()
+    }
+
+    @Test
+    fun isNonEmptySelection_whenNonEmptySelection_multiLine_returnsTrue() {
+        val text = "Text Demo"
+        val annotatedString = AnnotatedString(text = text)
+        val startOffset = text.indexOf('m')
+        val endOffset = text.indexOf('x')
+
+        selectionRegistrar.subscribe(endSelectable)
+        selectionRegistrar.subscribe(middleSelectable)
+        selectionRegistrar.subscribe(startSelectable)
+        selectionRegistrar.subscribe(lastSelectable)
+        selectionRegistrar.sorted = true
+        whenever(startSelectable.getText()).thenReturn(annotatedString)
+        whenever(middleSelectable.getText()).thenReturn(annotatedString)
+        whenever(endSelectable.getText()).thenReturn(annotatedString)
+        selectionManager.selection = Selection(
+            start = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectableId = startSelectableId
+            ),
+            end = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = endOffset,
+                selectableId = endSelectableId
+            ),
+            handlesCrossed = true
+        )
+
+        assertThat(selectionManager.isNonEmptySelection()).isTrue()
+    }
+
+    @Test
+    fun isNonEmptySelection_whenEmptySelection_multiLine_returnsFalse() {
+        val text = "Text Demo"
+        val annotatedString = AnnotatedString(text)
+        val startOffset = text.length
+        val endOffset = 0
+
+        selectionRegistrar.subscribe(startSelectable)
+        selectionRegistrar.subscribe(middleSelectable)
+        selectionRegistrar.subscribe(endSelectable)
+        selectionRegistrar.subscribe(lastSelectable)
+        selectionRegistrar.sorted = true
+        whenever(startSelectable.getText()).thenReturn(annotatedString)
+        whenever(middleSelectable.getText()).thenReturn(AnnotatedString(""))
+        whenever(endSelectable.getText()).thenReturn(annotatedString)
+        selectionManager.selection = Selection(
+            start = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectableId = startSelectableId
+            ),
+            end = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = endOffset,
+                selectableId = endSelectableId
+            ),
+            handlesCrossed = false
+        )
+
+        assertThat(selectionManager.isNonEmptySelection()).isFalse()
+    }
+
+    @Test
+    fun isNonEmptySelection_whenEmptySelection_multiLineCrossed_returnsFalse() {
+        val text = "Text Demo"
+        val annotatedString = AnnotatedString(text)
+        val startOffset = 0
+        val endOffset = text.length
+
+        selectionRegistrar.subscribe(endSelectable)
+        selectionRegistrar.subscribe(middleSelectable)
+        selectionRegistrar.subscribe(startSelectable)
+        selectionRegistrar.subscribe(lastSelectable)
+        selectionRegistrar.sorted = true
+        whenever(startSelectable.getText()).thenReturn(annotatedString)
+        whenever(middleSelectable.getText()).thenReturn(AnnotatedString(""))
+        whenever(endSelectable.getText()).thenReturn(annotatedString)
+        selectionManager.selection = Selection(
+            start = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = startOffset,
+                selectableId = startSelectableId
+            ),
+            end = Selection.AnchorInfo(
+                direction = ResolvedTextDirection.Ltr,
+                offset = endOffset,
+                selectableId = endSelectableId
+            ),
+            handlesCrossed = true
+        )
+
+        assertThat(selectionManager.isNonEmptySelection()).isFalse()
+    }
+
+    @Test
     fun getSelectedText_selection_null_return_null() {
         selectionManager.selection = null
 
@@ -555,7 +727,7 @@
         )
         selectionManager.hasFocus = true
 
-        selectionManager.showSelectionToolbar()
+        selectionManager.showToolbar = true
 
         verify(textToolbar, times(1)).showMenu(
             eq(Rect.Zero),
@@ -588,7 +760,7 @@
         )
         selectionManager.hasFocus = false
 
-        selectionManager.showSelectionToolbar()
+        selectionManager.showToolbar = true
 
         verify(textToolbar, never()).showMenu(
             eq(Rect.Zero),
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/StringHelpersTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/StringHelpersTest.kt
index 4f37e11..333c95a 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/StringHelpersTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/StringHelpersTest.kt
@@ -25,25 +25,27 @@
 
 @RunWith(JUnit4::class)
 class StringHelpersTest {
-    val string = "ab\n\ncd"
-    val endOfFirstLinePos = string.indexOf("\n")
-    val emptyLinePos = endOfFirstLinePos + 1
+    private val string = "ab\n\ncd"
+    private val endOfFirstLinePos = string.indexOf("\n")
+    private val emptyLinePos = endOfFirstLinePos + 1
 
     @Test
     fun findParagraphStart() {
         assertThat(string.findParagraphStart(string.indexOf("a"))).isEqualTo(string.indexOf("a"))
+        assertThat(string.findParagraphStart(string.indexOf("b"))).isEqualTo(string.indexOf("a"))
+        assertThat(string.findParagraphStart(endOfFirstLinePos)).isEqualTo(string.indexOf("a"))
+        assertThat(string.findParagraphStart(emptyLinePos)).isEqualTo(emptyLinePos)
+        assertThat(string.findParagraphStart(string.indexOf("c"))).isEqualTo(string.indexOf("c"))
         assertThat(string.findParagraphStart(string.indexOf("d"))).isEqualTo(string.indexOf("c"))
-        // ignore directly preceding line break
-        assertThat(string.findParagraphStart(string.indexOf("c"))).isEqualTo(emptyLinePos)
-        assertThat(string.findParagraphStart(emptyLinePos)).isEqualTo(string.indexOf("a"))
     }
 
     @Test
     fun findParagraphEnd() {
+        assertThat(string.findParagraphEnd(string.indexOf("a"))).isEqualTo(endOfFirstLinePos)
+        assertThat(string.findParagraphEnd(string.indexOf("b"))).isEqualTo(endOfFirstLinePos)
+        assertThat(string.findParagraphEnd(endOfFirstLinePos)).isEqualTo(endOfFirstLinePos)
+        assertThat(string.findParagraphEnd(emptyLinePos)).isEqualTo(emptyLinePos)
         assertThat(string.findParagraphEnd(string.indexOf("c"))).isEqualTo(string.length)
         assertThat(string.findParagraphEnd(string.indexOf("d"))).isEqualTo(string.length)
-        // ignore directly following line break
-        assertThat(string.findParagraphEnd(endOfFirstLinePos)).isEqualTo(emptyLinePos)
-        assertThat(string.findParagraphEnd(emptyLinePos)).isEqualTo(string.length)
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
index 525e6bf..32eb142 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
@@ -154,7 +154,7 @@
         manager.touchSelectionObserver.onStart(dragBeginPosition)
 
         assertThat(state.handleState).isEqualTo(HandleState.Selection)
-        assertThat(state.showFloatingToolbar).isTrue()
+        assertThat(state.showFloatingToolbar).isFalse()
         assertThat(value.selection).isEqualTo(fakeTextRange)
         verify(
             hapticFeedback,
@@ -170,21 +170,16 @@
     @Test
     fun TextFieldSelectionManager_touchSelectionObserver_onLongPress_blank() {
         // Setup
-        val fakeLineNumber = 0
         val fakeLineEnd = text.length
         whenever(layoutResultProxy.isPositionOnText(dragBeginPosition)).thenReturn(false)
-        whenever(layoutResultProxy.getLineForVerticalPosition(dragBeginPosition.y))
-            .thenReturn(fakeLineNumber)
-        whenever(layoutResult.getLineLeft(fakeLineNumber))
-            .thenReturn(dragBeginPosition.x + 1.0f)
-        whenever(layoutResultProxy.getLineEnd(fakeLineNumber)).thenReturn(fakeLineEnd)
+        whenever(layoutResultProxy.getOffsetForPosition(dragBeginPosition)).thenReturn(fakeLineEnd)
 
         // Act
         manager.touchSelectionObserver.onStart(dragBeginPosition)
 
         // Assert
         assertThat(state.handleState).isEqualTo(HandleState.Selection)
-        assertThat(state.showFloatingToolbar).isTrue()
+        assertThat(state.showFloatingToolbar).isFalse()
         assertThat(value.selection).isEqualTo(TextRange(fakeLineEnd))
         verify(
             hapticFeedback,
@@ -199,6 +194,8 @@
 
     @Test
     fun TextFieldSelectionManager_touchSelectionObserver_onDrag() {
+        whenever(layoutResultProxy.isPositionOnText(dragBeginPosition)).thenReturn(true)
+
         manager.touchSelectionObserver.onStart(dragBeginPosition)
         manager.touchSelectionObserver.onDrag(dragDistance)
 
@@ -253,7 +250,7 @@
         manager.handleDragObserver(isStartHandle = true).onDrag(dragDistance)
 
         assertThat(state.showFloatingToolbar).isFalse()
-        assertThat(value.selection).isEqualTo(TextRange(dragOffset, "Hello".length))
+        assertThat(value.selection).isEqualTo(TextRange(text.length, "Hello".length))
         verify(
             hapticFeedback,
             times(1)
@@ -279,13 +276,18 @@
         manager.handleDragObserver(false).onStart(Offset.Zero)
         manager.handleDragObserver(false).onDrag(Offset.Zero)
 
+        verify(
+            hapticFeedback,
+            times(1)
+        ).performHapticFeedback(HapticFeedbackType.TextHandleMove)
+
         manager.handleDragObserver(false).onStop()
 
         assertThat(manager.draggingHandle).isNull()
         assertThat(state.showFloatingToolbar).isTrue()
         verify(
             hapticFeedback,
-            times(0)
+            times(1)
         ).performHapticFeedback(HapticFeedbackType.TextHandleMove)
     }
 
@@ -342,13 +344,18 @@
         manager.handleDragObserver(false).onStart(Offset.Zero)
         manager.handleDragObserver(false).onDrag(Offset.Zero)
 
+        verify(
+            hapticFeedback,
+            times(1)
+        ).performHapticFeedback(HapticFeedbackType.TextHandleMove)
+
         manager.cursorDragObserver().onStop()
 
         assertThat(manager.draggingHandle).isNull()
         assertThat(state.showFloatingToolbar).isFalse()
         verify(
             hapticFeedback,
-            times(0)
+            times(1)
         ).performHapticFeedback(HapticFeedbackType.TextHandleMove)
     }
 
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt
index b3b12d7..a0b5b1c 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt
@@ -35,7 +35,7 @@
 
     @Test
     fun generateProfile() {
-        baselineProfileRule.collectBaselineProfile(
+        baselineProfileRule.collect(
             packageName = PACKAGE_NAME
         ) {
             val intent = Intent()
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/SteppedForLoopDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/SteppedForLoopDetector.kt
index d3a4b34..cd820d9 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/SteppedForLoopDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/SteppedForLoopDetector.kt
@@ -27,22 +27,19 @@
 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.intellij.psi.PsiClassType
 import com.intellij.psi.PsiType
-import com.intellij.psi.impl.source.PsiClassReferenceType
 import org.jetbrains.uast.UBinaryExpression
-import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UForEachExpression
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.isIntegralLiteral
 import org.jetbrains.uast.kotlin.isKotlin
 import org.jetbrains.uast.skipParenthesizedExprDown
-import org.jetbrains.uast.util.isMethodCall
 
 /**
  * Lint [Detector] to prevent allocating ranges and progression when using `step()` in a
  * for loops. For instance: `for (i in a..b step 2)` .
+ * See https://youtrack.jetbrains.com/issue/KT-59115
  */
 class SteppedForLoopDetector : Detector(), SourceCodeScanner {
     override fun getApplicableUastTypes() = listOf(
@@ -58,31 +55,11 @@
                     // Check the expression is of the form a step b, where a is a Progression type
                     if (
                         isIntegerProgression(type.leftOperand.getExpressionType()) &&
-                        !isLiteralProgression(type.leftOperand.skipParenthesizedExprDown()) &&
+                        isUntilRange(type.leftOperand.skipParenthesizedExprDown()) &&
                         type.operatorIdentifier?.name == "step" &&
                         isInteger(type.rightOperand.getExpressionType())
                     ) {
-                        report(context, node, type, type.rightOperand.asRenderString())
-                    }
-                }
-                is UQualifiedReferenceExpression -> {
-                    if (type.selector.isMethodCall()) {
-                        val method = type.selector as UCallExpression
-                        // Check we invoke step(x) on a Progression type
-                        if (
-                            isIntegerProgression(method.receiverType) &&
-                            !isLiteralProgression(method.receiver?.skipParenthesizedExprDown()) &&
-                            method.methodName == "step" &&
-                            method.valueArgumentCount == 1 &&
-                            isInteger(method.valueArguments[0].getExpressionType())
-                        ) {
-                            report(
-                                context,
-                                node,
-                                method,
-                                method.valueArguments[0].asRenderString()
-                            )
-                        }
+                        report(context, node, type, type.rightOperand.textRepresentation())
                     }
                 }
             }
@@ -92,7 +69,7 @@
     private fun isIntegerProgression(type: PsiType?): Boolean {
         if (type == null) return false
 
-        if (type is PsiClassReferenceType) {
+        if (type is PsiClassType) {
             val cls = type.resolve()
             return cls != null &&
                 (
@@ -106,13 +83,17 @@
         return false
     }
 
-    private fun isLiteralProgression(expression: UExpression?) =
-            expression is UBinaryExpression &&
-            expression.operator.text == ".." &&
-            expression.leftOperand.skipParenthesizedExprDown().isIntegralLiteral() &&
-            expression.rightOperand.skipParenthesizedExprDown().isIntegralLiteral()
+    private fun UElement.textRepresentation() = sourcePsi?.text ?: asRenderString()
 
-    private fun isInteger(type: PsiType?) = type == PsiType.INT || type == PsiType.LONG
+    // https://youtrack.jetbrains.com/issue/KT-59115
+    private fun isUntilRange(expression: UExpression?) =
+        expression is UBinaryExpression &&
+            (expression.operatorIdentifier?.name == "..<" ||
+                expression.operatorIdentifier?.name == "until")
+
+    // TODO: Use PsiTypes.intType() and PsiTypes.longType() when they are available
+    private fun isInteger(type: PsiType?) =
+        type?.canonicalText == "int" || type?.canonicalText == "long"
 
     private fun report(context: JavaContext, node: UElement, target: Any?, messageContext: String) {
         context.report(
@@ -126,11 +107,11 @@
     companion object {
         val ISSUE = Issue.create(
             "SteppedForLoop",
-            "A loop over a primitive range (Int/Long/ULong/Char) creates " +
-                "unnecessary allocations",
-            "Using the step function when iterating over a range of integer types " +
-                "causes the allocation of a Range and of a Progression. To avoid the " +
-                "allocations, consider using a while loop and manual loop counter.",
+            "A loop over an 'until' or '..<' primitive range (Int/Long/ULong/Char)" +
+                " creates unnecessary allocations",
+            "Using 'until' or '..<' to create an iteration range bypasses a compiler" +
+                " optimization. Consider until '..' instead. " +
+                "See https://youtrack.jetbrains.com/issue/KT-59115",
             Category.PERFORMANCE, 5, Severity.ERROR,
             Implementation(
                 SteppedForLoopDetector::class.java,
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/SteppedForLoopDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/SteppedForLoopDetectorTest.kt
index 47f2175..be654b0 100644
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/SteppedForLoopDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/SteppedForLoopDetectorTest.kt
@@ -60,7 +60,7 @@
     }
 
     @Test
-    fun calledOnSteppedLoop() {
+    fun skippedOnInclusiveSteppedLoops() {
         lint().files(
             kotlin(
                 """
@@ -70,33 +70,7 @@
                     for (i in a..b step 2) {
                         println(i)
                     }
-                }
-            """
-            )
-        )
-            .run()
-            .expect(
-                """
-src/test/test.kt:5: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in a..b step 2) {
-                              ~~~~~~~~~~~
-1 errors, 0 warnings
-            """
-            )
-    }
-
-    @Test
-    fun skippedOnConstantSteppedLoop() {
-        lint().files(
-            kotlin(
-                """
-                package test
-
-                fun test() {
-                    for (i in 0..10 step 2) {
-                        println(i)
-                    }
-                    for (i in (0..10).step(2)) {
+                    for (i in a downTo b step 2) {
                         println(i)
                     }
                 }
@@ -108,59 +82,7 @@
     }
 
     @Test
-    fun calledOnUnitSteppedLoop() {
-        lint().files(
-            kotlin(
-                """
-                package test
-
-                fun test(a: Int, b: Int) {
-                    for (i in a..b step 1) {
-                        println(i)
-                    }
-                }
-            """
-            )
-        )
-            .run()
-            .expect(
-                """
-src/test/test.kt:5: Error: stepping the integer range by 1. [SteppedForLoop]
-                    for (i in a..b step 1) {
-                              ~~~~~~~~~~~
-1 errors, 0 warnings
-            """
-            )
-    }
-
-    @Test
-    fun calledOnExpressionSteppedLoop() {
-        lint().files(
-            kotlin(
-                """
-                package test
-
-                fun test(a: Int, b: Int, c: Int) {
-                    for (i in a..b step (c / 2)) {
-                        println(i)
-                    }
-                }
-            """
-            )
-        )
-            .run()
-            .expect(
-                """
-src/test/test.kt:5: Error: stepping the integer range by (c / 2). [SteppedForLoop]
-                    for (i in a..b step (c / 2)) {
-                              ~~~~~~~~~~~~~~~~~
-1 errors, 0 warnings
-            """
-            )
-    }
-
-    @Test
-    fun calledOnSteppedUntilLoop() {
+    fun calledOnSteppedLoop() {
         lint().files(
             kotlin(
                 """
@@ -170,6 +92,9 @@
                     for (i in a until b step 2) {
                         println(i)
                     }
+                    for (i in a..<b step 2) {
+                        println(i)
+                    }
                 }
             """
             )
@@ -180,20 +105,26 @@
 src/test/test.kt:5: Error: stepping the integer range by 2. [SteppedForLoop]
                     for (i in a until b step 2) {
                               ~~~~~~~~~~~~~~~~
-1 errors, 0 warnings
+src/test/test.kt:8: Error: stepping the integer range by 2. [SteppedForLoop]
+                    for (i in a..<b step 2) {
+                              ~~~~~~~~~~~~
+2 errors, 0 warnings
             """
             )
     }
 
     @Test
-    fun calledOnSteppedDownToLoop() {
+    fun calledOnConstantSteppedLoop() {
         lint().files(
             kotlin(
                 """
                 package test
 
-                fun test(a: Int, b: Int) {
-                    for (i in a downTo b step 2) {
+                fun test() {
+                    for (i in 0 until 10 step 2) {
+                        println(i)
+                    }
+                    for (i in 0..<10 step 2) {
                         println(i)
                     }
                 }
@@ -204,22 +135,28 @@
             .expect(
                 """
 src/test/test.kt:5: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in a downTo b step 2) {
+                    for (i in 0 until 10 step 2) {
                               ~~~~~~~~~~~~~~~~~
-1 errors, 0 warnings
+src/test/test.kt:8: Error: stepping the integer range by 2. [SteppedForLoop]
+                    for (i in 0..<10 step 2) {
+                              ~~~~~~~~~~~~~
+2 errors, 0 warnings
             """
             )
     }
 
     @Test
-    fun calledOnStepAsFunctionLoop() {
+    fun calledOnUnitSteppedLoop() {
         lint().files(
             kotlin(
                 """
                 package test
 
                 fun test(a: Int, b: Int) {
-                    for (i in (a..b).step(2)) {
+                    for (i in a until b step 1) {
+                        println(i)
+                    }
+                    for (i in a..<b step 1) {
                         println(i)
                     }
                 }
@@ -229,10 +166,45 @@
             .run()
             .expect(
                 """
-src/test/test.kt:5: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in (a..b).step(2)) {
-                              ~~~~~~~~~~~~~~
-1 errors, 0 warnings
+src/test/test.kt:5: Error: stepping the integer range by 1. [SteppedForLoop]
+                    for (i in a until b step 1) {
+                              ~~~~~~~~~~~~~~~~
+src/test/test.kt:8: Error: stepping the integer range by 1. [SteppedForLoop]
+                    for (i in a..<b step 1) {
+                              ~~~~~~~~~~~~
+2 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun calledOnExpressionSteppedLoop() {
+        lint().files(
+            kotlin(
+                """
+                package test
+
+                fun test(a: Int, b: Int, c: Int) {
+                    for (i in a until b step (c / 2)) {
+                        println(i)
+                    }
+                    for (i in a..<b step (c / 2)) {
+                        println(i)
+                    }
+                }
+            """
+            )
+        )
+            .run()
+            .expect(
+                """
+src/test/test.kt:5: Error: stepping the integer range by (c / 2). [SteppedForLoop]
+                    for (i in a until b step (c / 2)) {
+                              ~~~~~~~~~~~~~~~~~~~~~~
+src/test/test.kt:8: Error: stepping the integer range by (c / 2). [SteppedForLoop]
+                    for (i in a..<b step (c / 2)) {
+                              ~~~~~~~~~~~~~~~~~~
+2 errors, 0 warnings
             """
             )
     }
@@ -245,22 +217,22 @@
                 package test
 
                 fun test(a: UInt, b: UInt) {
-                    for (i in a..b step 2) {
+                    for (i in a until b step 2) {
                         println(i)
                     }
                 }
                 fun test(a: Char, b: Char) {
-                    for (i in a..b step 2) {
+                    for (i in a until b step 2) {
                         println(i)
                     }
                 }
                 fun test(a: Long, b: Long) {
-                    for (i in a..b step 2L) {
+                    for (i in a until b step 2L) {
                         println(i)
                     }
                 }
                 fun test(a: ULong, b: ULong) {
-                    for (i in a..b step 2L) {
+                    for (i in a until b step 2L) {
                         println(i)
                     }
                 }
@@ -271,92 +243,20 @@
             .expect(
                 """
 src/test/test.kt:5: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in a..b step 2) {
-                              ~~~~~~~~~~~
+                    for (i in a until b step 2) {
+                              ~~~~~~~~~~~~~~~~
 src/test/test.kt:10: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in a..b step 2) {
-                              ~~~~~~~~~~~
-src/test/test.kt:15: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in a..b step 2L) {
-                              ~~~~~~~~~~~~
-src/test/test.kt:20: Error: stepping the integer range by 2. [SteppedForLoop]
-                    for (i in a..b step 2L) {
-                              ~~~~~~~~~~~~
+                    for (i in a until b step 2) {
+                              ~~~~~~~~~~~~~~~~
+src/test/test.kt:15: Error: stepping the integer range by 2L. [SteppedForLoop]
+                    for (i in a until b step 2L) {
+                              ~~~~~~~~~~~~~~~~~
+src/test/test.kt:20: Error: stepping the integer range by 2L. [SteppedForLoop]
+                    for (i in a until b step 2L) {
+                              ~~~~~~~~~~~~~~~~~
 4 errors, 0 warnings
             """
             )
     }
-
-    @Test
-    fun skippedOnStepMethodOnUnknownTypes() {
-        lint().files(
-            kotlin(
-                """
-                package test
-
-                class RangeProducerForTest(val start: Int, val end: Int) {
-                    fun step(i: Int) = start..end step i
-                }
-
-                fun step(i: Int) = 0..10 step i
-
-                fun test(a: Int, b: Int) {
-                    for (i in RangeProducerForTest(a, b).step(2)) {
-                        println(i)
-                    }
-                    for (i in step(2)) {
-                        println(i)
-                    }
-                }
-            """
-            )
-        )
-            .run()
-            .expectClean()
-    }
-
-    @Test
-    fun skippedOnStepWithNonIntegerArgumentsTypes() {
-        lint().files(
-            kotlin(
-                """
-                package test
-
-                fun IntProgression.step(s: String) =
-                    IntProgression.fromClosedRange(first, last, s.length)
-
-                fun test(a: Int, b: Int) {
-                    for (i in 0..10 step "abc") {
-                        println(i)
-                    }
-                }
-            """
-            )
-        )
-            .run()
-            .expectClean()
-    }
-
-    @Test
-    fun skippedOnStepWithMoreThanOneArgument() {
-        lint().files(
-            kotlin(
-                """
-                package test
-
-                fun IntProgression.step(a: Int, b: Int) =
-                    IntProgression.fromClosedRange(first, last, a + b)
-
-                fun test(a: Int, b: Int) {
-                    for (i in (0..10).step(1, 3)) {
-                        println(i)
-                    }
-                }
-            """
-            )
-        )
-            .run()
-            .expectClean()
-    }
 }
 /* ktlint-enable max-line-length */
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ChipTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ChipTest.kt
index 9b81597..1694d7c 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ChipTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ChipTest.kt
@@ -17,7 +17,9 @@
 package androidx.compose.material
 
 import android.os.Build
+import androidx.compose.foundation.horizontalScroll
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -25,6 +27,10 @@
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -49,8 +55,10 @@
 import androidx.compose.ui.test.assertHasClickAction
 import androidx.compose.ui.test.assertHeightIsAtLeast
 import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTouchHeightIsEqualTo
 import androidx.compose.ui.test.assertTouchWidthIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
@@ -59,6 +67,7 @@
 import androidx.compose.ui.test.hasClickAction
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.text.TextStyle
@@ -264,6 +273,44 @@
     }
 
     @Test
+    fun horizontalPadding_withCustomWidth_filterChip() {
+        val chipWidth = 200.dp
+        rule.setMaterialContent {
+            FilterChip(
+                selected = false,
+                onClick = {},
+                content = { Text("Test chip") },
+                modifier = Modifier.width(chipWidth),
+                leadingIcon = {
+                    Icon(
+                        Icons.Filled.Settings,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.testTag("Leading").size(ChipDefaults.LeadingIconSize)
+                    )
+                },
+                trailingIcon = {
+                    Icon(
+                        Icons.Filled.Settings,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.testTag("Trailing").size(ChipDefaults.LeadingIconSize)
+                    )
+                },
+            )
+        }
+
+        rule.onNodeWithTag("Leading", useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(4.dp)
+        rule.onNodeWithText("Test chip", useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(
+                4.dp + ChipDefaults.LeadingIconSize + 8.dp
+            )
+        rule.onNodeWithTag("Trailing", useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(
+                chipWidth - 8.dp - ChipDefaults.LeadingIconSize
+            )
+    }
+
+    @Test
     fun contentColorIsCorrect() {
         var onSurface = Color.Unspecified
         var content = Color.Unspecified
@@ -345,6 +392,54 @@
     }
 
     @Test
+    fun correctDimensionsInScrollableRow_filterChip() {
+        val labelWidth = 64.dp
+        val horizontalPadding = 12.dp
+        rule.setMaterialContent {
+            Row(Modifier.horizontalScroll(rememberScrollState())) {
+                FilterChip(
+                    selected = false,
+                    onClick = {},
+                    content = { Box(Modifier.width(labelWidth)) },
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .assertHeightIsEqualTo(ChipDefaults.MinHeight)
+            .assertWidthIsEqualTo(labelWidth + horizontalPadding * 2)
+    }
+
+    @Test
+    fun longLabelDoesNotHideTrailingIcon_filterChip() {
+        rule.setMaterialContent {
+            FilterChip(
+                selected = false,
+                onClick = {},
+                leadingIcon = {
+                    Icon(
+                        imageVector = Icons.Filled.Settings,
+                        contentDescription = "Localized Description",
+                        modifier = Modifier.size(ChipDefaults.LeadingIconSize)
+                    )
+                },
+                trailingIcon = {
+                    Icon(
+                        imageVector = Icons.Filled.Settings,
+                        contentDescription = "Localized Description",
+                        modifier = Modifier.testTag("Trailing").size(ChipDefaults.LeadingIconSize)
+                    )
+                }
+            ) {
+                Text("Long long long long long long long long long long long long long long" +
+                    "long long long long long long long long long long long long long long long")
+            }
+        }
+
+        rule.onNodeWithTag("Trailing", useUnmergedTree = true).assertIsDisplayed()
+    }
+
+    @Test
     fun clickableInMinimumTouchTarget() {
         var clicked = false
         rule.setMaterialContent {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
index d3a929e..b5f6dc0 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
@@ -214,6 +215,7 @@
             ) {
                 Row(
                     Modifier
+                        .width(IntrinsicSize.Max)
                         .defaultMinSize(
                             minHeight = ChipDefaults.MinHeight
                         )
@@ -274,7 +276,12 @@
                         }
                         Spacer(Modifier.width(LeadingIconEndSpacing))
                     }
-                    content()
+                    Row(
+                        modifier = Modifier.weight(1f),
+                        horizontalArrangement = Arrangement.Start,
+                        verticalAlignment = Alignment.CenterVertically,
+                        content = content,
+                    )
                     if (trailingIcon != null) {
                         Spacer(Modifier.width(TrailingIconSpacing))
                         trailingIcon()
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 3275ebb..3c5de3a 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1321,8 +1321,8 @@
   }
 
   public final class TooltipKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 3275ebb..3c5de3a 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1321,8 +1321,8 @@
   }
 
   public final class TooltipKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
index 6d72666..a3c631429 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -69,6 +69,7 @@
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.swipeRight
 import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
@@ -387,6 +388,60 @@
     }
 
     @Test
+    fun centerAlignedTopAppBar_longTextDoesNotOverflowToActions() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CenterAlignedTopAppBar(
+                    title = {
+                        Text(
+                            text = "This is a very very very very long title",
+                            modifier = Modifier.testTag(TitleTestTag),
+                            overflow = TextOverflow.Ellipsis,
+                            maxLines = 1
+                        )
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        val actionsBounds = rule.onNodeWithTag(ActionsTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+
+        // Check that the title does not render over the actions.
+        assertThat(titleBounds.right).isLessThan(actionsBounds.left)
+    }
+
+    @Test
+    fun centerAlignedTopAppBar_longTextDoesNotOverflowToNavigation() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CenterAlignedTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text(
+                            text = "This is a very very very very long title",
+                            modifier = Modifier.testTag(TitleTestTag),
+                            overflow = TextOverflow.Ellipsis,
+                            maxLines = 1
+                        )
+                    }
+
+                )
+            }
+        }
+        val navigationIconBounds =
+            rule.onNodeWithTag(NavigationIconTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+
+        // Check that the title does not render over the navigation icon.
+        assertThat(titleBounds.left).isGreaterThan(navigationIconBounds.right)
+    }
+
+    @Test
     fun centerAlignedTopAppBar_titleDefaultStyle() {
         var textStyle: TextStyle? = null
         var expectedTextStyle: TextStyle? = null
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ChipTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ChipTest.kt
index 2dd3963..19cef5d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ChipTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ChipTest.kt
@@ -17,16 +17,21 @@
 package androidx.compose.material3
 
 import android.os.Build
+import androidx.compose.foundation.horizontalScroll
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Done
 import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material3.tokens.AssistChipTokens
 import androidx.compose.material3.tokens.FilterChipTokens
 import androidx.compose.material3.tokens.InputChipTokens
@@ -55,6 +60,7 @@
 import androidx.compose.ui.test.assertHasClickAction
 import androidx.compose.ui.test.assertHeightIsAtLeast
 import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.assertIsNotSelected
@@ -68,6 +74,7 @@
 import androidx.compose.ui.test.hasClickAction
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.text.TextStyle
@@ -223,6 +230,44 @@
     }
 
     @Test
+    fun horizontalPadding_assistChip_customWidth() {
+        val chipWidth = 200.dp
+        val horizontalPadding = 8.dp
+        rule.setMaterialContent(lightColorScheme()) {
+            AssistChip(
+                onClick = {},
+                label = { Text("Test chip") },
+                modifier = Modifier.width(chipWidth),
+                leadingIcon = {
+                    Icon(
+                        Icons.Filled.Settings,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.testTag("Leading").size(AssistChipDefaults.IconSize)
+                    )
+                },
+                trailingIcon = {
+                    Icon(
+                        Icons.Filled.Settings,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.testTag("Trailing").size(AssistChipDefaults.IconSize)
+                    )
+                },
+            )
+        }
+
+        rule.onNodeWithTag("Leading", useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(horizontalPadding)
+        rule.onNodeWithText("Test chip", useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(
+                horizontalPadding + AssistChipDefaults.IconSize + horizontalPadding
+            )
+        rule.onNodeWithTag("Trailing", useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(
+                chipWidth - horizontalPadding - AssistChipDefaults.IconSize
+            )
+    }
+
+    @Test
     fun labelContentColor_assistChip() {
         var expectedLabelColor = Color.Unspecified
         var contentColor = Color.Unspecified
@@ -466,6 +511,53 @@
     }
 
     @Test
+    fun correctDimensionsInScrollableRow_filterChip() {
+        val labelWidth = 64.dp
+        val horizontalPadding = 16.dp
+        rule.setMaterialContent(lightColorScheme()) {
+            Row(Modifier.horizontalScroll(rememberScrollState())) {
+                FilterChip(
+                    selected = false,
+                    onClick = {},
+                    label = { Box(Modifier.width(labelWidth)) },
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .assertHeightIsEqualTo(FilterChipDefaults.Height)
+            .assertWidthIsEqualTo(labelWidth + horizontalPadding * 2)
+    }
+
+    @Test
+    fun longLabelDoesNotHideTrailingIcon_filterChip() {
+        rule.setMaterialContent(lightColorScheme()) {
+            FilterChip(
+                selected = false,
+                onClick = {},
+                label = { Text("Long long long long long long long long long long long long" +
+                    "long long long long long long long long long long long long long long long") },
+                leadingIcon = {
+                    Icon(
+                        imageVector = Icons.Filled.Settings,
+                        contentDescription = "Localized Description",
+                        modifier = Modifier.size(FilterChipDefaults.IconSize)
+                    )
+                },
+                trailingIcon = {
+                    Icon(
+                        imageVector = Icons.Filled.Settings,
+                        contentDescription = "Localized Description",
+                        modifier = Modifier.testTag("Trailing").size(FilterChipDefaults.IconSize)
+                    )
+                }
+            )
+        }
+
+        rule.onNodeWithTag("Trailing", useUnmergedTree = true).assertIsDisplayed()
+    }
+
+    @Test
     fun labelContentColor_unselectedFilterChip() {
         var expectedLabelColor = Color.Unspecified
         var contentColor = Color.Unspecified
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
index 99e10db..3b039c7 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
@@ -26,10 +26,11 @@
 internal actual fun TooltipPopup(
     popupPositionProvider: PopupPositionProvider,
     onDismissRequest: () -> Unit,
+    focusable: Boolean,
     content: @Composable () -> Unit
 ) = Popup(
     popupPositionProvider = popupPositionProvider,
     onDismissRequest = onDismissRequest,
     content = content,
-    properties = PopupProperties(focusable = true)
+    properties = PopupProperties(focusable = focusable)
 )
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index d85d841..2a2aef5 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -1358,7 +1358,7 @@
                 0
             }
 
-        val layoutHeight = heightPx.roundToInt()
+        val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()
 
         layout(constraints.maxWidth, layoutHeight) {
             // Navigation icon
@@ -1370,7 +1370,24 @@
             // Title
             titlePlaceable.placeRelative(
                 x = when (titleHorizontalArrangement) {
-                    Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
+                    Arrangement.Center -> {
+                        var baseX = (constraints.maxWidth - titlePlaceable.width) / 2
+                        if (baseX < navigationIconPlaceable.width) {
+                            // May happen if the navigation is wider than the actions and the
+                            // title is long. In this case, prioritize showing more of the title by
+                            // offsetting it to the right.
+                            baseX += (navigationIconPlaceable.width - baseX)
+                        } else if (baseX + titlePlaceable.width >
+                            constraints.maxWidth - actionIconsPlaceable.width
+                        ) {
+                            // May happen if the actions are wider than the navigation and the title
+                            // is long. In this case, offset to the left.
+                            baseX += ((constraints.maxWidth - actionIconsPlaceable.width) -
+                                (baseX + titlePlaceable.width))
+                        }
+                        baseX
+                    }
+
                     Arrangement.End ->
                         constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
                     // Arrangement.Start.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
index 4ddcc13..6f2b7ac 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -1372,10 +1373,8 @@
         enabled = enabled,
         shape = shape,
         color = colors.containerColor(enabled, selected).value,
-        tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value
-            ?: 0.dp,
-        shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value
-            ?: 0.dp,
+        tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp,
+        shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
         border = border,
         interactionSource = interactionSource,
     ) {
@@ -1413,6 +1412,7 @@
     ) {
         Row(
             Modifier
+                .width(IntrinsicSize.Max)
                 .defaultMinSize(minHeight = minHeight)
                 .padding(paddingValues),
             horizontalArrangement = Arrangement.Start,
@@ -1426,7 +1426,11 @@
                 )
             }
             Spacer(Modifier.width(HorizontalElementsPadding))
-            label()
+            Row(
+                modifier = Modifier.weight(1f),
+                horizontalArrangement = Arrangement.Start,
+                verticalAlignment = Alignment.CenterVertically
+            ) { label() }
             Spacer(Modifier.width(HorizontalElementsPadding))
             if (trailingIcon != null) {
                 CompositionLocalProvider(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 38a673c..6678c9e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -24,6 +24,7 @@
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
@@ -56,6 +57,8 @@
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
 import androidx.compose.ui.semantics.onLongClick
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
@@ -85,6 +88,11 @@
  *
  * @param tooltip the composable that will be used to populate the tooltip's content.
  * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
+ * the tooltip will consume touch events while it's shown and will have accessibility
+ * focus move to the first element of the component. When false, the tooltip
+ * won't consume touch events while it's shown but assistive-tech users will need
+ * to swipe or drag to get to the first element of the component.
  * @param tooltipState handles the state of the tooltip's visibility.
  * @param shape the [Shape] that should be applied to the tooltip container.
  * @param containerColor [Color] that will be applied to the tooltip's container.
@@ -96,6 +104,7 @@
 fun PlainTooltipBox(
     tooltip: @Composable () -> Unit,
     modifier: Modifier = Modifier,
+    focusable: Boolean = true,
     tooltipState: PlainTooltipState = rememberPlainTooltipState(),
     shape: Shape = TooltipDefaults.plainTooltipContainerShape,
     containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
@@ -113,6 +122,7 @@
             )
         },
         modifier = modifier,
+        focusable = focusable,
         tooltipState = tooltipState,
         shape = shape,
         containerColor = containerColor,
@@ -138,6 +148,11 @@
  *
  * @param text the message to be displayed in the center of the tooltip.
  * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
+ * the tooltip will consume touch events while it's shown and will have accessibility
+ * focus move to the first element of the component. When false, the tooltip
+ * won't consume touch events while it's shown but assistive-tech users will need
+ * to swipe or drag to get to the first element of the component.
  * @param tooltipState handles the state of the tooltip's visibility.
  * @param title An optional title for the tooltip.
  * @param action An optional action for the tooltip.
@@ -150,6 +165,7 @@
 fun RichTooltipBox(
     text: @Composable () -> Unit,
     modifier: Modifier = Modifier,
+    focusable: Boolean = false,
     title: (@Composable () -> Unit)? = null,
     action: (@Composable () -> Unit)? = null,
     tooltipState: RichTooltipState = rememberRichTooltipState(action != null),
@@ -176,6 +192,7 @@
         elevation = RichTooltipTokens.ContainerElevation,
         maxWidth = RichTooltipMaxWidth,
         modifier = modifier,
+        focusable = focusable,
         content = content
     )
 }
@@ -189,6 +206,7 @@
     tooltipContent: @Composable () -> Unit,
     tooltipPositionProvider: PopupPositionProvider,
     modifier: Modifier,
+    focusable: Boolean,
     shape: Shape,
     tooltipState: TooltipState,
     containerColor: Color,
@@ -252,7 +270,8 @@
                     if (tooltipState.isVisible) {
                         coroutineScope.launch { tooltipState.dismiss() }
                     }
-                }
+                },
+                focusable = focusable
             ) {
                 Surface(
                     modifier = modifier
@@ -262,7 +281,10 @@
                             minHeight = TooltipMinHeight
                         )
                         .animateTooltip(transition)
-                        .semantics { paneTitle = tooltipPaneDescription },
+                        .semantics {
+                            liveRegion = LiveRegionMode.Assertive
+                            paneTitle = tooltipPaneDescription
+                        },
                     shape = shape,
                     color = containerColor,
                     shadowElevation = elevation,
@@ -371,7 +393,7 @@
         @Composable get() = PlainTooltipTokens.ContainerColor.toColor()
 
     /**
-     * The default [color] for the content within the [PlainTooltipBox].
+     * The default [Color] for the content within the [PlainTooltipBox].
      */
     val plainTooltipContentColor: Color
         @Composable get() = PlainTooltipTokens.SupportingTextColor.toColor()
@@ -783,6 +805,7 @@
 internal expect fun TooltipPopup(
     popupPositionProvider: PopupPositionProvider,
     onDismissRequest: () -> Unit,
+    focusable: Boolean,
     content: @Composable () -> Unit
 )
 
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
index 95a6f96..51ffed0 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
@@ -25,6 +25,7 @@
 internal actual fun TooltipPopup(
     popupPositionProvider: PopupPositionProvider,
     onDismissRequest: () -> Unit,
+    focusable: Boolean,
     content: @Composable () -> Unit
 ) = Popup(
     popupPositionProvider = popupPositionProvider,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt
index a4f89c5..09c150e 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt
@@ -119,7 +119,12 @@
 
         val newState = TextFieldValue(
             annotatedString = mBuffer.toAnnotatedString(),
-            selection = mBuffer.selection,
+            // preserve original reversed selection when creating new state.
+            // otherwise the text range may flicker to un-reversed for a frame,
+            // which can cause haptics and handles to be crossed.
+            selection = mBuffer.selection.run {
+                takeUnless { mBufferState.selection.reversed } ?: TextRange(max, min)
+            },
             composition = mBuffer.composition
         )
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/DialogDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/DialogDemo.kt
index 442665e..c3878af 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/DialogDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/DialogDemo.kt
@@ -35,6 +35,11 @@
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+
+private var expandedWidthInit = false
+private var expandedHeightInit = false
+private var usePlatformDefaultWidthInit = false
 
 @Composable
 fun DialogDemo() {
@@ -42,11 +47,22 @@
     var shape by remember { mutableStateOf(RectangleShape) }
     var elevation by remember { mutableStateOf(8.dp) }
     var openDialog by remember { mutableStateOf(true) }
+    var expandedWidth by remember { mutableStateOf(expandedWidthInit) }
+    var expandedHeight by remember { mutableStateOf(expandedHeightInit) }
+    var usePlatformDefaultWidth by remember { mutableStateOf(usePlatformDefaultWidthInit) }
 
+    TextButton(onClick = { openDialog = !openDialog }) {
+        Text("Tap anywhere to reopen dialog")
+    }
     if (openDialog) {
-        Dialog(onDismissRequest = { openDialog = false }) {
+        Dialog(
+            onDismissRequest = { openDialog = false },
+            properties = DialogProperties(usePlatformDefaultWidth = usePlatformDefaultWidth)
+        ) {
+            val width = if (expandedWidth) { 1500.dp } else { 300.dp }
+            val height = if (expandedHeight) { 600.dp } else { 400.dp }
             Card(
-                modifier = Modifier.size(350.dp, 200.dp).padding(10.dp),
+                modifier = Modifier.size(width, height).padding(10.dp),
                 elevation = elevation,
                 shape = shape
             ) {
@@ -64,7 +80,31 @@
                             }
                         }
                     ) {
-                        Text("Toggle shape")
+                        Text("Toggle corners")
+                    }
+                    TextButton(
+                        onClick = {
+                            expandedWidth = !expandedWidth
+                            expandedWidthInit = expandedWidth
+                        }
+                    ) {
+                        Text("Toggle width")
+                    }
+                    TextButton(
+                        onClick = {
+                            expandedHeight = !expandedHeight
+                            expandedHeightInit = expandedHeight
+                        }
+                    ) {
+                        Text("Toggle height")
+                    }
+                    TextButton(
+                        onClick = {
+                            usePlatformDefaultWidth = !usePlatformDefaultWidth
+                            usePlatformDefaultWidthInit = usePlatformDefaultWidthInit
+                        }
+                    ) {
+                        Text("Toggle widthlock")
                     }
                     Row(verticalAlignment = Alignment.CenterVertically) {
                         TextButton(onClick = { elevation -= 1.dp }) {
@@ -75,6 +115,8 @@
                             Text("+1")
                         }
                     }
+                    Text("Current size: [$width, $height]")
+                    Text("usePlatformDefaultWidth = $usePlatformDefaultWidth")
                 }
             }
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
index 1dec8ea..d98d807 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
@@ -20,12 +20,14 @@
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.test.TestActivity
@@ -36,6 +38,7 @@
 import androidx.compose.ui.test.onFirst
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.height
 import androidx.test.espresso.Espresso
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -315,7 +318,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun canFillScreenWidth_dependingOnProperty() {
         var box1Width = 0
@@ -339,4 +341,38 @@
             Truth.assertThat(box2Width).isLessThan(box1Width)
         }
     }
+
+    @Test
+    fun canChangeSize() {
+        var width by mutableStateOf(10.dp)
+        var usePlatformDefaultWidth by mutableStateOf(false)
+        var actualWidth = 0
+
+        rule.setContent {
+            Dialog(
+                onDismissRequest = {},
+                properties = DialogProperties(usePlatformDefaultWidth = usePlatformDefaultWidth)
+            ) {
+                Box(Modifier.size(width, 150.dp).onSizeChanged { actualWidth = it.width })
+            }
+        }
+        rule.runOnIdle {
+            Truth.assertThat(actualWidth).isEqualTo((10 * rule.density.density).roundToInt())
+        }
+        width = 20.dp
+        rule.runOnIdle {
+            Truth.assertThat(actualWidth).isEqualTo((20 * rule.density.density).roundToInt())
+        }
+
+        usePlatformDefaultWidth = true
+
+        width = 30.dp
+        rule.runOnIdle {
+            Truth.assertThat(actualWidth).isEqualTo((30 * rule.density.density).roundToInt())
+        }
+        width = 40.dp
+        rule.runOnIdle {
+            Truth.assertThat(actualWidth).isEqualTo((40 * rule.density.density).roundToInt())
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
index 36d5ef1..df8fecb 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/PopupTest.kt
@@ -385,6 +385,40 @@
         }
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun canChangeSize() {
+        var width by mutableStateOf(10.dp)
+        var usePlatformDefaultWidth by mutableStateOf(false)
+        var actualWidth = 0
+
+        rule.setContent {
+            Popup(
+                properties = PopupProperties(usePlatformDefaultWidth = usePlatformDefaultWidth)
+            ) {
+                Box(Modifier.size(width, 150.dp).onSizeChanged { actualWidth = it.width })
+            }
+        }
+        rule.runOnIdle {
+            assertThat(actualWidth).isEqualTo((10 * rule.density.density).roundToInt())
+        }
+        width = 20.dp
+        rule.runOnIdle {
+            assertThat(actualWidth).isEqualTo((20 * rule.density.density).roundToInt())
+        }
+
+        usePlatformDefaultWidth = true
+
+        width = 30.dp
+        rule.runOnIdle {
+            assertThat(actualWidth).isEqualTo((30 * rule.density.density).roundToInt())
+        }
+        width = 40.dp
+        rule.runOnIdle {
+            assertThat(actualWidth).isEqualTo((40 * rule.density.density).roundToInt())
+        }
+    }
+
     @Test
     fun didNotMeasureTooSmallLast() {
         rule.setContent {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
index 346e6f3..1ecf799 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
@@ -247,8 +247,10 @@
         super.internalOnLayout(changed, left, top, right, bottom)
         // Now set the content size as fixed layout params, such that ViewRootImpl knows
         // the exact window size.
-        val child = getChildAt(0) ?: return
-        window.setLayout(child.measuredWidth, child.measuredHeight)
+        if (!usePlatformDefaultWidth) {
+            val child = getChildAt(0) ?: return
+            window.setLayout(child.measuredWidth, child.measuredHeight)
+        }
     }
 
     private val displayWidth: Int
@@ -405,6 +407,14 @@
         this.properties = properties
         setSecurePolicy(properties.securePolicy)
         setLayoutDirection(layoutDirection)
+        if (properties.usePlatformDefaultWidth && !dialogLayout.usePlatformDefaultWidth) {
+            // Undo fixed size in internalOnLayout, which would suppress size changes when
+            // usePlatformDefaultWidth is true.
+            window?.setLayout(
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.WRAP_CONTENT
+            )
+        }
         dialogLayout.usePlatformDefaultWidth = properties.usePlatformDefaultWidth
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
             @OptIn(ExperimentalComposeUiApi::class)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 08611787..c078408 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -483,10 +483,12 @@
         super.internalOnLayout(changed, left, top, right, bottom)
         // Now set the content size as fixed layout params, such that ViewRootImpl knows
         // the exact window size.
-        val child = getChildAt(0) ?: return
-        params.width = child.measuredWidth
-        params.height = child.measuredHeight
-        popupLayoutHelper.updateViewLayout(windowManager, this, params)
+        if (!properties.usePlatformDefaultWidth) {
+            val child = getChildAt(0) ?: return
+            params.width = child.measuredWidth
+            params.height = child.measuredHeight
+            popupLayoutHelper.updateViewLayout(windowManager, this, params)
+        }
     }
 
     private val displayWidth: Int
@@ -562,6 +564,13 @@
         layoutDirection: LayoutDirection
     ) {
         this.onDismissRequest = onDismissRequest
+        if (properties.usePlatformDefaultWidth && !this.properties.usePlatformDefaultWidth) {
+            // Undo fixed size in internalOnLayout, which would suppress size changes when
+            // usePlatformDefaultWidth is true.
+            params.width = WindowManager.LayoutParams.WRAP_CONTENT
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT
+            popupLayoutHelper.updateViewLayout(windowManager, this, params)
+        }
         this.properties = properties
         this.testTag = testTag
         setIsFocusable(properties.focusable)
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
index 0a608ac..000c9c2 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
@@ -57,27 +57,24 @@
 import java.util.Set;
 
 /**
- * This class allows you to define programmatically a set of constraints to be used with
- *  {@link ConstraintLayout}.
- * <p>
- * For details about Constraint behaviour see {@link ConstraintLayout}.
- * It lets you create and save constraints, and apply them to an existing ConstraintLayout.
- * ConstraintsSet can be created in various ways:
+ * Defines a set of constraints to be used with {@link ConstraintLayout}.
+ *
+ * <p>{@code ConstraintSet} enables you create and save constraints and apply
+ * them to an existing {@code ConstraintLayout}. For details about constraint
+ * behaviour, see {@link ConstraintLayout}.</p>
+ *
+ * <p>{@code ConstraintsSet} can be created in various ways:</p>
  * <ul>
- * <li>
- * Manually <br> {@code c = new ConstraintSet(); c.connect(....);}
- * </li>
- * <li>
- * from a R.layout.* object <br> {@code c.clone(context, R.layout.layout1);}
- * </li>
- * <li>
- * from a ConstraintLayout <br> {@code c.clone(constraintLayout);}
- * </li>
+ *     <li>Manually &mdash;
+ *         {@code c = new ConstraintSet(); c.connect(...);}</li>
+ *     <li>From an {@code R.layout.*} object &mdash;
+ *         {@code c.clone(context, R.layout.layout1);}</li>
+ *     <li>From a {@code ConstraintLayout} &mdash;
+ *         {@code c.clone(constraintLayout);}</li>
  * </ul>
- * <p>
- *  Example code:
- *  <pre>
- *      import android.content.Context;
+ *
+ * <p>Example code:</p>
+ * <pre>import android.content.Context;
  *      import android.os.Bundle;
  *      import android.support.constraint.ConstraintLayout;
  *      import android.support.constraint.ConstraintSet;
@@ -86,32 +83,30 @@
  *      import android.view.View;
  *
  *      public class MainActivity extends AppCompatActivity {
- *          ConstraintSet mConstraintSet1 = new ConstraintSet(); // create a Constraint Set
- *          ConstraintSet mConstraintSet2 = new ConstraintSet(); // create a Constraint Set
- *          ConstraintLayout mConstraintLayout; // cache the ConstraintLayout
+ *          ConstraintSet mConstraintSet1 = new ConstraintSet(); // Create a ConstraintSet.
+ *          ConstraintSet mConstraintSet2 = new ConstraintSet(); // Create a ConstraintSet.
+ *          ConstraintLayout mConstraintLayout; // Cache the ConstraintLayout.
  *          boolean mOld = true;
  *
  *
  *          protected void onCreate(Bundle savedInstanceState) {
  *              super.onCreate(savedInstanceState);
  *              Context context = this;
- *              mConstraintSet2.clone(context, R.layout.state2); // get constraints from layout
+ *              mConstraintSet2.clone(context, R.layout.state2); // Get constraints from layout.
  *              setContentView(R.layout.state1);
  *              mConstraintLayout = (ConstraintLayout) findViewById(R.id.activity_main);
- *              mConstraintSet1.clone(mConstraintLayout); // get constraints from ConstraintSet
+ *              mConstraintSet1.clone(mConstraintLayout); // Get constraints from ConstraintSet.
  *          }
  *
  *          public void foo(View view) {
  *              TransitionManager.beginDelayedTransition(mConstraintLayout);
  *              if (mOld = !mOld) {
- *                  mConstraintSet1.applyTo(mConstraintLayout); // set new constraints
+ *                  mConstraintSet1.applyTo(mConstraintLayout); // Set new constraints.
  *              }  else {
- *                  mConstraintSet2.applyTo(mConstraintLayout); // set new constraints
+ *                  mConstraintSet2.applyTo(mConstraintLayout); // Set new constraints.
  *              }
  *          }
- *      }
- *  <pre/>
- * <p/>
+ *      }</pre>
  */
 public class ConstraintSet {
     private static final String TAG = "ConstraintSet";
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Guideline.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Guideline.java
index c4eda2a..577a8d7 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Guideline.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/Guideline.java
@@ -61,29 +61,30 @@
  * and {@link ConstraintSet#setGuidelinePercent} functions in {@link ConstraintSet}.
  * <p>
  *   Example of a {@code Button} constrained to a vertical {@code Guideline}:
- *   <pre>
- *     <androidx.constraintlayout.widget.ConstraintLayout
- *         xmlns:android="http://schemas.android.com/apk/res/android"
- *         xmlns:app="http://schemas.android.com/apk/res-auto"
- *         xmlns:tools="http://schemas.android.com/tools"
- *         android:layout_width="match_parent"
- *         android:layout_height="match_parent">
+ *   <pre>{@code
+ *          <androidx.constraintlayout.widget.ConstraintLayout
+ *              xmlns:android="http://schemas.android.com/apk/res/android"
+ *              xmlns:app="http://schemas.android.com/apk/res-auto"
+ *              xmlns:tools="http://schemas.android.com/tools"
+ *              android:layout_width="match_parent"
+ *              android:layout_height="match_parent">
  *
- *         <androidx.constraintlayout.widget.Guideline
- *             android:layout_width="wrap_content"
- *             android:layout_height="wrap_content"
- *             android:id="@+id/guideline"
- *             app:layout_constraintGuide_begin="100dp"
- *             android:orientation="vertical"/>
- *         <Button
- *             android:text="Button"
- *             android:layout_width="wrap_content"
- *             android:layout_height="wrap_content"
- *             android:id="@+id/button"
- *             app:layout_constraintLeft_toLeftOf="@+id/guideline"
- *             android:layout_marginTop="16dp"
- *             app:layout_constraintTop_toTopOf="parent" />
- *     </androidx.constraintlayout.widget.ConstraintLayout>
+ *              <androidx.constraintlayout.widget.Guideline
+ *                  android:layout_width="wrap_content"
+ *                  android:layout_height="wrap_content"
+ *                  android:id="@+id/guideline"
+ *                  app:layout_constraintGuide_begin="100dp"
+ *                  android:orientation="vertical"/>
+ *              <Button
+ *                  android:text="Button"
+ *                  android:layout_width="wrap_content"
+ *                  android:layout_height="wrap_content"
+ *                  android:id="@+id/button"
+ *                  app:layout_constraintLeft_toLeftOf="@+id/guideline"
+ *                  android:layout_marginTop="16dp"
+ *                  app:layout_constraintTop_toTopOf="parent" />
+ *          </androidx.constraintlayout.widget.ConstraintLayout>
+ *        }
  *  </pre>
  * <p/>
  */
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/core/core-performance-play-services/api/1.11.0-beta02.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to core/core-performance-play-services/api/1.11.0-beta02.txt
diff --git a/core/core-performance-play-services/api/current.txt b/core/core-performance-play-services/api/current.txt
new file mode 100644
index 0000000..85c5ff4
--- /dev/null
+++ b/core/core-performance-play-services/api/current.txt
@@ -0,0 +1,17 @@
+// Signature format: 4.0
+package androidx.core.performance.play.services {
+
+  public final class PlayServicesDevicePerformanceSupplier implements androidx.core.performance.DevicePerformanceSupplier {
+    ctor public PlayServicesDevicePerformanceSupplier();
+    method public static androidx.core.performance.DevicePerformance createDevicePerformance(android.content.Context context);
+    method public kotlinx.coroutines.flow.Flow<java.lang.Integer> getMediaPerformanceClassFlow();
+    property public kotlinx.coroutines.flow.Flow<java.lang.Integer> mediaPerformanceClassFlow;
+    field public static final androidx.core.performance.play.services.PlayServicesDevicePerformanceSupplier.Companion Companion;
+  }
+
+  public static final class PlayServicesDevicePerformanceSupplier.Companion {
+    method public androidx.core.performance.DevicePerformance createDevicePerformance(android.content.Context context);
+  }
+
+}
+
diff --git a/camera/camera-camera2/api/res-1.3.0-beta01.txt b/core/core-performance-play-services/api/res-1.11.0-beta02.txt
similarity index 100%
rename from camera/camera-camera2/api/res-1.3.0-beta01.txt
rename to core/core-performance-play-services/api/res-1.11.0-beta02.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/core/core-performance-play-services/api/res-current.txt
similarity index 100%
copy from appactions/builtintypes/builtintypes-core/api/res-current.txt
copy to core/core-performance-play-services/api/res-current.txt
diff --git a/camera/camera-effects/api/1.3.0-beta01.txt b/core/core-performance-play-services/api/restricted_1.11.0-beta02.txt
similarity index 100%
copy from camera/camera-effects/api/1.3.0-beta01.txt
copy to core/core-performance-play-services/api/restricted_1.11.0-beta02.txt
diff --git a/core/core-performance-play-services/api/restricted_current.txt b/core/core-performance-play-services/api/restricted_current.txt
new file mode 100644
index 0000000..85c5ff4
--- /dev/null
+++ b/core/core-performance-play-services/api/restricted_current.txt
@@ -0,0 +1,17 @@
+// Signature format: 4.0
+package androidx.core.performance.play.services {
+
+  public final class PlayServicesDevicePerformanceSupplier implements androidx.core.performance.DevicePerformanceSupplier {
+    ctor public PlayServicesDevicePerformanceSupplier();
+    method public static androidx.core.performance.DevicePerformance createDevicePerformance(android.content.Context context);
+    method public kotlinx.coroutines.flow.Flow<java.lang.Integer> getMediaPerformanceClassFlow();
+    property public kotlinx.coroutines.flow.Flow<java.lang.Integer> mediaPerformanceClassFlow;
+    field public static final androidx.core.performance.play.services.PlayServicesDevicePerformanceSupplier.Companion Companion;
+  }
+
+  public static final class PlayServicesDevicePerformanceSupplier.Companion {
+    method public androidx.core.performance.DevicePerformance createDevicePerformance(android.content.Context context);
+  }
+
+}
+
diff --git a/appactions/builtintypes/builtintypes-core/build.gradle b/core/core-performance-play-services/build.gradle
similarity index 65%
copy from appactions/builtintypes/builtintypes-core/build.gradle
copy to core/core-performance-play-services/build.gradle
index fd57aad..5ed74ea 100644
--- a/appactions/builtintypes/builtintypes-core/build.gradle
+++ b/core/core-performance-play-services/build.gradle
@@ -15,7 +15,6 @@
  */
 
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -26,28 +25,26 @@
 dependencies {
     api(libs.kotlinStdlib)
 
+    implementation(libs.kotlinCoroutinesCore)
+    implementation(libs.playServicesDevicePerformance)
+    implementation(project(":core:core-performance"))
+
+    testImplementation(libs.testCore)
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core"
-    defaultConfig {
-        minSdkVersion 26
-    }
-}
-
-tasks.withType(KotlinCompile).configureEach {
-    compilerOptions {
-        freeCompilerArgs.add("-Xjvm-default=all")
-    }
+    namespace "androidx.core.performance.play.services"
 }
 
 androidx {
-    name = "AppActions Builtin Types Core"
+    name = "Core Performance Play Services extensions"
     type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.CORE_PERFORMANCE
     inceptionYear = "2023"
-    description = "This library exposes a core set of data types based on schema.org definitions."
+    description = "Get media performance class data from Google."
 }
diff --git a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceSupplier.kt b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceSupplier.kt
new file mode 100644
index 0000000..921caf5
--- /dev/null
+++ b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceSupplier.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.core.performance.play.services
+
+import android.content.Context
+import androidx.core.performance.DevicePerformance
+import androidx.core.performance.DevicePerformanceSupplier
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/** Uses Google Play Services to supply media performance class data. */
+class PlayServicesDevicePerformanceSupplier : DevicePerformanceSupplier {
+
+    companion object {
+        /**
+         * Create DevicePerformance from the context backed by StaticDevicePerformanceSupplier.
+         *
+         * This should be done in [android.app.Application.onCreate].
+         */
+        @JvmStatic
+        fun createDevicePerformance(
+            // Real implementations will require a context
+            @Suppress("UNUSED_PARAMETER") context: Context
+        ): DevicePerformance =
+            DevicePerformance.create(PlayServicesDevicePerformanceSupplier())
+    }
+
+    override val mediaPerformanceClassFlow: Flow<Int> = flow {
+        emit(0)
+        // TODO(281079628): implement
+    }
+}
\ No newline at end of file
diff --git a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/androidx-core-core-performance-play-services-documentation.md b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/androidx-core-core-performance-play-services-documentation.md
new file mode 100644
index 0000000..8a1f22b
--- /dev/null
+++ b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/androidx-core-core-performance-play-services-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+Core Performance Play Services
+
+# Package androidx.core.performance.play.services
+
+Google implementation of androidx.core.performance
\ No newline at end of file
diff --git a/core/core-performance-play-services/src/test/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceSupplierTest.kt b/core/core-performance-play-services/src/test/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceSupplierTest.kt
new file mode 100644
index 0000000..54b0d67
--- /dev/null
+++ b/core/core-performance-play-services/src/test/java/androidx/core/performance/play/services/PlayServicesDevicePerformanceSupplierTest.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.core.performance.play.services
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+/** Unit tests for [PlayServicesDevicePerformanceSupplier]. */
+class PlayServicesDevicePerformanceSupplierTest {
+
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun mediaPerformanceClassFlow_30() = runTest {
+        val supplier = PlayServicesDevicePerformanceSupplier()
+        assertThat(supplier.mediaPerformanceClassFlow.toList()).containsExactly(0)
+    }
+}
diff --git a/core/core-performance-testing/OWNERS b/core/core-performance-testing/OWNERS
new file mode 100644
index 0000000..da22b63
--- /dev/null
+++ b/core/core-performance-testing/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 461355
+essick@google.com
+nchalko@google.com
diff --git a/camera/camera-effects-still-portrait/api/1.3.0-beta01.txt b/core/core-performance-testing/api/1.11.0-beta02.txt
similarity index 100%
copy from camera/camera-effects-still-portrait/api/1.3.0-beta01.txt
copy to core/core-performance-testing/api/1.11.0-beta02.txt
diff --git a/core/core-performance-testing/api/current.txt b/core/core-performance-testing/api/current.txt
new file mode 100644
index 0000000..f184012
--- /dev/null
+++ b/core/core-performance-testing/api/current.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.core.performance.testing {
+
+  public final class FakeDevicePerformanceSupplier implements androidx.core.performance.DevicePerformanceSupplier {
+    ctor public FakeDevicePerformanceSupplier(int mediaPerformanceClass);
+    method public kotlinx.coroutines.flow.Flow<java.lang.Integer> getMediaPerformanceClassFlow();
+    property public kotlinx.coroutines.flow.Flow<java.lang.Integer> mediaPerformanceClassFlow;
+  }
+
+}
+
diff --git a/camera/camera-camera2/api/res-1.3.0-beta01.txt b/core/core-performance-testing/api/res-1.11.0-beta02.txt
similarity index 100%
copy from camera/camera-camera2/api/res-1.3.0-beta01.txt
copy to core/core-performance-testing/api/res-1.11.0-beta02.txt
diff --git a/appactions/builtintypes/builtintypes-core/api/res-current.txt b/core/core-performance-testing/api/res-current.txt
similarity index 100%
copy from appactions/builtintypes/builtintypes-core/api/res-current.txt
copy to core/core-performance-testing/api/res-current.txt
diff --git a/camera/camera-effects/api/1.3.0-beta01.txt b/core/core-performance-testing/api/restricted_1.11.0-beta02.txt
similarity index 100%
copy from camera/camera-effects/api/1.3.0-beta01.txt
copy to core/core-performance-testing/api/restricted_1.11.0-beta02.txt
diff --git a/core/core-performance-testing/api/restricted_current.txt b/core/core-performance-testing/api/restricted_current.txt
new file mode 100644
index 0000000..f184012
--- /dev/null
+++ b/core/core-performance-testing/api/restricted_current.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.core.performance.testing {
+
+  public final class FakeDevicePerformanceSupplier implements androidx.core.performance.DevicePerformanceSupplier {
+    ctor public FakeDevicePerformanceSupplier(int mediaPerformanceClass);
+    method public kotlinx.coroutines.flow.Flow<java.lang.Integer> getMediaPerformanceClassFlow();
+    property public kotlinx.coroutines.flow.Flow<java.lang.Integer> mediaPerformanceClassFlow;
+  }
+
+}
+
diff --git a/appactions/builtintypes/builtintypes-core/build.gradle b/core/core-performance-testing/build.gradle
similarity index 64%
copy from appactions/builtintypes/builtintypes-core/build.gradle
copy to core/core-performance-testing/build.gradle
index fd57aad..2109a27 100644
--- a/appactions/builtintypes/builtintypes-core/build.gradle
+++ b/core/core-performance-testing/build.gradle
@@ -15,7 +15,6 @@
  */
 
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,29 +24,25 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+    implementation(libs.kotlinCoroutinesCore)
+    implementation(project(":core:core-performance"))
 
+
+    testImplementation(libs.testCore)
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
-
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
 }
 
 android {
-    namespace "androidx.appactions.builtintypes.core"
-    defaultConfig {
-        minSdkVersion 26
-    }
-}
-
-tasks.withType(KotlinCompile).configureEach {
-    compilerOptions {
-        freeCompilerArgs.add("-Xjvm-default=all")
-    }
+    namespace "androidx.core.performance.testing"
 }
 
 androidx {
-    name = "AppActions Builtin Types Core"
+    name = "Core Performance Testing"
     type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.CORE_PERFORMANCE
     inceptionYear = "2023"
-    description = "This library exposes a core set of data types based on schema.org definitions."
+    description = "Test support for core-performance."
 }
diff --git a/core/core-performance-testing/src/main/java/androidx/core/performance/testing/FakeDevicePerformanceSupplier.kt b/core/core-performance-testing/src/main/java/androidx/core/performance/testing/FakeDevicePerformanceSupplier.kt
new file mode 100644
index 0000000..80ac20b
--- /dev/null
+++ b/core/core-performance-testing/src/main/java/androidx/core/performance/testing/FakeDevicePerformanceSupplier.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.core.performance.testing
+
+import androidx.core.performance.DevicePerformanceSupplier
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * A DevicePerformanceSupplier that immediately emits the `mediaPerformanceClass` provided and
+ * completes the flow.
+ *
+ * @param mediaPerformanceClass The media performance class value to emit.
+ */
+class FakeDevicePerformanceSupplier(private val mediaPerformanceClass: Int) :
+    DevicePerformanceSupplier {
+    override val mediaPerformanceClassFlow: Flow<Int> = flow {
+        emit(mediaPerformanceClass)
+    }
+}
\ No newline at end of file
diff --git a/core/core-performance-testing/src/main/java/androidx/core/performance/testing/androidx-core-core-performance-testing-documentation.md b/core/core-performance-testing/src/main/java/androidx/core/performance/testing/androidx-core-core-performance-testing-documentation.md
new file mode 100644
index 0000000..b6ff0ef
--- /dev/null
+++ b/core/core-performance-testing/src/main/java/androidx/core/performance/testing/androidx-core-core-performance-testing-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+Core Performance Testing
+
+# Package androidx.core.performance.testing
+
+Test doubles and utilities for androidx.core.performance
diff --git a/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceSupplierTest.kt b/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceSupplierTest.kt
new file mode 100644
index 0000000..edf7f93b
--- /dev/null
+++ b/core/core-performance-testing/src/test/java/androidx/core/performance/testing/FakeDevicePerformanceSupplierTest.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.core.performance.testing
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+/** Unit tests for [FakeDevicePerformanceSupplier]. */
+class FakeDevicePerformanceSupplierTest {
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun mediaPerformanceClassFlow_30() = runTest {
+        val fake = FakeDevicePerformanceSupplier(30)
+        assertThat(fake.mediaPerformanceClassFlow.toList()).containsExactly(30)
+    }
+
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun mediaPerformanceClassFlow_31() = runTest {
+        val fake = FakeDevicePerformanceSupplier(31)
+        assertThat(fake.mediaPerformanceClassFlow.toList()).containsExactly(31)
+    }
+}
diff --git a/core/core-performance/build.gradle b/core/core-performance/build.gradle
index 0084eb5..044a5ad 100644
--- a/core/core-performance/build.gradle
+++ b/core/core-performance/build.gradle
@@ -28,10 +28,12 @@
     implementation(libs.kotlinCoroutinesCore)
 
     testImplementation(libs.testCore)
+    testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(libs.robolectric)
+    testImplementation(project(":core:core-performance-testing"))
 
     samples(project(":core:core-performance:core-performance-samples"))
 }
diff --git a/core/core-performance/samples/build.gradle b/core/core-performance/samples/build.gradle
index 469b5ee..cbd177a 100644
--- a/core/core-performance/samples/build.gradle
+++ b/core/core-performance/samples/build.gradle
@@ -25,6 +25,7 @@
 dependencies {
     api(libs.kotlinStdlib)
     implementation(project(":core:core-performance"))
+    implementation(project(":core:core-performance-play-services"))
     compileOnly(project(":annotation:annotation-sampled"))
 }
 
diff --git a/core/core-performance/samples/src/main/java/androidx/core/performance/samples/Usage.kt b/core/core-performance/samples/src/main/java/androidx/core/performance/samples/Usage.kt
index b2d55b7..60f10f0 100644
--- a/core/core-performance/samples/src/main/java/androidx/core/performance/samples/Usage.kt
+++ b/core/core-performance/samples/src/main/java/androidx/core/performance/samples/Usage.kt
@@ -20,7 +20,7 @@
 import android.os.Build
 import androidx.annotation.Sampled
 import androidx.core.performance.DevicePerformance
-import androidx.core.performance.StaticDevicePerformanceSupplier
+import androidx.core.performance.play.services.PlayServicesDevicePerformanceSupplier
 
 @Sampled
 fun usage() {
@@ -31,7 +31,7 @@
 
         override fun onCreate() {
             devicePerformance =
-                StaticDevicePerformanceSupplier.createDevicePerformance()
+                PlayServicesDevicePerformanceSupplier.createDevicePerformance(applicationContext)
         }
 
         fun doSomeThing() {
diff --git a/core/core-performance/src/test/java/androidx/core/performance/DevicePerformanceTest.kt b/core/core-performance/src/test/java/androidx/core/performance/DevicePerformanceTest.kt
index 5316c09..f94e0bc 100644
--- a/core/core-performance/src/test/java/androidx/core/performance/DevicePerformanceTest.kt
+++ b/core/core-performance/src/test/java/androidx/core/performance/DevicePerformanceTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -16,100 +16,17 @@
 
 package androidx.core.performance
 
-import android.os.Build.VERSION_CODES.R
-import android.os.Build.VERSION_CODES.S
+import androidx.core.performance.testing.FakeDevicePerformanceSupplier
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.annotation.Config
-import org.robolectric.shadows.ShadowBuild
-import org.robolectric.shadows.ShadowSystemProperties
 
 /** Unit tests for [DevicePerformance]. */
-@RunWith(RobolectricTestRunner::class)
 class DevicePerformanceTest {
 
     @Test
-    @Config(maxSdk = R)
-    fun getMediaPerformanceClass_sdk30() {
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(0)
-    }
-
-    @Test
-    @Config(maxSdk = R, minSdk = R)
-    fun getMediaPerformanceClass_sdk30_declared() {
-        // on R devices, it doesn't matter if you set the value, ignore it
-        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "30")
-        ShadowBuild.reset()
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(0)
-    }
-
-    // declared an undefined perf class, treat it as 0
-    @Test
-    @Config(minSdk = S)
-    fun getMediaPerformanceClass_sdk31_declared25() {
-        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
-        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "25")
-        ShadowBuild.reset()
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(0)
-    }
-
-    @Test
-    @Config(minSdk = S)
-    fun getMediaPerformanceClass_sdk31_declared30() {
-        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
-        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "30")
-        ShadowBuild.reset()
-        val pc = createPerformanceClass()
+    fun mediaPerformanceClass() {
+        val fake = FakeDevicePerformanceSupplier(30)
+        val pc = DevicePerformance.create(fake)
         assertThat(pc.mediaPerformanceClass).isEqualTo(30)
     }
-
-    @Test
-    @Config(minSdk = S)
-    fun getMediaPerformanceClass_sdk31_declared31() {
-        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
-        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "31")
-        ShadowBuild.reset()
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(31)
-    }
-
-    @Test
-    @Config(minSdk = S)
-    fun getMediaPerformanceClass_sdk31_notDeclared() {
-        ShadowBuild.reset()
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(0)
-    }
-
-    @Test
-    fun getMediaPerformanceClass_sdk30_inList() {
-        ShadowBuild.reset()
-        ShadowBuild.setBrand("robolectric-BrandX")
-        ShadowBuild.setProduct("ProductX")
-        ShadowBuild.setDevice("Device30")
-        ShadowBuild.setVersionRelease("11")
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(30)
-    }
-
-    @Test
-    fun getMediaPerformanceClass_sdk31_inList() {
-        ShadowBuild.reset()
-        ShadowBuild.setBrand("robolectric-BrandX")
-        ShadowBuild.setProduct("ProductX")
-        ShadowBuild.setDevice("Device31")
-        ShadowBuild.setVersionRelease("12")
-        val pc = createPerformanceClass()
-        assertThat(pc.mediaPerformanceClass).isEqualTo(31)
-    }
-
-    private fun createPerformanceClass(): DevicePerformance {
-        return DevicePerformance.create(StaticDevicePerformanceSupplier()
-        )
-    }
 }
diff --git a/core/core-performance/src/test/java/androidx/core/performance/StaticDevicePerformanceTest.kt b/core/core-performance/src/test/java/androidx/core/performance/StaticDevicePerformanceTest.kt
new file mode 100644
index 0000000..06ca122
--- /dev/null
+++ b/core/core-performance/src/test/java/androidx/core/performance/StaticDevicePerformanceTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2021 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.core.performance
+
+import android.os.Build.VERSION_CODES.R
+import android.os.Build.VERSION_CODES.S
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowBuild
+import org.robolectric.shadows.ShadowSystemProperties
+
+/** Unit tests for [StaticDevicePerformanceSupplier]. */
+@RunWith(RobolectricTestRunner::class)
+class StaticDevicePerformanceTest {
+
+    @Test
+    @Config(maxSdk = R)
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk30() = runTest {
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(0)
+    }
+
+    @Test
+    @Config(maxSdk = R, minSdk = R)
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk30_declared() = runTest {
+        // on R devices, it doesn't matter if you set the value, ignore it
+        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "30")
+        ShadowBuild.reset()
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(0)
+    }
+
+    // declared an undefined perf class, treat it as 0
+    @Test
+    @Config(minSdk = S)
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk31_declared25() = runTest {
+        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
+        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "25")
+        ShadowBuild.reset()
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(0)
+    }
+
+    @Test
+    @Config(minSdk = S)
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk31_declared30() = runTest {
+        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
+        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "30")
+        ShadowBuild.reset()
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(30)
+    }
+
+    @Test
+    @Config(minSdk = S)
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk31_declared31() = runTest {
+        // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
+        ShadowSystemProperties.override("ro.odm.build.media_performance_class", "31")
+        ShadowBuild.reset()
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(31)
+    }
+
+    @Test
+    @Config(minSdk = S)
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk31_notDeclared() = runTest {
+        ShadowBuild.reset()
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(0)
+    }
+
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk30_inList() = runTest {
+        ShadowBuild.reset()
+        ShadowBuild.setBrand("robolectric-BrandX")
+        ShadowBuild.setProduct("ProductX")
+        ShadowBuild.setDevice("Device30")
+        ShadowBuild.setVersionRelease("11")
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(30)
+    }
+
+    @Test
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    fun getMediaPerformanceClass_sdk31_inList() = runTest {
+        ShadowBuild.reset()
+        ShadowBuild.setBrand("robolectric-BrandX")
+        ShadowBuild.setProduct("ProductX")
+        ShadowBuild.setDevice("Device31")
+        ShadowBuild.setVersionRelease("12")
+        val pc = StaticDevicePerformanceSupplier()
+        assertThat(pc.mediaPerformanceClassFlow.toList()).containsExactly(31)
+    }
+}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 3e1f91d..0265a0a 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -14,8 +14,14 @@
     docs(project(":activity:activity-ktx"))
     kmpDocs(project(":annotation:annotation"))
     docs(project(":annotation:annotation-experimental"))
-    docs(project(":appactions:builtintypes:builtintypes-core"))
-    samples(project(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples"))
+    docs(project(":appactions:builtintypes:builtintypes"))
+    samples(project(":appactions:builtintypes:builtintypes:builtintypes-samples"))
+    docs(project(":appactions:builtintypes:builtintypes-common"))
+    samples(project(":appactions:builtintypes:builtintypes-common:builtintypes-common-samples"))
+    docs(project(":appactions:builtintypes:builtintypes-communications"))
+    samples(project(":appactions:builtintypes:builtintypes-communications:builtintypes-communications-samples"))
+    docs(project(":appactions:builtintypes:builtintypes-productivity"))
+    samples(project(":appactions:builtintypes:builtintypes-productivity:builtintypes-productivity-samples"))
     docs(project(":appactions:interaction:interaction-capabilities-communication"))
     docs(project(":appactions:interaction:interaction-capabilities-core"))
     docs(project(":appactions:interaction:interaction-capabilities-productivity"))
@@ -25,6 +31,7 @@
     docs(project(":appactions:interaction:interaction-proto"))
     docs(project(":appactions:interaction:interaction-service"))
     docs(project(":appactions:interaction:interaction-service-proto"))
+    docs(project(":appactions:interaction:interaction-service-testing"))
     docs(project(":appcompat:appcompat"))
     docs(project(":appcompat:appcompat-resources"))
     docs(project(":appsearch:appsearch"))
@@ -131,6 +138,8 @@
     docs(project(":core:core-ktx"))
     docs(project(":core:core-location-altitude"))
     docs(project(":core:core-performance"))
+    docs(project(":core:core-performance-play-services"))
+    docs(project(":core:core-performance-testing"))
     samples(project(":core:core-performance:core-performance-samples"))
     docs(project(":core:core-remoteviews"))
     docs(project(":core:core-splashscreen"))
diff --git a/glance/glance-appwidget/api/1.0.0-beta02.txt b/glance/glance-appwidget/api/1.0.0-beta02.txt
index acafa55..1300920 100644
--- a/glance/glance-appwidget/api/1.0.0-beta02.txt
+++ b/glance/glance-appwidget/api/1.0.0-beta02.txt
@@ -24,6 +24,7 @@
   public final class CheckBoxKt {
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines, optional String? key);
   }
 
   public final class CheckboxDefaults {
@@ -79,7 +80,9 @@
 
   public abstract class GlanceAppWidgetReceiver extends android.appwidget.AppWidgetProvider {
     ctor public GlanceAppWidgetReceiver();
+    method @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext getCoroutineContext();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
+    property @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext coroutineContext;
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
     field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
     field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
@@ -123,6 +126,7 @@
   public final class RadioButtonKt {
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, androidx.glance.action.Action? onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines, optional String? key);
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -161,6 +165,7 @@
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines, optional String? key);
   }
 
 }
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index acafa55..1300920 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -24,6 +24,7 @@
   public final class CheckBoxKt {
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines, optional String? key);
   }
 
   public final class CheckboxDefaults {
@@ -79,7 +80,9 @@
 
   public abstract class GlanceAppWidgetReceiver extends android.appwidget.AppWidgetProvider {
     ctor public GlanceAppWidgetReceiver();
+    method @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext getCoroutineContext();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
+    property @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext coroutineContext;
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
     field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
     field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
@@ -123,6 +126,7 @@
   public final class RadioButtonKt {
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, androidx.glance.action.Action? onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines, optional String? key);
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -161,6 +165,7 @@
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines, optional String? key);
   }
 
 }
diff --git a/glance/glance-appwidget/api/restricted_1.0.0-beta02.txt b/glance/glance-appwidget/api/restricted_1.0.0-beta02.txt
index acafa55..1300920 100644
--- a/glance/glance-appwidget/api/restricted_1.0.0-beta02.txt
+++ b/glance/glance-appwidget/api/restricted_1.0.0-beta02.txt
@@ -24,6 +24,7 @@
   public final class CheckBoxKt {
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines, optional String? key);
   }
 
   public final class CheckboxDefaults {
@@ -79,7 +80,9 @@
 
   public abstract class GlanceAppWidgetReceiver extends android.appwidget.AppWidgetProvider {
     ctor public GlanceAppWidgetReceiver();
+    method @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext getCoroutineContext();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
+    property @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext coroutineContext;
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
     field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
     field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
@@ -123,6 +126,7 @@
   public final class RadioButtonKt {
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, androidx.glance.action.Action? onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines, optional String? key);
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -161,6 +165,7 @@
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines, optional String? key);
   }
 
 }
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index acafa55..1300920 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -24,6 +24,7 @@
   public final class CheckBoxKt {
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines, optional String? key);
   }
 
   public final class CheckboxDefaults {
@@ -79,7 +80,9 @@
 
   public abstract class GlanceAppWidgetReceiver extends android.appwidget.AppWidgetProvider {
     ctor public GlanceAppWidgetReceiver();
+    method @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext getCoroutineContext();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
+    property @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext coroutineContext;
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
     field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
     field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
@@ -123,6 +126,7 @@
   public final class RadioButtonKt {
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, androidx.glance.action.Action? onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines, optional String? key);
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -161,6 +165,7 @@
   public final class SwitchKt {
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines, optional String? key);
   }
 
 }
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt
index ef88370..6998de4 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/VerticalGridAppWidget.kt
@@ -19,13 +19,16 @@
 import android.content.Context
 import android.content.Intent
 import android.os.Build
+import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.unit.dp
 import androidx.glance.Button
 import androidx.glance.GlanceId
 import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
 import androidx.glance.LocalContext
 import androidx.glance.LocalSize
+import androidx.glance.action.clickable
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
 import androidx.glance.appwidget.action.actionStartActivity
@@ -78,9 +81,21 @@
         item {
             Text("${localSize.width}x${localSize.height}")
         }
-        items(count = 20, itemId = { it * 2L }) { index ->
+        items(count = 22, itemId = { it * 2L }) { index ->
             Text("Item $index")
         }
+        item {
+            Text(
+                text = "Clickable text",
+                modifier = GlanceModifier
+                    .background(GlanceTheme.colors.surfaceVariant)
+                    .padding(8.dp)
+                    .cornerRadius(28.dp)
+                    .clickable {
+                        Log.i("SampleGrid", "Clicked the clickable text!")
+                    }
+            )
+        }
         itemsIndexed(
             listOf(
                 GlanceAppWidgetDemoActivity::class.java,
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt
index 5f318d9..d7693a77 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/CoroutineBroadcastReceiverTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.BroadcastReceiver
 import android.content.Context
+import android.content.Context.RECEIVER_NOT_EXPORTED
 import android.content.Intent
 import android.content.IntentFilter
 import androidx.test.core.app.ApplicationProvider
@@ -58,10 +59,19 @@
     @Test
     fun onReceive() {
         val broadcastReceiver = TestBroadcast()
-        context.registerReceiver(
-            broadcastReceiver,
-            IntentFilter(BROADCAST_ACTION)
-        )
+
+        if (android.os.Build.VERSION.SDK_INT < 33) {
+            context.registerReceiver(
+                broadcastReceiver,
+                IntentFilter(BROADCAST_ACTION),
+            )
+        } else {
+            context.registerReceiver(
+                broadcastReceiver,
+                IntentFilter(BROADCAST_ACTION),
+                RECEIVER_NOT_EXPORTED,
+            )
+        }
 
         val value = "value"
         context.sendBroadcast(
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
index 4887f75..52d61ec 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
@@ -64,7 +64,8 @@
 import org.junit.Test
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@SdkSuppress(minSdkVersion = 29)
+// b/205868100 Run on 34+ once FLAG_UNSAFE_MUTABLE_IMPLICIT_INTENT is added
+@SdkSuppress(minSdkVersion = 29, maxSdkVersion = 33)
 @MediumTest
 class LazyColumnTest {
     @get:Rule
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/StrictModeTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/StrictModeTest.kt
index d906533..42295e0 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/StrictModeTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/StrictModeTest.kt
@@ -44,7 +44,6 @@
 import kotlin.test.assertIs
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -63,7 +62,7 @@
         previousPolicy = StrictMode.getVmPolicy()
         StrictMode.setVmPolicy(
             StrictMode.VmPolicy.Builder()
-                .detectAll()
+                .detectUnsafeIntentLaunch()
                 .penaltyListener(executor) {
                     Log.e("StrictModeTest", "Logging violation:")
                     Log.e("StrictModeTest", "$it")
@@ -122,7 +121,6 @@
         Truth.assertThat(CallbackTest.received.get()).containsExactly(1, 2)
     }
 
-    @Ignore // b/277763853
     @Test
     fun lazyColumn_actionRunCallback() {
         TestGlanceAppWidget.uiDefinition = {
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
index 3b4719e..0ec5659 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import androidx.glance.Emittable
+import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
 import androidx.glance.GlanceTheme
@@ -100,6 +101,43 @@
     maxLines
 )
 
+/**
+ * Adds a check box view to the glance view.
+ *
+ * @param checked whether the check box is checked
+ * @param onCheckedChange the action to be run when the checkbox is clicked
+ * @param modifier the modifier to apply to the check box
+ * @param text the text to display to the end of the check box
+ * @param style the style to apply to [text]
+ * @param colors the color tint to apply to the check box
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this checkbox. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@ExperimentalGlanceApi
+@Composable
+fun CheckBox(
+    checked: Boolean,
+    onCheckedChange: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    text: String = "",
+    style: TextStyle? = null,
+    colors: CheckBoxColors = CheckboxDefaults.colors(),
+    maxLines: Int = Int.MAX_VALUE,
+    key: String? = null,
+) = CheckBoxElement(
+    checked,
+    action(key, onCheckedChange),
+    modifier,
+    text,
+    style,
+    colors,
+    maxLines
+)
+
 @Composable
 private fun CheckBoxElement(
     checked: Boolean,
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
index 9031d03d..964fa8d 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
@@ -25,9 +25,12 @@
 import android.os.Bundle
 import android.util.Log
 import androidx.annotation.CallSuper
+import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.appwidget.action.LambdaActionBroadcasts
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.launch
@@ -49,6 +52,7 @@
  * implementation, and you must not call [AppWidgetProvider.goAsync], as it will be called by the
  * super implementation. This means your processing time must be short.
  */
+@OptIn(ExperimentalGlanceApi::class)
 abstract class GlanceAppWidgetReceiver : AppWidgetProvider() {
 
     companion object {
@@ -74,6 +78,18 @@
      */
     abstract val glanceAppWidget: GlanceAppWidget
 
+    /**
+     * Override [coroutineContext] to provide custom [CoroutineContext] in which to run
+     * update requests.
+     *
+     * Note: This does not set the [CoroutineContext] for the GlanceAppWidget, which will always run
+     * on the main thread.
+     */
+    @get:ExperimentalGlanceApi
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @ExperimentalGlanceApi
+    open val coroutineContext: CoroutineContext = Dispatchers.Default
+
     @CallSuper
     override fun onUpdate(
         context: Context,
@@ -86,7 +102,7 @@
                 "Using Glance in devices with API<23 is untested and might behave unexpectedly."
             )
         }
-        goAsync {
+        goAsync(coroutineContext) {
             updateManager(context)
             appWidgetIds.map { async { glanceAppWidget.update(context, it) } }
                 .awaitAll()
@@ -100,7 +116,7 @@
         appWidgetId: Int,
         newOptions: Bundle
     ) {
-        goAsync {
+        goAsync(coroutineContext) {
             updateManager(context)
             glanceAppWidget.resize(context, appWidgetId, newOptions)
         }
@@ -108,7 +124,7 @@
 
     @CallSuper
     override fun onDeleted(context: Context, appWidgetIds: IntArray) {
-        goAsync {
+        goAsync(coroutineContext) {
             updateManager(context)
             appWidgetIds.forEach { glanceAppWidget.deleted(context, it) }
         }
@@ -146,7 +162,7 @@
                             ?: error("Intent is missing ActionKey extra")
                     val id = intent.getIntExtra(LambdaActionBroadcasts.ExtraAppWidgetId, -1)
                     if (id == -1) error("Intent is missing AppWidgetId extra")
-                    goAsync {
+                    goAsync(coroutineContext) {
                         updateManager(context)
                         glanceAppWidget.triggerAction(context, id, actionKey)
                     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
index e4045f7..da340c7c 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
@@ -22,13 +22,13 @@
 import androidx.glance.Emittable
 import androidx.glance.EmittableButton
 import androidx.glance.EmittableImage
+import androidx.glance.EmittableLazyItemWithChildren
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
 import androidx.glance.ImageProvider
 import androidx.glance.action.ActionModifier
 import androidx.glance.action.LambdaAction
 import androidx.glance.appwidget.action.CompoundButtonAction
-import androidx.glance.appwidget.lazy.EmittableLazyListItem
 import androidx.glance.extractModifier
 import androidx.glance.findModifier
 import androidx.glance.layout.Alignment
@@ -49,7 +49,7 @@
     coerceToOneChild(root)
     root.normalizeSizes()
     root.transformTree { view ->
-        if (view is EmittableLazyListItem) normalizeLazyListItem(view)
+        if (view is EmittableLazyItemWithChildren) normalizeLazyListItem(view)
         view.transformBackgroundImageAndActionRipple()
     }
 }
@@ -139,7 +139,9 @@
     ) { index, actions, child ->
         val (action: LambdaAction?, modifiers: GlanceModifier) =
             child.modifier.extractLambdaAction()
-        if (action != null && child !is EmittableSizeBox && child !is EmittableLazyListItem) {
+        if (action != null &&
+            child !is EmittableSizeBox &&
+            child !is EmittableLazyItemWithChildren) {
             val newKey = action.key + "+$index"
             val newAction = LambdaAction(newKey, action.block)
             actions.getOrPut(newKey) { mutableListOf() }.add(newAction)
@@ -164,7 +166,7 @@
         }
     }
 
-private fun normalizeLazyListItem(view: EmittableLazyListItem) {
+private fun normalizeLazyListItem(view: EmittableLazyItemWithChildren) {
     if (view.children.size == 1 && view.alignment == Alignment.CenterStart) return
     val box = EmittableBox()
     box.children += view.children
@@ -184,9 +186,10 @@
  * convert the target emittable to an [EmittableText]
  */
 private fun Emittable.transformBackgroundImageAndActionRipple(): Emittable {
-    // EmittableLazyListItem and EmittableSizeBox are wrappers for their immediate only child,
-    // and do not get translated to their own element. We will transform their child instead.
-    if (this is EmittableLazyListItem || this is EmittableSizeBox) return this
+    // EmittableLazyItemWithChildren and EmittableSizeBox are wrappers for their immediate
+    // only child, and do not get translated to their own element. We will transform their child
+    // instead.
+    if (this is EmittableLazyItemWithChildren || this is EmittableSizeBox) return this
 
     var target = this
     val isButton = target is EmittableButton
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt
index 4db3e21..63e99e3 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RadioButton.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import androidx.glance.Emittable
+import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
 import androidx.glance.GlanceTheme
@@ -135,6 +136,50 @@
 )
 
 /**
+ * Adds a radio button to the glance view.
+ *
+ * When showing a [Row] or [Column] that has [RadioButton] children, use
+ * [GlanceModifier.selectableGroup] to enable the radio group effect (unselecting the previously
+ * selected radio button when another is selected).
+ *
+ * @param checked whether the radio button is checked
+ * @param onClick the action to be run when the radio button is clicked
+ * @param modifier the modifier to apply to the radio button
+ * @param enabled if false, the radio button will not be clickable
+ * @param text the text to display to the end of the radio button
+ * @param style the style to apply to [text]
+ * @param colors the color tint to apply to the radio button
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this radio button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@ExperimentalGlanceApi
+@Composable
+fun RadioButton(
+    checked: Boolean,
+    onClick: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    text: String = "",
+    style: TextStyle? = null,
+    colors: RadioButtonColors = RadioButtonDefaults.colors(),
+    maxLines: Int = Int.MAX_VALUE,
+    key: String? = null,
+) = RadioButtonElement(
+    checked,
+    action(key, onClick),
+    modifier,
+    enabled,
+    text,
+    style,
+    colors,
+    maxLines
+)
+
+/**
  * Contains the default values used by [RadioButton].
  */
 object RadioButtonDefaults {
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
index 06a9168..2eb8a9f 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Composable
 import androidx.glance.Emittable
+import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
 import androidx.glance.GlanceTheme
@@ -101,6 +102,43 @@
 )
 
 /**
+ * Adds a switch view to the glance view.
+ *
+ * @param checked whether the switch is checked
+ * @param onCheckedChange the action to be run when the switch is clicked
+ * @param modifier the modifier to apply to the switch
+ * @param text the text to display to the end of the switch
+ * @param style the style to apply to [text]
+ * @param colors the tint colors for the thumb and track of the switch
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this switch. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@ExperimentalGlanceApi
+@Composable
+fun Switch(
+    checked: Boolean,
+    onCheckedChange: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    text: String = "",
+    style: TextStyle? = null,
+    colors: SwitchColors = SwitchDefaults.colors(),
+    maxLines: Int = Int.MAX_VALUE,
+    key: String? = null,
+) = SwitchElement(
+    checked,
+    action(key, onCheckedChange),
+    modifier,
+    text,
+    style,
+    colors,
+    maxLines
+)
+
+/**
  * Contains the default values used by [Switch].
  */
 object SwitchDefaults {
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
index cc134a5..eaaf5e0 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.key
 import androidx.glance.Emittable
+import androidx.glance.EmittableLazyItemWithChildren
 import androidx.glance.EmittableWithChildren
 import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceModifier
@@ -291,7 +292,7 @@
             "activityOptions=$activityOptions, children=[\n${childrenToString()}\n])"
 }
 
-internal class EmittableLazyListItem : EmittableWithChildren() {
+internal class EmittableLazyListItem : EmittableLazyItemWithChildren() {
     override var modifier: GlanceModifier
         get() = children.singleOrNull()?.modifier
             ?: GlanceModifier.wrapContentHeight().fillMaxWidth()
@@ -299,7 +300,6 @@
             throw IllegalAccessError("You cannot set the modifier of an EmittableLazyListItem")
         }
     var itemId: Long = 0
-    var alignment: Alignment = Alignment.CenterStart
 
     override fun copy(): Emittable = EmittableLazyListItem().also {
         it.itemId = itemId
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
index 63e920f..9778863 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
@@ -21,6 +21,7 @@
 import androidx.compose.runtime.key
 import androidx.compose.ui.unit.Dp
 import androidx.glance.Emittable
+import androidx.glance.EmittableLazyItemWithChildren
 import androidx.glance.EmittableWithChildren
 import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceModifier
@@ -292,7 +293,7 @@
         "children=[\n${childrenToString()}\n])"
 }
 
-internal class EmittableLazyVerticalGridListItem : EmittableWithChildren() {
+internal class EmittableLazyVerticalGridListItem : EmittableLazyItemWithChildren() {
     override var modifier: GlanceModifier
         get() = children.singleOrNull()?.modifier
             ?: GlanceModifier.wrapContentHeight().fillMaxWidth()
@@ -302,7 +303,6 @@
             )
         }
     var itemId: Long = 0
-    var alignment: Alignment = Alignment.CenterStart
 
     override fun copy(): Emittable = EmittableLazyVerticalGridListItem().also {
         it.itemId = itemId
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
index d0b15a5..c1eeb93 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
@@ -617,6 +617,21 @@
     }
 
     @Test
+    fun canTranslateLazyVerticalGrid_withClickableItems() = fakeCoroutineScope.runTest {
+        val rv = context.runAndTranslate {
+            LazyVerticalGrid(gridCells = GridCells.Fixed(3)) {
+                items(2, { it * 2L }) { index ->
+                    Row(modifier = GlanceModifier.clickable {}) {
+                        Text("Item $index")
+                    }
+                }
+            }
+        }
+
+        assertIs<GridView>(context.applyRemoteViews(rv))
+    }
+
+    @Test
     fun canTranslateAndroidRemoteViews() = fakeCoroutineScope.runTest {
         val result = context.runAndTranslate {
             val providedViews = RemoteViews(context.packageName, R.layout.text_sample).also {
diff --git a/glance/glance/api/1.0.0-beta02.txt b/glance/glance/api/1.0.0-beta02.txt
index 6fc7e87..35b8894 100644
--- a/glance/glance/api/1.0.0-beta02.txt
+++ b/glance/glance/api/1.0.0-beta02.txt
@@ -23,6 +23,7 @@
   public final class ButtonKt {
     method @androidx.compose.runtime.Composable public static void Button(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
   }
 
   public final class ColorFilter {
@@ -127,6 +128,7 @@
 
   public final class ActionKt {
     method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 6fc7e87..35b8894 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -23,6 +23,7 @@
   public final class ButtonKt {
     method @androidx.compose.runtime.Composable public static void Button(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
   }
 
   public final class ColorFilter {
@@ -127,6 +128,7 @@
 
   public final class ActionKt {
     method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
diff --git a/glance/glance/api/restricted_1.0.0-beta02.txt b/glance/glance/api/restricted_1.0.0-beta02.txt
index 6fc7e87..35b8894 100644
--- a/glance/glance/api/restricted_1.0.0-beta02.txt
+++ b/glance/glance/api/restricted_1.0.0-beta02.txt
@@ -23,6 +23,7 @@
   public final class ButtonKt {
     method @androidx.compose.runtime.Composable public static void Button(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
   }
 
   public final class ColorFilter {
@@ -127,6 +128,7 @@
 
   public final class ActionKt {
     method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 6fc7e87..35b8894 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -23,6 +23,7 @@
   public final class ButtonKt {
     method @androidx.compose.runtime.Composable public static void Button(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
     method @androidx.compose.runtime.Composable public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
   }
 
   public final class ColorFilter {
@@ -127,6 +128,7 @@
 
   public final class ActionKt {
     method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
+    method @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
diff --git a/glance/glance/src/main/java/androidx/glance/Button.kt b/glance/glance/src/main/java/androidx/glance/Button.kt
index ddfb772..57c9fd2 100644
--- a/glance/glance/src/main/java/androidx/glance/Button.kt
+++ b/glance/glance/src/main/java/androidx/glance/Button.kt
@@ -71,6 +71,35 @@
     maxLines: Int = Int.MAX_VALUE,
 ) = ButtonElement(text, action(block = onClick), modifier, enabled, style, colors, maxLines)
 
+/**
+ * Adds a button view to the glance view.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param style The style to be applied to the text in this button.
+ * @param colors The colors to use for the background and content of the button.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@ExperimentalGlanceApi
+@Composable
+fun Button(
+    text: String,
+    onClick: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    style: TextStyle? = null,
+    colors: ButtonColors = ButtonDefaults.buttonColors(),
+    maxLines: Int = Int.MAX_VALUE,
+    key: String? = null
+) = ButtonElement(text, action(key, onClick), modifier, enabled, style, colors, maxLines)
+
 @Composable
 internal fun ButtonElement(
     text: String,
diff --git a/glance/glance/src/main/java/androidx/glance/Emittables.kt b/glance/glance/src/main/java/androidx/glance/Emittables.kt
index 3cd5dc3..9e549ca 100644
--- a/glance/glance/src/main/java/androidx/glance/Emittables.kt
+++ b/glance/glance/src/main/java/androidx/glance/Emittables.kt
@@ -17,6 +17,7 @@
 package androidx.glance
 
 import androidx.annotation.RestrictTo
+import androidx.glance.layout.Alignment
 
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -36,3 +37,9 @@
     protected fun childrenToString(): String =
         children.joinToString(",\n").prependIndent("  ")
 }
+
+/** @suppress */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+abstract class EmittableLazyItemWithChildren : EmittableWithChildren() {
+    var alignment: Alignment = Alignment.CenterStart
+}
\ No newline at end of file
diff --git a/glance/glance/src/main/java/androidx/glance/action/Action.kt b/glance/glance/src/main/java/androidx/glance/action/Action.kt
index d301e87..422872d 100644
--- a/glance/glance/src/main/java/androidx/glance/action/Action.kt
+++ b/glance/glance/src/main/java/androidx/glance/action/Action.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
+import androidx.glance.ExperimentalGlanceApi
 import androidx.glance.GlanceModifier
 
 /**
@@ -38,9 +39,28 @@
  * Run [block] in response to a user click.
  */
 @Composable
-fun GlanceModifier.clickable(block: () -> Unit): GlanceModifier =
+fun GlanceModifier.clickable(
+    block: () -> Unit
+): GlanceModifier =
     this.then(ActionModifier(action(block = block)))
 
+/**
+ * Run [block] in response to a user click.
+ *
+ * @param block The action to run.
+ * @param key A stable and unique key that identifies the action for this element. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@ExperimentalGlanceApi
+@Composable
+fun GlanceModifier.clickable(
+    key: String? = null,
+    block: () -> Unit
+): GlanceModifier =
+    this.then(ActionModifier(action(key, block)))
+
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class ActionModifier(val action: Action) : GlanceModifier.Element {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7b7a64e..8ff6388 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -177,7 +177,7 @@
 kotlinCoroutinesRx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3", version.ref = "kotlinCoroutines" }
 kotlinDaemonEmbeddable = { module = "org.jetbrains.kotlin:kotlin-daemon-embeddable", version.ref = "kotlin" }
 kotlinKlibCommonizer = { module = "org.jetbrains.kotlin:kotlin-klib-commonizer", version.ref = "kotlin" }
-kotlinMetadataJvm = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.6.1" }
+kotlinMetadataJvm = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.6.2" }
 kotlinSerializationCore = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinSerialization" }
 kotlinSerializationProtobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinSerialization" }
 kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index c95558d..1778366 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1006,5 +1006,21 @@
             <sha256 value="4e54622f5dc0f8b6c51e28650268f001e3b55d076c8e3a9d9731c050820c0a3d" origin="Generated by Gradle" reason="Artifact is not signed"/>
          </artifact>
       </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.3.0">
+         <artifact name="kotlin-stdlib-1.3.0.jar">
+            <sha256 value="4ff0fcb97f4983b4aaba12668c24ad21b08460915db1b021d8f1d8bee687f21c" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-stdlib-1.3.0.pom">
+            <sha256 value="4018b25ecf91e9dec4e240d896b95d12009640e3b1ce9b8efed2323e7ac65b07" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="1.3.0">
+         <artifact name="kotlin-stdlib-common-1.3.0.jar">
+            <sha256 value="4b161ef619eee0d1a49b1c4f0c4a8e46f4e342573efd8e0106a765f47475fe39" origin="Generated by Gradle"/>
+         </artifact>
+         <artifact name="kotlin-stdlib-common-1.3.0.pom">
+            <sha256 value="7c6e870a98ec8a001c971fe1a5fe4ae739cdbda913aa89abc6ab74d60e39ae9b" origin="Generated by Gradle"/>
+         </artifact>
+      </component>
    </components>
 </verification-metadata>
diff --git a/graphics/graphics-core/build.gradle b/graphics/graphics-core/build.gradle
index b99b3c8..07c8a7d 100644
--- a/graphics/graphics-core/build.gradle
+++ b/graphics/graphics-core/build.gradle
@@ -51,6 +51,7 @@
                                 "-Wl,--version-script=${versionScript}"
             }
         }
+        consumerProguardFiles 'proguard-rules.pro'
     }
     externalNativeBuild {
         cmake {
diff --git a/graphics/graphics-core/proguard-rules.pro b/graphics/graphics-core/proguard-rules.pro
new file mode 100644
index 0000000..026b496
--- /dev/null
+++ b/graphics/graphics-core/proguard-rules.pro
@@ -0,0 +1,42 @@
+# Keep class names of kotlin classes refered to by JNI code.
+# Each class requires 2 proguard rules, one to match on the class and
+# the other to match on each method referenced by native code.
+-if class androidx.graphics.surface.SurfaceControlCompat
+-keep @androidx.graphics.utils.JniVisible public class *
+
+-if class androidx.graphics.surface.SurfaceControlCompat
+-keepclasseswithmembers class * {
+    @androidx.graphics.utils.JniVisible *;
+}
+
+-if class androidx.hardware.SyncFenceCompat
+-keep @androidx.graphics.utils.JniVisible public class *
+
+-if class androidx.hardware.SyncFenceCompat
+-keepclasseswithmembers class * {
+    @androidx.graphics.utils.JniVisible *;
+}
+
+-if class androidx.graphics.lowlatency.CanvasFrontBufferedRenderer
+-keep @androidx.graphics.utils.JniVisible public class *
+
+-if class androidx.graphics.lowlatency.CanvasFrontBufferedRenderer
+-keepclasseswithmembers class * {
+    @androidx.graphics.utils.JniVisible *;
+}
+
+-if class androidx.graphics.lowlatency.GLFrontBufferedRenderer
+-keep @androidx.graphics.utils.JniVisible public class *
+
+-if class androidx.graphics.lowlatency.GLFrontBufferedRenderer
+-keepclasseswithmembers class * {
+    @androidx.graphics.utils.JniVisible *;
+}
+
+-if class androidx.opengl.EGLExt
+-keep @androidx.graphics.utils.JniVisible public class *
+
+-if class androidx.opengl.EGLExt
+-keepclasseswithmembers class * {
+    @androidx.graphics.utils.JniVisible *;
+}
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/BufferTransformHintResolverTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/BufferTransformHintResolverTest.kt
index ff5d80f..e7dc260 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/BufferTransformHintResolverTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/BufferTransformHintResolverTest.kt
@@ -26,6 +26,7 @@
 import androidx.graphics.lowlatency.BufferTransformHintResolver.Companion.ORIENTATION_270
 import androidx.graphics.lowlatency.BufferTransformHintResolver.Companion.ORIENTATION_90
 import androidx.graphics.lowlatency.BufferTransformHintResolver.Companion.UNKNOWN_TRANSFORM
+import androidx.graphics.surface.JniBindings
 import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_IDENTITY
 import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_ROTATE_180
 import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_ROTATE_270
@@ -162,7 +163,7 @@
     @Test
     fun testGetDisplayOrientationMethodLinked() {
         try {
-            BufferTransformHintResolver.getDisplayOrientation()
+            JniBindings.nGetDisplayOrientation()
         } catch (linkError: UnsatisfiedLinkError) {
             fail("Unable to resolve getDisplayOrientation")
         } catch (exception: Exception) {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
index 845e3bb..7269eba 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
@@ -309,7 +309,16 @@
                 transform: FloatArray,
                 params: Collection<Int>
             ) {
+
                 GLES20.glViewport(0, 0, bufferInfo.width, bufferInfo.height)
+                if (params.isEmpty()) {
+                    // We will receive no inputs on the first render so just render black.
+                    // This will be in response to surfaceRedrawNeeded
+                    GLES20.glClearColor(0f, 0f, 0f, 1f)
+                    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+                    GLES20.glFlush()
+                    return
+                }
                 Matrix.orthoM(
                     mOrthoMatrix,
                     0,
@@ -1384,6 +1393,47 @@
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
+    fun testMultiBufferedLayerRenderedOnSurfaceRedraw() {
+        val renderLatch = CountDownLatch(1)
+        val callbacks = object : GLFrontBufferedRenderer.Callback<Any> {
+
+            override fun onDrawFrontBufferedLayer(
+                eglManager: EGLManager,
+                bufferInfo: BufferInfo,
+                transform: FloatArray,
+                param: Any
+            ) {
+                // NO-OP
+            }
+
+            override fun onDrawMultiBufferedLayer(
+                eglManager: EGLManager,
+                bufferInfo: BufferInfo,
+                transform: FloatArray,
+                params: Collection<Any>
+            ) {
+                renderLatch.countDown()
+            }
+        }
+        var renderer: GLFrontBufferedRenderer<Any>? = null
+        var surfaceView: SurfaceView?
+        try {
+            val scenario = ActivityScenario.launch(FrontBufferedRendererTestActivity::class.java)
+                .moveToState(Lifecycle.State.CREATED)
+                .onActivity {
+                    surfaceView = it.getSurfaceView()
+                    renderer = GLFrontBufferedRenderer(surfaceView!!, callbacks)
+                }
+
+            scenario.moveToState(Lifecycle.State.RESUMED)
+            assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+        } finally {
+            renderer.blockingRelease()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
     fun testFrontBufferClearAfterRender() {
         var frontLatch = CountDownLatch(1)
         val commitLatch = CountDownLatch(1)
diff --git a/graphics/graphics-core/src/main/cpp/CMakeLists.txt b/graphics/graphics-core/src/main/cpp/CMakeLists.txt
index 88c5511..5bd2dc3 100644
--- a/graphics/graphics-core/src/main/cpp/CMakeLists.txt
+++ b/graphics/graphics-core/src/main/cpp/CMakeLists.txt
@@ -30,7 +30,6 @@
              graphics-core.cpp
              egl_utils.cpp
              sync_fence.cpp
-             buffer_transform_hint_resolver.cpp
              sc_test_utils.cpp
         )
 
diff --git a/graphics/graphics-core/src/main/cpp/buffer_transform_hint_resolver.cpp b/graphics/graphics-core/src/main/cpp/buffer_transform_hint_resolver.cpp
deleted file mode 100644
index fc627ba..0000000
--- a/graphics/graphics-core/src/main/cpp/buffer_transform_hint_resolver.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <android/log.h>
-#include <jni.h>
-#include <sys/system_properties.h>
-
-#include "buffer_transform_hint_resolver.h"
-
-jstring BufferTransformHintResolver_getDisplayOrientation(
-        JNIEnv *env, jclass) {
-    char name[PROP_VALUE_MAX];
-    __system_property_get("ro.surface_flinger.primary_display_orientation", name);
-    return (*env).NewStringUTF(name);
-}
-
-static const JNINativeMethod JNI_METHODS[] = {
-        {
-            "getDisplayOrientation",
-            "()Ljava/lang/String;",
-            (void *)BufferTransformHintResolver_getDisplayOrientation
-        }
-};
-
-jint loadBufferTransformHintResolverMethods(JNIEnv* env) {
-    jclass bufferTransformHintResolverClazz = env->FindClass(
-            "androidx/graphics/lowlatency/BufferTransformHintResolver");
-    if (bufferTransformHintResolverClazz == nullptr) {
-        //ALOGE("Unable to resolve buffer transform hint resolver class");
-        return JNI_ERR;
-    }
-
-    if (env->RegisterNatives(bufferTransformHintResolverClazz, JNI_METHODS,
-                             sizeof(JNI_METHODS) / sizeof(JNINativeMethod)) != JNI_OK) {
-        return JNI_ERR;
-    }
-
-    return JNI_OK;
-}
diff --git a/graphics/graphics-core/src/main/cpp/buffer_transform_hint_resolver.h b/graphics/graphics-core/src/main/cpp/buffer_transform_hint_resolver.h
deleted file mode 100644
index cc85c69..0000000
--- a/graphics/graphics-core/src/main/cpp/buffer_transform_hint_resolver.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROIDX_BUFFER_TRANSFORM_HINT_RESOLVER_H
-#define ANDROIDX_BUFFER_TRANSFORM_HINT_RESOLVER_H
-
-jint loadBufferTransformHintResolverMethods(JNIEnv* env);
-
-#endif //ANDROIDX_BUFFER_TRANSFORM_HINT_RESOLVER_H
diff --git a/graphics/graphics-core/src/main/cpp/graphics-core.cpp b/graphics/graphics-core/src/main/cpp/graphics-core.cpp
index e19cb4d..1da82e26 100644
--- a/graphics/graphics-core/src/main/cpp/graphics-core.cpp
+++ b/graphics/graphics-core/src/main/cpp/graphics-core.cpp
@@ -29,9 +29,9 @@
 #include <android/hardware_buffer_jni.h>
 #include <android/log.h>
 #include <android/sync.h>
+#include <sys/system_properties.h>
 #include "egl_utils.h"
 #include "sync_fence.h"
-#include "buffer_transform_hint_resolver.h"
 
 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
 
@@ -442,6 +442,12 @@
     ASurfaceTransaction_setGeometry(st, sc, src, dest, transformation);
 }
 
+jstring JniBindings_nGetDisplayOrientation(JNIEnv *env, jclass) {
+    char name[PROP_VALUE_MAX];
+    __system_property_get("ro.surface_flinger.primary_display_orientation", name);
+    return (*env).NewStringUTF(name);
+}
+
 void loadRectInfo(JNIEnv *env) {
     gRectInfo.clazz = env->FindClass("android/graphics/Rect");
 
@@ -562,6 +568,11 @@
                 "nSetGeometry",
                 "(JJIIIII)V",
                 (void *) JniBindings_nSetGeometry
+        },
+        {
+            "nGetDisplayOrientation",
+                "()Ljava/lang/String;",
+                (void *)JniBindings_nGetDisplayOrientation
         }
 };
 
@@ -593,9 +604,5 @@
         return JNI_ERR;
     }
 
-    if (loadBufferTransformHintResolverMethods(env) != JNI_OK) {
-        return JNI_ERR;
-    }
-
     return JNI_VERSION_1_6;
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/BufferTransformHintResolver.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/BufferTransformHintResolver.kt
index 5c7c12e..0f8906c 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/BufferTransformHintResolver.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/BufferTransformHintResolver.kt
@@ -21,6 +21,7 @@
 import android.view.Surface
 import android.view.View
 import androidx.annotation.RequiresApi
+import androidx.graphics.surface.JniBindings
 import androidx.graphics.surface.SurfaceControlCompat
 
 /**
@@ -35,7 +36,7 @@
         } else {
             val orientation: String?
             return try {
-                orientation = getDisplayOrientation()
+                orientation = JniBindings.nGetDisplayOrientation()
                 val rotation = view.display?.rotation
                 if (rotation != null) {
                     val transform = getBufferTransformHintFromInstallOrientation(
@@ -112,13 +113,6 @@
         const val ORIENTATION_90 = "ORIENTATION_90"
         const val ORIENTATION_180 = "ORIENTATION_180"
         const val ORIENTATION_270 = "ORIENTATION_270"
-
-        init {
-            System.loadLibrary("graphics-core")
-        }
-
-        @JvmStatic
-        external fun getDisplayOrientation(): String
     }
 }
 
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt
index c222838..5b02f83 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SurfaceViewRenderLayer.kt
@@ -30,6 +30,7 @@
 import androidx.graphics.surface.SurfaceControlCompat
 import androidx.hardware.SyncFenceCompat
 import java.util.Collections
+import java.util.concurrent.CountDownLatch
 
 /**
  * [ParentRenderLayer] instance that leverages a [SurfaceView]'s [SurfaceControlCompat] as the
@@ -42,6 +43,7 @@
 
     private var mLayerCallback: ParentRenderLayer.Callback<T>? = null
     private var mFrameBufferRenderer: FrameBufferRenderer? = null
+    private var mGLRenderer: GLRenderer? = null
     private var mRenderTarget: GLRenderer.RenderTarget? = null
     private var mParentSurfaceControl: SurfaceControlCompat? = null
     private val mBufferTransform = BufferTransformer()
@@ -50,7 +52,7 @@
 
     private var mTransformHint = BufferTransformHintResolver.UNKNOWN_TRANSFORM
     private var mInverse = BufferTransformHintResolver.UNKNOWN_TRANSFORM
-    private val mHolderCallback = object : SurfaceHolder.Callback {
+    private val mHolderCallback = object : SurfaceHolder.Callback2 {
 
         override fun surfaceCreated(holder: SurfaceHolder) {
             // NO-OP wait on surfaceChanged callback
@@ -70,6 +72,44 @@
             mLayerCallback?.onSizeChanged(width, height)
         }
 
+        override fun surfaceRedrawNeeded(p0: SurfaceHolder) {
+            val latch = CountDownLatch(1)
+            renderMultiBufferedLayer { latch.countDown() }
+            latch.await()
+        }
+
+        override fun surfaceRedrawNeededAsync(holder: SurfaceHolder, drawingFinished: Runnable) {
+            renderMultiBufferedLayer(drawingFinished)
+        }
+
+        private fun renderMultiBufferedLayer(onComplete: Runnable) {
+            val renderer = mGLRenderer
+            val renderTarget = mRenderTarget
+            if (renderer != null && renderer.isRunning() && renderTarget != null) {
+                // Register a callback in case the GLRenderer is torn down while we are waiting
+                // for rendering to complete. In this case invoke the drawFinished callback
+                // either if the render is complete or if the GLRenderer is torn down, whatever
+                // comes first
+                val callback = object : GLRenderer.EGLContextCallback {
+                    override fun onEGLContextCreated(eglManager: EGLManager) {
+                        // NO-OP
+                    }
+
+                    override fun onEGLContextDestroyed(eglManager: EGLManager) {
+                        onComplete.run()
+                        renderer.unregisterEGLContextCallback(this)
+                    }
+                }
+                renderer.registerEGLContextCallback(callback)
+                renderTarget.requestRender {
+                    onComplete.run()
+                    renderer.unregisterEGLContextCallback(callback)
+                }
+            } else {
+                onComplete.run()
+            }
+        }
+
         override fun surfaceDestroyed(p0: SurfaceHolder) {
             mLayerCallback?.onLayerDestroyed()
         }
@@ -105,7 +145,6 @@
         renderer: GLRenderer,
         renderLayerCallback: GLFrontBufferedRenderer.Callback<T>
     ): GLRenderer.RenderTarget {
-        var params: Collection<T>? = null
         val bufferInfo = BufferInfo()
         val frameBufferRenderer = FrameBufferRenderer(
             object : FrameBufferRenderer.RenderCallback {
@@ -126,7 +165,7 @@
                         eglManager,
                         bufferInfo,
                         mBufferTransform.transform,
-                        params ?: Collections.emptyList()
+                        mLayerCallback?.obtainMultiBufferedLayerParams() ?: Collections.emptyList()
                     )
                 }
 
@@ -170,13 +209,10 @@
                     }
                 }
             })
-        val parentFrameBufferRenderer = WrapperFrameBufferRenderer<T>(frameBufferRenderer) {
-            params = mLayerCallback?.obtainMultiBufferedLayerParams()
-            params != null
-        }
-        val renderTarget = renderer.attach(surfaceView, parentFrameBufferRenderer)
+        val renderTarget = renderer.attach(surfaceView, frameBufferRenderer)
         mRenderTarget = renderTarget
         mFrameBufferRenderer = frameBufferRenderer
+        mGLRenderer = renderer
         return renderTarget
     }
 
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/WrapperFrameBufferRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/WrapperFrameBufferRenderer.kt
deleted file mode 100644
index a80712b..0000000
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/WrapperFrameBufferRenderer.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.graphics.lowlatency
-
-import android.opengl.EGLConfig
-import android.opengl.EGLSurface
-import android.os.Build
-import android.view.Surface
-import androidx.annotation.RequiresApi
-import androidx.graphics.opengl.FrameBufferRenderer
-import androidx.graphics.opengl.GLRenderer
-import androidx.graphics.opengl.egl.EGLManager
-import androidx.graphics.opengl.egl.EGLSpec
-
-/**
- * Wrapper Renderer around [FrameBufferRenderer] that skips rendering on a given condition
- */
-@RequiresApi(Build.VERSION_CODES.O)
-internal class WrapperFrameBufferRenderer<T>(
-    private val frameBufferRenderer: FrameBufferRenderer,
-    private val shouldRender: () -> Boolean
-) : GLRenderer.RenderCallback {
-
-    override fun onSurfaceCreated(
-        spec: EGLSpec,
-        config: EGLConfig,
-        surface: Surface,
-        width: Int,
-        height: Int
-    ): EGLSurface? = frameBufferRenderer.onSurfaceCreated(spec, config, surface, width, height)
-
-    override fun onDrawFrame(eglManager: EGLManager) {
-        if (shouldRender()) {
-            frameBufferRenderer.onDrawFrame(eglManager)
-        }
-    }
-}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt
index daa5636..92336a6 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt
@@ -27,6 +27,7 @@
 import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
 import androidx.graphics.lowlatency.FrontBufferUtils
+import androidx.graphics.utils.JniVisible
 import androidx.hardware.SyncFenceCompat
 import java.util.concurrent.Executor
 
@@ -181,6 +182,7 @@
      * Interface to handle request to
      * [SurfaceControlV29.Transaction.addTransactionCompletedListener]
      */
+    @JniVisible
     internal interface TransactionCompletedListener {
         /**
          * Invoked when a frame including the updates in a transaction was presented.
@@ -188,6 +190,7 @@
          * Buffers which are replaced or removed from the scene in the transaction invoking
          * this callback may be reused after this point.
          */
+        @JniVisible
         fun onTransactionCompleted()
     }
 
@@ -195,10 +198,12 @@
      * Interface to handle request to
      * [SurfaceControlCompat.Transaction.addTransactionCommittedListener]
      */
+    @JniVisible
     interface TransactionCommittedListener {
         /**
          * Invoked when the transaction has been committed in SurfaceFlinger
          */
+        @JniVisible
         fun onTransactionCommitted()
     }
 
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
index 1ad3054..cc5c086 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
@@ -23,25 +23,33 @@
 import android.view.Surface
 import android.view.SurfaceControl
 import androidx.annotation.RequiresApi
+import androidx.graphics.utils.JniVisible
 import androidx.hardware.SyncFenceV19
 import java.util.concurrent.Executor
 
+@JniVisible
 internal class JniBindings {
     companion object {
         @JvmStatic
+        @JniVisible
         external fun nCreate(surfaceControl: Long, debugName: String): Long
         @JvmStatic
+        @JniVisible
         external fun nCreateFromSurface(surface: Surface, debugName: String): Long
         @JvmStatic
+        @JniVisible
         external fun nRelease(surfaceControl: Long)
         @JvmStatic
-
+        @JniVisible
         external fun nTransactionCreate(): Long
         @JvmStatic
+        @JniVisible
         external fun nTransactionDelete(surfaceTransaction: Long)
         @JvmStatic
+        @JniVisible
         external fun nTransactionApply(surfaceTransaction: Long)
         @JvmStatic
+        @JniVisible
         external fun nTransactionReparent(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -49,23 +57,27 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nTransactionSetOnComplete(
             surfaceTransaction: Long,
             listener: SurfaceControlCompat.TransactionCompletedListener
         )
 
         @JvmStatic
+        @JniVisible
         external fun nTransactionSetOnCommit(
             surfaceTransaction: Long,
             listener: SurfaceControlCompat.TransactionCommittedListener
         )
 
         @JvmStatic
+        @JniVisible
         external fun nDupFenceFd(
             syncFence: SyncFenceV19
         ): Int
 
         @JvmStatic
+        @JniVisible
         external fun nSetBuffer(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -74,6 +86,7 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetGeometry(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -85,6 +98,7 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetVisibility(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -92,8 +106,10 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetZOrder(surfaceTransaction: Long, surfaceControl: Long, zOrder: Int)
         @JvmStatic
+        @JniVisible
         external fun nSetDamageRegion(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -101,12 +117,14 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetDesiredPresentTime(
             surfaceTransaction: Long,
             desiredPresentTime: Long
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetBufferTransparency(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -114,6 +132,7 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetBufferAlpha(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -121,6 +140,7 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetCrop(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -131,6 +151,7 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetPosition(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -139,6 +160,7 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetScale(
             surfaceTransaction: Long,
             surfaceControl: Long,
@@ -147,12 +169,17 @@
         )
 
         @JvmStatic
+        @JniVisible
         external fun nSetBufferTransform(
             surfaceTransaction: Long,
             surfaceControl: Long,
             transformation: Int
         )
 
+        @JvmStatic
+        @JniVisible
+        external fun nGetDisplayOrientation(): String
+
         init {
             System.loadLibrary("graphics-core")
         }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java b/graphics/graphics-core/src/main/java/androidx/graphics/utils/JniVisible.kt
similarity index 72%
rename from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java
rename to graphics/graphics-core/src/main/java/androidx/graphics/utils/JniVisible.kt
index 03934ba..c867b3e 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/utils/JniVisible.kt
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.appactions.interaction.capabilities.core.impl;
-
-import androidx.annotation.NonNull;
+package androidx.graphics.utils
 
 /**
- * A builder of objects of some specific type.
- *
- * @param <T>
+ * Annotation used to indicate that a class, method or field is referenced by
+ * JNI and should be exempt from code obfuscation tools (ex. proguard/r8)
  */
-public interface BuilderOf<T> {
-    @NonNull
-    T build();
-}
+internal annotation class JniVisible()
diff --git a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceBindings.kt b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceBindings.kt
index be7f8dc..2e6cec8 100644
--- a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceBindings.kt
+++ b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceBindings.kt
@@ -16,17 +16,22 @@
 
 package androidx.hardware
 
+import androidx.graphics.utils.JniVisible
+
 /**
  * Helper class of jni bindings to verify dlopen/dlsym behavior to resolve sync_info_file and
  * sync_info_file_free methods
  */
+@JniVisible
 internal class SyncFenceBindings private constructor() {
     companion object {
 
         @JvmStatic
+        @JniVisible
         external fun nResolveSyncFileInfo(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nResolveSyncFileInfoFree(): Boolean
 
         init {
diff --git a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt
index 2b17fc6..055bc4f 100644
--- a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt
+++ b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt
@@ -138,12 +138,7 @@
         @RequiresApi(Build.VERSION_CODES.TIRAMISU)
         @androidx.annotation.DoNotInline
         fun createSyncFenceCompatV33(): SyncFenceCompat {
-            val display = EGL15.eglGetPlatformDisplay(
-                EGL15.EGL_PLATFORM_ANDROID_KHR,
-                EGL14.EGL_DEFAULT_DISPLAY.toLong(),
-                mEmptyAttributes,
-                0
-            )
+            val display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
             if (display == EGL15.EGL_NO_DISPLAY) {
                 throw RuntimeException("no EGL display")
             }
diff --git a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceV19.kt b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceV19.kt
index 108bb14..fd4e7ee 100644
--- a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceV19.kt
+++ b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceV19.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.annotation.RequiresApi
+import androidx.graphics.utils.JniVisible
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.locks.ReentrantLock
 import kotlin.concurrent.withLock
@@ -33,6 +34,7 @@
  * such as for display or media encoding.
  */
 @RequiresApi(Build.VERSION_CODES.KITKAT)
+@JniVisible
 internal class SyncFenceV19(private var fd: Int) : AutoCloseable, SyncFenceImpl {
 
     private val fenceLock = ReentrantLock()
@@ -60,6 +62,7 @@
     }
 
     // Accessed through JNI to obtain the dup'ed file descriptor in a thread safe manner
+    @JniVisible
     private fun dupeFileDescriptor(): Int = fenceLock.withLock {
         return if (isValid()) {
             nDup(fd)
diff --git a/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt b/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt
index 1014478..958011a 100644
--- a/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt
+++ b/graphics/graphics-core/src/main/java/androidx/opengl/EGLExt.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.graphics.opengl.egl.EGLConfigAttributes
+import androidx.graphics.utils.JniVisible
 import androidx.hardware.SyncFenceCompat
 import androidx.hardware.SyncFenceV19
 import androidx.opengl.EGLExt.Companion.eglCreateSyncKHR
@@ -644,9 +645,11 @@
  * public API. This class is provided to separate responsibilities of jni method registration
  * and helps to avoid synthetic accessor warnings
  */
+@JniVisible
 internal class EGLBindings {
     companion object {
         @JvmStatic
+        @JniVisible
         external fun nCreateImageFromHardwareBuffer(
             eglDisplayPtr: Long,
             hardwareBuffer: HardwareBuffer
@@ -655,15 +658,19 @@
         // Note this API is explicitly a GL API and not an EGL API which is the reason
         // why this has the GL prefix vs EGL
         @JvmStatic
+        @JniVisible
         external fun nImageTargetTexture2DOES(target: Int, eglImagePtr: Long)
 
         @JvmStatic
+        @JniVisible
         external fun nDupNativeFenceFDANDROID(eglDisplayPtr: Long, syncPtr: Long): Int
 
         @JvmStatic
+        @JniVisible
         external fun nCreateSyncKHR(eglDisplayPtr: Long, type: Int, attrs: IntArray?): Long
 
         @JvmStatic
+        @JniVisible
         external fun nGetSyncAttribKHR(
             eglDisplayPtr: Long,
             syncPtr: Long,
@@ -673,6 +680,7 @@
         ): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nClientWaitSyncKHR(
             eglDisplayPtr: Long,
             syncPtr: Long,
@@ -681,38 +689,50 @@
         ): Int
 
         @JvmStatic
+        @JniVisible
         external fun nDestroySyncKHR(eglDisplayPtr: Long, syncPtr: Long): Boolean
         @JvmStatic
+        @JniVisible
         external fun nDestroyImageKHR(eglDisplayPtr: Long, eglImagePtr: Long): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglGetNativeClientBufferAndroid(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsDupNativeFenceFDANDROID(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglCreateImageKHR(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglDestroyImageKHR(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsGlImageTargetTexture2DOES(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglCreateSyncKHR(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglGetSyncAttribKHR(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglClientWaitSyncKHR(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nSupportsEglDestroySyncKHR(): Boolean
 
         @JvmStatic
+        @JniVisible
         external fun nEqualToNativeForeverTimeout(timeoutNanos: Long): Boolean
 
         init {
diff --git a/libraryversions.toml b/libraryversions.toml
index 9921118..cf3fbbf 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -14,7 +14,7 @@
 BLUETOOTH = "1.0.0-alpha01"
 BROWSER = "1.6.0-alpha02"
 BUILDSRC_TESTS = "1.0.0-alpha01"
-CAMERA = "1.3.0-beta01"
+CAMERA = "1.3.0-alpha08"
 CAMERA_PIPE = "1.0.0-alpha01"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.4.0-alpha01"
@@ -87,7 +87,7 @@
 LOADER = "1.2.0-alpha01"
 MEDIA = "1.7.0-alpha02"
 MEDIA2 = "1.3.0-alpha01"
-MEDIAROUTER = "1.5.0-alpha01"
+MEDIAROUTER = "1.5.0-alpha02"
 METRICS = "1.0.0-alpha05"
 NAVIGATION = "2.7.0-beta01"
 PAGING = "3.2.0-beta01"
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
index a4a6f28..1a86608 100644
--- a/lint-checks/integration-tests/build.gradle
+++ b/lint-checks/integration-tests/build.gradle
@@ -72,7 +72,10 @@
 
 // workaround for b/189877657
 afterEvaluate {
-    tasks.named("copyDebugAndroidLintReports").configure {
+    tasks.findByName("copyDebugAndroidLintReports")?.configure {
+        enabled = false
+    }
+    tasks.findByName("copyDebugLintReports")?.configure {
         enabled = false
     }
 }
diff --git a/mediarouter/mediarouter/build.gradle b/mediarouter/mediarouter/build.gradle
index 46de044..100993c 100644
--- a/mediarouter/mediarouter/build.gradle
+++ b/mediarouter/mediarouter/build.gradle
@@ -25,7 +25,9 @@
     api("androidx.media:media:1.4.1")
     api(libs.guavaListenableFuture)
 
-    implementation("androidx.core:core:1.6.0")
+    // We use a project dependency on androidx.core to keep an up-to-date BuildCompat dependency.
+    // See b/283315121 for context.
+    implementation(project(":core:core"))
     implementation("androidx.appcompat:appcompat:1.1.0")
     implementation("androidx.palette:palette:1.0.0")
     implementation("androidx.recyclerview:recyclerview:1.1.0")
diff --git a/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt b/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt
index f608c44..bfd61c7 100644
--- a/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt
+++ b/paging/paging-runtime/src/androidTest/java/androidx/paging/StateRestorationTest.kt
@@ -35,25 +35,23 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import kotlin.coroutines.ContinuationInterceptor
 import kotlin.coroutines.CoroutineContext
 import kotlin.time.ExperimentalTime
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.internal.ThreadSafeHeap
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.withContext
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -79,43 +77,24 @@
      * main, and background for pager.
      * testScope for running tests.
      */
-    private val trackedDispatchers = mutableListOf<TestCoroutineDispatcher>()
-
     private val scheduler = TestCoroutineScheduler()
-    private val mainDispatcher = TestCoroutineDispatcher(scheduler).track()
-    private var backgroundDispatcher = TestCoroutineDispatcher(scheduler).track()
-    private val testScope = TestCoroutineScope(scheduler).track()
+    private val mainDispatcher = StandardTestDispatcher(scheduler)
+    private var backgroundDispatcher = StandardTestDispatcher(scheduler)
 
     /**
      * A fake lifecycle scope for collections that get cancelled when we recreate the recyclerview.
      */
-    private lateinit var lifecycleScope: TestCoroutineScope
+    private lateinit var lifecycleScope: TestScope
     private lateinit var recyclerView: TestRecyclerView
     private lateinit var layoutManager: RestoreAwareLayoutManager
     private lateinit var adapter: TestAdapter
 
-    /**
-     * tracks [this] dispatcher for idling control.
-     */
-    private fun TestCoroutineDispatcher.track() = apply {
-        trackedDispatchers.add(this)
-    }
-
-    /**
-     * tracks the dispatcher of this scope for idling control.
-     */
-    private fun TestCoroutineScope.track() = apply {
-        (this@track.coroutineContext[ContinuationInterceptor.Key] as TestCoroutineDispatcher)
-            .track()
-    }
-
     @Before
     fun init() {
         createRecyclerView()
     }
 
     @SdkSuppress(minSdkVersion = 21) // b/189492631
-    @Ignore // the test needs to be adapted for new coroutines test lib - b/220884819
     @Test
     fun restoreState_withPlaceholders() {
         runTest {
@@ -135,19 +114,24 @@
             assertThat(
                 layoutManager.restoredState
             ).isFalse()
-            backgroundDispatcher.pauseDispatcher()
-            collectPagesAsync(
-                createPager(
-                    pageSize = 10,
-                    enablePlaceholders = true
-                ).flow
-            )
+            // pause item loads
+            val delayedJob = launch(start = CoroutineStart.LAZY) {
+                collectPagesAsync(
+                    createPager(
+                        pageSize = 10,
+                        enablePlaceholders = true
+                    ).flow
+                )
+            }
             measureAndLayout()
-            // background worker is blocked, still shouldn't restore state
+            // item load is paused, still shouldn't restore state
             assertThat(
                 layoutManager.restoredState
             ).isFalse()
-            backgroundDispatcher.resumeDispatcher()
+
+            // now load items
+            delayedJob.start()
+
             measureAndLayout()
             assertThat(
                 layoutManager.restoredState
@@ -168,7 +152,7 @@
                 pageSize = 60,
                 enablePlaceholders = false
             )
-            val cacheScope = TestCoroutineScope(Job() + scheduler).track()
+            val cacheScope = TestScope(Job() + scheduler)
             val cachedFlow = pager.flow.cachedIn(cacheScope)
             collectPagesAsync(cachedFlow)
             measureAndLayout()
@@ -258,9 +242,9 @@
         if (this::lifecycleScope.isInitialized) {
             this.lifecycleScope.cancel()
         }
-        lifecycleScope = TestCoroutineScope(
+        lifecycleScope = TestScope(
             SupervisorJob() + mainDispatcher
-        ).track()
+        )
         val context = ApplicationProvider.getApplicationContext<Application>()
         recyclerView = TestRecyclerView(context)
         recyclerView.itemAnimator = null
@@ -270,25 +254,17 @@
         recyclerView.layoutManager = layoutManager
     }
 
-    private fun runPending() {
-        while (trackedDispatchers.any { it.isNotEmpty && it.isNotPaused }) {
-            trackedDispatchers.filter { it.isNotPaused }.forEach {
-                it.runCurrent()
-            }
-        }
-    }
-
     private fun scrollToPosition(pos: Int) {
         while (adapter.itemCount <= pos) {
             val prevSize = adapter.itemCount
             adapter.triggerItemLoad(prevSize - 1)
-            runPending()
+            scheduler.runCurrent()
             // this might be an issue with dropping but it is not the case here
             assertWithMessage("more items should be loaded")
                 .that(adapter.itemCount)
                 .isGreaterThan(prevSize)
         }
-        runPending()
+        scheduler.runCurrent()
         recyclerView.scrollToPosition(pos)
         measureAndLayout()
         val child = layoutManager.findViewByPosition(pos)
@@ -303,11 +279,11 @@
     }
 
     private fun measureAndLayout() {
-        runPending()
+        scheduler.runCurrent()
         while (recyclerView.isLayoutRequested) {
             measure()
             layout()
-            runPending()
+            scheduler.runCurrent()
         }
     }
 
@@ -326,18 +302,17 @@
         measureAndLayout()
     }
 
-    private fun runTest(block: TestCoroutineScope.() -> Unit) {
-        testScope.runBlockingTest {
+    private fun runTest(block: TestScope.() -> Unit) =
+        runTest(UnconfinedTestDispatcher(scheduler)) {
             try {
-                this.block()
+                block()
             } finally {
-                runPending()
+                scheduler.runCurrent()
                 // always cancel the lifecycle scope to ensure any collection there ends
                 if (this@StateRestorationTest::lifecycleScope.isInitialized) {
                     lifecycleScope.cancel()
                 }
             }
-        }
     }
 
     /**
@@ -419,30 +394,6 @@
         }
     }
 
-    /**
-     * Checks whether a [TestCoroutineDispatcher] has any pending actions using reflection :)
-     */
-    @OptIn(InternalCoroutinesApi::class)
-    private val TestCoroutineDispatcher.isNotEmpty: Boolean
-        get() {
-            this.scheduler::class.java.getDeclaredField("events").let {
-                it.isAccessible = true
-                val heap = it.get(this.scheduler) as ThreadSafeHeap<*>
-                return !heap.isEmpty
-            }
-        }
-
-    /**
-     * Checks whether a [TestCoroutineDispatcher] is paused or not using reflection.
-     */
-    private val TestCoroutineDispatcher.isNotPaused: Boolean
-        get() {
-            this@isNotPaused::class.java.getDeclaredField("dispatchImmediately").let {
-                it.isAccessible = true
-                return it.get(this) as Boolean
-            }
-        }
-
     data class Item(
         val id: Int,
         val height: Int = (RV_HEIGHT / 10) + (1 + (id % 10))
diff --git a/privacysandbox/ads/OWNERS b/privacysandbox/ads/OWNERS
index dc742a8..dfb8b61 100644
--- a/privacysandbox/ads/OWNERS
+++ b/privacysandbox/ads/OWNERS
@@ -10,3 +10,4 @@
 npattan@google.com # Creator
 jmarkoff@google.com # DevRel Jetpack Lead
 carolinewang@google.com # DevRel TPM Lead
+ramarcus@google.com # Common PoC
diff --git a/room/room-gradle-plugin/build.gradle b/room/room-gradle-plugin/build.gradle
index 24552d6..7622ad9 100644
--- a/room/room-gradle-plugin/build.gradle
+++ b/room/room-gradle-plugin/build.gradle
@@ -41,7 +41,7 @@
     testImplementation(project(":internal-testutils-gradle-plugin"))
     testImplementation(gradleTestKit())
     testImplementation(libs.junit)
-    testImplementation(libs.truth)
+    testImplementation(project(":internal-testutils-kmp"))
     testImplementation(libs.testParameterInjector)
 
     testPlugin("com.android.tools.build:gradle:7.3.0")
diff --git a/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt b/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
index ce82693..42c16d8 100644
--- a/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
+++ b/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
@@ -16,8 +16,8 @@
 
 package androidx.room.gradle
 
+import androidx.kruth.assertThat
 import androidx.testutils.gradle.ProjectSetupRule
-import com.google.common.truth.Truth.assertThat
 import com.google.testing.junit.testparameterinjector.TestParameter
 import com.google.testing.junit.testparameterinjector.TestParameterInjector
 import java.io.File
diff --git a/room/room-paging-guava/build.gradle b/room/room-paging-guava/build.gradle
index e40d8bc..6acea55 100644
--- a/room/room-paging-guava/build.gradle
+++ b/room/room-paging-guava/build.gradle
@@ -31,7 +31,7 @@
     implementation(project(":room:room-guava"))
     api("androidx.paging:paging-guava:3.1.1")
 
-    androidTestImplementation(libs.truth)
+    androidTestImplementation(project(":internal-testutils-kmp"))
     androidTestImplementation(libs.testExtJunitKtx)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.kotlinTestJunit)
diff --git a/room/room-paging-guava/src/androidTest/kotlin/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSourceTest.kt b/room/room-paging-guava/src/androidTest/kotlin/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSourceTest.kt
index 88e0bdc..0a621a1 100644
--- a/room/room-paging-guava/src/androidTest/kotlin/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSourceTest.kt
+++ b/room/room-paging-guava/src/androidTest/kotlin/androidx/room/paging/guava/LimitOffsetListenableFuturePagingSourceTest.kt
@@ -18,6 +18,8 @@
 
 import android.database.Cursor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
 import androidx.paging.LoadType
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource
@@ -37,8 +39,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.testutils.TestExecutor
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
 import com.google.common.util.concurrent.FutureCallback
 import com.google.common.util.concurrent.Futures.addCallback
 import com.google.common.util.concurrent.ListenableFuture
@@ -183,7 +183,7 @@
             queryExecutor.executeAll() // run transformAsync and async function
 
             val result = listenableFuture.await()
-            assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+            assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
             assertTrue(listenableFuture.isDone)
         }
 
@@ -201,7 +201,7 @@
             queryExecutor.executeAll() // run transformAsync and async function
 
             val result = listenableFuture.await()
-            assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+            assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
             assertTrue(listenableFuture.isDone)
         }
 
@@ -552,7 +552,7 @@
             val callbackExecutor = TestExecutor()
             var onFailureReceived = false
             listenableFuture.onFailure(callbackExecutor) { throwable ->
-                assertThat(throwable).isInstanceOf(CancellationException::class.java)
+                assertThat(throwable).isInstanceOf<CancellationException>()
                 onFailureReceived = true
             }
 
@@ -580,7 +580,7 @@
             val callbackExecutor = TestExecutor()
             var onFailureReceived = false
             listenableFuture.onFailure(callbackExecutor) { throwable ->
-                assertThat(throwable).isInstanceOf(CancellationException::class.java)
+                assertThat(throwable).isInstanceOf<CancellationException>()
                 onFailureReceived = true
             }
 
@@ -612,7 +612,7 @@
             val callbackExecutor = TestExecutor()
             var onFailureReceived = false
             listenableFuture.onFailure(callbackExecutor) { throwable ->
-                assertThat(throwable).isInstanceOf(CancellationException::class.java)
+                assertThat(throwable).isInstanceOf<CancellationException>()
                 onFailureReceived = true
             }
 
@@ -732,7 +732,7 @@
             queryExecutor.executeNext()
 
             val result = listenableFuture.await()
-            assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+            assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
             assertThat(pagingSource.invalid)
         }
 
@@ -766,7 +766,7 @@
             queryExecutor.executeNext()
 
             val result = listenableFuture.await()
-            assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+            assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
             assertThat(pagingSource.invalid)
         }
 
diff --git a/room/room-paging-rxjava2/build.gradle b/room/room-paging-rxjava2/build.gradle
index d9b9d5d..13bcc9c 100644
--- a/room/room-paging-rxjava2/build.gradle
+++ b/room/room-paging-rxjava2/build.gradle
@@ -31,7 +31,7 @@
     implementation(project(":room:room-paging"))
     implementation(project(":room:room-rxjava2"))
 
-    androidTestImplementation(libs.truth)
+    androidTestImplementation(project(":internal-testutils-kmp"))
     androidTestImplementation(libs.testExtJunitKtx)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.kotlinTestJunit) //
diff --git a/room/room-paging-rxjava2/src/androidTest/kotlin/androidx/room/paging/rxjava2/LimitOffsetRxPagingSourceTest.kt b/room/room-paging-rxjava2/src/androidTest/kotlin/androidx/room/paging/rxjava2/LimitOffsetRxPagingSourceTest.kt
index 47c7485..fca3c49 100644
--- a/room/room-paging-rxjava2/src/androidTest/kotlin/androidx/room/paging/rxjava2/LimitOffsetRxPagingSourceTest.kt
+++ b/room/room-paging-rxjava2/src/androidTest/kotlin/androidx/room/paging/rxjava2/LimitOffsetRxPagingSourceTest.kt
@@ -18,6 +18,8 @@
 
 import android.database.Cursor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
 import androidx.paging.LoadType
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource.LoadParams
@@ -39,8 +41,6 @@
 import androidx.test.filters.MediumTest
 import androidx.testutils.TestExecutor
 import androidx.testutils.withTestTimeout
-import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
 import io.reactivex.Single
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicBoolean
@@ -225,7 +225,7 @@
 
         val single2 = pagingSource.append(key = 55)
         val result = single2.await()
-        Truth.assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
     }
 
     @Test
@@ -243,7 +243,7 @@
 
         // let room complete its tasks
         countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
-        Truth.assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
     }
 
     @Test
@@ -262,7 +262,7 @@
         // let room complete its tasks
         countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
         val result = observer.values().first()
-        Truth.assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
         observer.dispose()
     }
 
@@ -279,7 +279,7 @@
         val pagingSource2 = LimitOffsetRxPagingSourceImpl(db)
         val single2 = pagingSource2.refresh()
         val result2 = single2.await() as LoadResult.Page
-        Truth.assertThat(result2.data).containsExactlyElementsIn(
+        assertThat(result2.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(0, 15)
         )
     }
@@ -297,13 +297,13 @@
 
         val single2 = pagingSource.append(key = 40)
         val result2 = single2.await() as LoadResult.Page
-        Truth.assertThat(result2.data).containsExactlyElementsIn(
+        assertThat(result2.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(40, 45)
         )
 
         val single3 = pagingSource.append(key = 45) // sequential append
         val result3 = single3.await() as LoadResult.Page
-        Truth.assertThat(result3.data).containsExactlyElementsIn(
+        assertThat(result3.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(45, 50)
         )
     }
@@ -321,13 +321,13 @@
 
         val single2 = pagingSource.prepend(key = 40)
         val result2 = single2.await() as LoadResult.Page
-        Truth.assertThat(result2.data).containsExactlyElementsIn(
+        assertThat(result2.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(35, 40)
         )
 
         val single3 = pagingSource.prepend(key = 45) // sequential prepend
         val result3 = single3.await() as LoadResult.Page
-        Truth.assertThat(result3.data).containsExactlyElementsIn(
+        assertThat(result3.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(40, 45)
         )
     }
@@ -341,8 +341,8 @@
         val single = pagingSource.refresh()
             // dispose right after subscription
             .doOnSubscribe { disposable -> disposable.dispose() }
-            .doOnSuccess { Truth.assertWithMessage("The single should not succeed").fail() }
-            .doOnError { Truth.assertWithMessage("The single should not error out").fail() }
+            .doOnSuccess { assertWithMessage("The single should not succeed").fail() }
+            .doOnError { assertWithMessage("The single should not error out").fail() }
             .doOnDispose { isDisposed = true }
 
         assertFailsWith<AssertionError> { withTestTimeout(2) { single.await() } }
@@ -352,7 +352,7 @@
         // using same paging source
         val single2 = pagingSource.refresh()
         val result2 = single2.await() as LoadResult.Page
-        Truth.assertThat(result2.data).containsExactlyElementsIn(
+        assertThat(result2.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(0, 15)
         )
     }
@@ -366,8 +366,8 @@
         val single = pagingSource.append(key = 15)
             // dispose right after subscription
             .doOnSubscribe { disposable -> disposable.dispose() }
-            .doOnSuccess { Truth.assertWithMessage("The single should not succeed").fail() }
-            .doOnError { Truth.assertWithMessage("The single should not error out").fail() }
+            .doOnSuccess { assertWithMessage("The single should not succeed").fail() }
+            .doOnError { assertWithMessage("The single should not error out").fail() }
             .doOnDispose { isDisposed = true }
 
         assertFailsWith<AssertionError> { withTestTimeout(2) { single.await() } }
@@ -377,7 +377,7 @@
         // try with same key same paging source
         val single2 = pagingSource.append(key = 15)
         val result2 = single2.await() as LoadResult.Page
-        Truth.assertThat(result2.data).containsExactlyElementsIn(
+        assertThat(result2.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(15, 20)
         )
     }
@@ -391,8 +391,8 @@
         val single = pagingSource.prepend(key = 40)
             // dispose right after subscription
             .doOnSubscribe { disposable -> disposable.dispose() }
-            .doOnSuccess { Truth.assertWithMessage("The single should not succeed").fail() }
-            .doOnError { Truth.assertWithMessage("The single should not error out").fail() }
+            .doOnSuccess { assertWithMessage("The single should not succeed").fail() }
+            .doOnError { assertWithMessage("The single should not error out").fail() }
             .doOnDispose { isDisposed = true }
 
         assertFailsWith<AssertionError> { withTestTimeout(2) { single.await() } }
@@ -402,7 +402,7 @@
         // try with same key same paging source
         val single2 = pagingSource.prepend(key = 40)
         val result2 = single2.await() as LoadResult.Page
-        Truth.assertThat(result2.data).containsExactlyElementsIn(
+        assertThat(result2.data).containsExactlyElementsIn(
             ITEMS_LIST.subList(35, 40)
         )
     }
@@ -461,8 +461,8 @@
             var isDisposed = false
             val single = pagingSource.refresh()
                 .doOnSubscribe { Thread.sleep(300) } // subscribe but delay the load
-                .doOnSuccess { Truth.assertWithMessage("The single should not succeed").fail() }
-                .doOnError { Truth.assertWithMessage("The single should not error out").fail() }
+                .doOnSuccess { assertWithMessage("The single should not succeed").fail() }
+                .doOnError { assertWithMessage("The single should not error out").fail() }
                 .doOnDispose { isDisposed = true }
 
             val job = launch { single.await() }
diff --git a/room/room-paging-rxjava3/build.gradle b/room/room-paging-rxjava3/build.gradle
index a4ef997..5bb0a41 100644
--- a/room/room-paging-rxjava3/build.gradle
+++ b/room/room-paging-rxjava3/build.gradle
@@ -31,7 +31,7 @@
     implementation(project(":room:room-paging"))
     implementation(project(":room:room-rxjava3"))
 
-    androidTestImplementation(libs.truth)
+    androidTestImplementation(project(":internal-testutils-kmp"))
     androidTestImplementation(libs.testExtJunitKtx)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.kotlinCoroutinesRx3)
diff --git a/room/room-paging-rxjava3/src/androidTest/kotlin/androidx/room/paging/rxjava3/LimitOffsetRxPagingSourceTest.kt b/room/room-paging-rxjava3/src/androidTest/kotlin/androidx/room/paging/rxjava3/LimitOffsetRxPagingSourceTest.kt
index 931185e..1684489 100644
--- a/room/room-paging-rxjava3/src/androidTest/kotlin/androidx/room/paging/rxjava3/LimitOffsetRxPagingSourceTest.kt
+++ b/room/room-paging-rxjava3/src/androidTest/kotlin/androidx/room/paging/rxjava3/LimitOffsetRxPagingSourceTest.kt
@@ -18,6 +18,8 @@
 
 import android.database.Cursor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
 import androidx.paging.LoadType
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource.LoadParams
@@ -39,8 +41,6 @@
 import androidx.test.filters.MediumTest
 import androidx.testutils.TestExecutor
 import androidx.testutils.withTestTimeout
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
 import io.reactivex.rxjava3.core.Single
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicBoolean
@@ -225,7 +225,7 @@
 
         val single2 = pagingSource.append(key = 55)
         val result = single2.await()
-        assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
     }
 
     @Test
@@ -243,7 +243,7 @@
 
         // let room complete its tasks
         countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
-        assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
     }
 
     @Test
@@ -262,7 +262,7 @@
         // let room complete its tasks
         countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
         val result = observer.values().first()
-        assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<*, *>>()
         observer.dispose()
     }
 
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
index 31a7c7f..c04bcff 100644
--- a/room/room-paging/build.gradle
+++ b/room/room-paging/build.gradle
@@ -49,7 +49,7 @@
     kspAndroidTest(
             project(path: ":room:room-compiler", configuration: "shadowAndImplementation")
     )
-    androidTestImplementation(libs.truth)
+    androidTestImplementation(project(":internal-testutils-kmp"))
     androidTestImplementation("androidx.arch.core:core-testing:2.2.0")
     androidTestImplementation(project(":internal-testutils-common"))
     androidTestImplementation(projectOrArtifact(":paging:paging-testing"))
diff --git a/room/room-paging/src/androidTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt b/room/room-paging/src/androidTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
index 8231cf1..fe817aa 100644
--- a/room/room-paging/src/androidTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
+++ b/room/room-paging/src/androidTest/kotlin/androidx/room/paging/LimitOffsetPagingSourceTest.kt
@@ -18,6 +18,7 @@
 
 import android.database.Cursor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.kruth.assertThat
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource
 import androidx.paging.PagingSource.LoadParams
@@ -33,7 +34,6 @@
 import androidx.test.filters.SmallTest
 import androidx.testutils.FilteringExecutor
 import androidx.testutils.TestExecutor
-import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
 import kotlin.test.assertFailsWith
@@ -420,7 +420,7 @@
         // return a LoadResult.Invalid
         val result2 = pager.append()
 
-        assertThat(result2).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result2).isInstanceOf<LoadResult.Invalid<*, *>>()
     }
 
     @Test
@@ -494,7 +494,7 @@
         // return LoadResult.Invalid
         val result2 = pager.prepend()
 
-        assertThat(result2).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result2).isInstanceOf<LoadResult.Invalid<*, *>>()
     }
 
     @Test
@@ -760,9 +760,7 @@
 
         // the db write should cause pagingSource to realize it is invalid when it tries to
         // append
-        assertThat(pager.append()).isInstanceOf(
-            LoadResult.Invalid::class.java
-        )
+        assertThat(pager.append()).isInstanceOf<LoadResult.Invalid<*, *>>()
         assertThat(pagingSource.invalid).isTrue()
     }
 
@@ -793,9 +791,7 @@
 
         // the db write should cause pagingSource to realize it is invalid when it tries to
         // prepend
-        assertThat(pager.prepend()).isInstanceOf(
-            LoadResult.Invalid::class.java
-        )
+        assertThat(pager.prepend()).isInstanceOf<LoadResult.Invalid<*, *>>()
         assertThat(pagingSource.invalid).isTrue()
     }
 }
diff --git a/room/room-runtime/build.gradle b/room/room-runtime/build.gradle
index f5a047d..d099c3e 100644
--- a/room/room-runtime/build.gradle
+++ b/room/room-runtime/build.gradle
@@ -56,7 +56,7 @@
     testImplementation("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.kotlinTest)
-    testImplementation(libs.truth)
+    testImplementation(project(":internal-testutils-kmp"))
     testImplementation(libs.testRunner) // Needed for @FlakyTest and @Ignore
 
     androidTestImplementation(libs.junit)
@@ -68,6 +68,7 @@
     androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(project(":internal-testutils-truth")) // for assertThrows
+    androidTestImplementation(project(":internal-testutils-kmp"))
     androidTestImplementation("androidx.arch.core:core-testing:2.2.0")
 
 }
diff --git a/room/room-runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt b/room/room-runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
index 2c72153..17bcb4b 100644
--- a/room/room-runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
+++ b/room/room-runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.kruth.assertThat
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
@@ -26,7 +27,6 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.testutils.assertThrows
-import com.google.common.truth.Truth.assertThat
 import java.io.IOException
 import java.util.concurrent.TimeUnit
 import org.junit.After
diff --git a/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt b/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
index 0a22c62..dae040f 100644
--- a/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
+++ b/room/room-runtime/src/androidTest/java/androidx/room/AutoClosingRoomOpenHelperTest.kt
@@ -21,6 +21,7 @@
 import android.database.sqlite.SQLiteException
 import android.os.Build
 import androidx.annotation.RequiresApi
+import androidx.kruth.assertThat
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
@@ -28,7 +29,7 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SdkSuppress
 import androidx.testutils.assertThrows
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
 import java.io.IOException
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
@@ -237,7 +238,7 @@
         db.query("select * from users").useCursor {
             assertThat(it.moveToFirst()).isTrue()
             assertThat(it.getInt(0)).isEqualTo(123)
-            assertThat(it.getDouble(1)).isWithin(.01).of(1.23)
+            Truth.assertThat(it.getDouble(1)).isWithin(.01).of(1.23)
 
             assertThat(it.getBlob(2)).isEqualTo(byteArrayOf(1, 2, 3))
             assertThat(it.isNull(3)).isTrue()
diff --git a/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt b/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt
index 45b1274..e48d8ed 100644
--- a/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/BuilderTest.kt
@@ -16,13 +16,13 @@
 package androidx.room
 
 import android.content.Context
+import androidx.kruth.assertThat
 import androidx.room.Room.databaseBuilder
 import androidx.room.Room.inMemoryDatabaseBuilder
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
-import com.google.common.truth.Truth.assertThat
 import java.io.File
 import java.util.concurrent.Executor
 import org.junit.Assert
@@ -339,7 +339,7 @@
     fun createBasic() {
         val context: Context = mock()
         val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java).build()
-        assertThat(db).isInstanceOf(BuilderTest_TestDatabase_Impl::class.java)
+        assertThat(db).isInstanceOf<BuilderTest_TestDatabase_Impl>()
         val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
         assertThat(config).isNotNull()
         assertThat(config.context).isEqualTo(context)
@@ -347,7 +347,7 @@
         assertThat(config.allowMainThreadQueries).isFalse()
         assertThat(config.journalMode).isEqualTo(RoomDatabase.JournalMode.TRUNCATE)
         assertThat(config.sqliteOpenHelperFactory)
-            .isInstanceOf(FrameworkSQLiteOpenHelperFactory::class.java)
+            .isInstanceOf<FrameworkSQLiteOpenHelperFactory>()
     }
 
     @Test
@@ -365,7 +365,7 @@
         val context: Context = mock()
         val db = databaseBuilder(context, TestDatabase::class.java, "foo")
             .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING).build()
-        assertThat(db).isInstanceOf(BuilderTest_TestDatabase_Impl::class.java)
+        assertThat(db).isInstanceOf<BuilderTest_TestDatabase_Impl>()
         val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
         assertThat(config.journalMode).isEqualTo(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
     }
@@ -377,7 +377,7 @@
         val db = inMemoryDatabaseBuilder(context, TestDatabase::class.java)
             .openHelperFactory(factory)
             .build()
-        assertThat(db).isInstanceOf(BuilderTest_TestDatabase_Impl::class.java)
+        assertThat(db).isInstanceOf<BuilderTest_TestDatabase_Impl>()
         val config: DatabaseConfiguration = (db as BuilderTest_TestDatabase_Impl).mConfig
         assertThat(config).isNotNull()
         assertThat(config.sqliteOpenHelperFactory).isEqualTo(factory)
@@ -399,7 +399,7 @@
         } catch (e: Exception) {
             exception = e
         }
-        assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exception).isInstanceOf<IllegalArgumentException>()
         assertThat(exception).hasMessageThat().contains("More than one of createFromAsset(), " +
             "createFromInputStream(), and createFromFile() were called on this Builder")
     }
@@ -418,7 +418,7 @@
         } catch (e: Exception) {
             exception = e
         }
-        assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exception).isInstanceOf<IllegalArgumentException>()
         assertThat(exception).hasMessageThat().contains(
             "Cannot create from asset or file for an in-memory"
         )
@@ -438,7 +438,7 @@
         } catch (e: Exception) {
             exception = e
         }
-        assertThat(exception).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exception).isInstanceOf<IllegalArgumentException>()
         assertThat(exception).hasMessageThat().contains(
             "Cannot create from asset or file for an in-memory"
         )
diff --git a/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt b/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
index 060110e..6c95435 100644
--- a/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
@@ -19,12 +19,12 @@
 import android.database.sqlite.SQLiteException
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.JunitTaskExecutorRule
+import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
 import androidx.sqlite.db.SimpleSQLiteQuery
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.sqlite.db.SupportSQLiteStatement
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
 import java.lang.ref.ReferenceQueue
 import java.lang.ref.WeakReference
 import java.util.Locale
diff --git a/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt b/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt
index 10ac217..0b7c29f 100644
--- a/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/ObservedTableTrackerTest.kt
@@ -15,7 +15,7 @@
  */
 package androidx.room
 
-import com.google.common.truth.Truth.assertThat
+import androidx.kruth.assertThat
 import java.util.Arrays
 import kotlin.test.assertNull
 import org.junit.Before
diff --git a/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt b/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt
index 669b7f2..44c455f 100644
--- a/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/RoomSQLiteQueryTest.kt
@@ -16,9 +16,9 @@
 
 package androidx.room
 
+import androidx.kruth.assertThat
 import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.sqlite.db.SupportSQLiteProgram
-import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt b/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt
index c9da445..705f7a1 100644
--- a/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/SharedSQLiteStatementTest.kt
@@ -15,8 +15,8 @@
  */
 package androidx.room
 
+import androidx.kruth.assertThat
 import androidx.sqlite.db.SupportSQLiteStatement
-import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.FutureTask
 import org.junit.Before
 import org.junit.Test
diff --git a/room/room-runtime/src/test/java/androidx/room/TransactionExecutorTest.kt b/room/room-runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
index 9163232..df061cc 100644
--- a/room/room-runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
@@ -16,7 +16,7 @@
 
 package androidx.room
 
-import com.google.common.truth.Truth.assertThat
+import androidx.kruth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
diff --git a/room/room-runtime/src/test/java/androidx/room/util/CursorUtilTest.kt b/room/room-runtime/src/test/java/androidx/room/util/CursorUtilTest.kt
index f2b673b..3b5c269 100644
--- a/room/room-runtime/src/test/java/androidx/room/util/CursorUtilTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/util/CursorUtilTest.kt
@@ -15,7 +15,7 @@
  */
 package androidx.room.util
 
-import com.google.common.truth.Truth.assertThat
+import androidx.kruth.assertThat
 import org.junit.Test
 
 class CursorUtilTest {
diff --git a/room/room-runtime/src/test/java/androidx/room/util/UUIDUtilTest.kt b/room/room-runtime/src/test/java/androidx/room/util/UUIDUtilTest.kt
index aafd7cd..7c5105d 100644
--- a/room/room-runtime/src/test/java/androidx/room/util/UUIDUtilTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/util/UUIDUtilTest.kt
@@ -16,7 +16,7 @@
 
 package androidx.room.util
 
-import com.google.common.truth.Truth.assertThat
+import androidx.kruth.assertThat
 import java.nio.ByteBuffer
 import java.util.UUID
 import kotlin.random.Random
diff --git a/room/room-rxjava2/build.gradle b/room/room-rxjava2/build.gradle
index c802802..0425945 100644
--- a/room/room-rxjava2/build.gradle
+++ b/room/room-rxjava2/build.gradle
@@ -30,7 +30,7 @@
     implementation("androidx.arch.core:core-runtime:2.2.0")
     implementation(libs.kotlinStdlib)
 
-    testImplementation(libs.truth)
+    testImplementation(project(":internal-testutils-kmp"))
     testImplementation(libs.kotlinTest)
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.mockitoKotlin4)
diff --git a/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt b/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
index a5d9af9..1aa1aec 100644
--- a/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
+++ b/room/room-rxjava2/src/test/java/androidx/room/RxRoomTest.kt
@@ -17,7 +17,7 @@
 
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
-import com.google.common.truth.Truth.assertThat
+import androidx.kruth.assertThat
 import io.reactivex.functions.Consumer
 import io.reactivex.observers.TestObserver
 import io.reactivex.subscribers.TestSubscriber
diff --git a/room/room-rxjava3/build.gradle b/room/room-rxjava3/build.gradle
index 3533c33..b83c5b0 100644
--- a/room/room-rxjava3/build.gradle
+++ b/room/room-rxjava3/build.gradle
@@ -31,7 +31,7 @@
     implementation("androidx.arch.core:core-runtime:2.2.0")
     implementation(libs.kotlinStdlib)
 
-    testImplementation(libs.truth)
+    testImplementation(project(":internal-testutils-kmp"))
     testImplementation(libs.kotlinTest)
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.mockitoKotlin4)
diff --git a/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt b/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
index e63839a..447d37a 100644
--- a/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
+++ b/room/room-rxjava3/src/test/java/androidx/room/rxjava3/RxRoomTest.kt
@@ -17,9 +17,9 @@
 
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.kruth.assertThat
 import androidx.room.InvalidationTracker
 import androidx.room.RoomDatabase
-import com.google.common.truth.Truth.assertThat
 import io.reactivex.rxjava3.functions.Consumer
 import io.reactivex.rxjava3.observers.TestObserver
 import io.reactivex.rxjava3.subscribers.TestSubscriber
diff --git a/room/room-testing/build.gradle b/room/room-testing/build.gradle
index e8970dc..67f56fd 100644
--- a/room/room-testing/build.gradle
+++ b/room/room-testing/build.gradle
@@ -38,7 +38,8 @@
     api(project(":room:room-migration"))
     api(libs.junit)
     implementation("androidx.arch.core:core-runtime:2.2.0")
-    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.truth) // Kruth currently lacks StringSubject#containsMatch
+    androidTestImplementation(project(":internal-testutils-kmp"))
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
index cc8902e..6b8e988 100644
--- a/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
+++ b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
@@ -17,13 +17,14 @@
 package androidx.room.testing.kotlintestapp.migration
 
 import android.database.sqlite.SQLiteException
+import androidx.kruth.assertThat
 import androidx.room.migration.Migration
 import androidx.room.testing.MigrationTestHelper
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,7 +64,7 @@
                 MIGRATION_1_2
             )
         } catch (e: SQLiteException) {
-            assertThat(e.message).containsMatch("no such table: Entity0")
+            Truth.assertThat(e.message).containsMatch("no such table: Entity0")
         }
     }
 
diff --git a/settings.gradle b/settings.gradle
index 042416b..ceba832 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -418,8 +418,14 @@
 includeProject(":annotation:annotation-experimental-lint")
 includeProject(":annotation:annotation-experimental-lint-integration-tests", "annotation/annotation-experimental-lint/integration-tests")
 includeProject(":annotation:annotation-sampled")
-includeProject(":appactions:builtintypes:builtintypes-core", [BuildType.MAIN])
-includeProject(":appactions:builtintypes:builtintypes-core:builtintypes-core-samples", "appactions/builtintypes/builtintypes-core/samples", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes:builtintypes-samples", "appactions/builtintypes/builtintypes/samples", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes-common", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes-common:builtintypes-common-samples", "appactions/builtintypes/builtintypes-common/samples", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes-communications", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes-communications:builtintypes-communications-samples", "appactions/builtintypes/builtintypes-communications/samples", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes-productivity", [BuildType.MAIN])
+includeProject(":appactions:builtintypes:builtintypes-productivity:builtintypes-productivity-samples", "appactions/builtintypes/builtintypes-productivity/samples", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-capabilities-communication", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-capabilities-core", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-capabilities-fitness", [BuildType.MAIN])
@@ -429,6 +435,7 @@
 includeProject(":appactions:interaction:interaction-proto", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-service", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-service-proto", [BuildType.MAIN])
+includeProject(":appactions:interaction:interaction-service-testing", [BuildType.MAIN])
 includeProject(":appactions:interaction:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":appcompat:appcompat", [BuildType.MAIN])
 includeProject(":appcompat:appcompat-benchmark", [BuildType.MAIN])
@@ -677,6 +684,8 @@
 includeProject(":core:core-ktx", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":core:core-location-altitude", [BuildType.MAIN])
 includeProject(":core:core-performance", [BuildType.MAIN])
+includeProject(":core:core-performance-testing", [BuildType.MAIN])
+includeProject(":core:core-performance-play-services", [BuildType.MAIN])
 includeProject(":core:core-performance:core-performance-samples", "core/core-performance/samples", [BuildType.MAIN])
 includeProject(":core:core-remoteviews", [BuildType.MAIN, BuildType.GLANCE])
 includeProject(":core:core-remoteviews:integration-tests:demos", [BuildType.MAIN, BuildType.GLANCE])
@@ -1065,6 +1074,7 @@
 includeProject(":wear:watchface:watchface-style", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:watchface:watchface-style-old-api-test-service", "wear/watchface/watchface-style/old-api-test-service", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:watchface:watchface-style-old-api-test-stub", "wear/watchface/watchface-style/old-api-test-stub", [BuildType.MAIN, BuildType.WEAR])
+includeProject(":webkit:integration-tests:instrumentation", [BuildType.MAIN])
 includeProject(":webkit:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":webkit:webkit", [BuildType.MAIN])
 includeProject(":window:window", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.WINDOW])
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 11e4e98..d47bdc9 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -347,6 +347,44 @@
     method public androidx.compose.ui.Modifier immersiveListItem(androidx.compose.ui.Modifier, int index);
   }
 
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemBorder {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemColors {
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ListItemBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border selectedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedSelectedBorder, optional androidx.tv.material3.Border focusedDisabledBorder, optional androidx.tv.material3.Border pressedSelectedBorder);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ListItemColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor, optional long selectedContainerColor, optional long selectedContentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long focusedSelectedContainerColor, optional long focusedSelectedContentColor, optional long pressedSelectedContainerColor, optional long pressedSelectedContentColor);
+    method public float getIconSize();
+    method public float getListItemElevation();
+    method public androidx.tv.material3.ListItemGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow, optional androidx.tv.material3.Glow selectedGlow, optional androidx.tv.material3.Glow focusedSelectedGlow, optional androidx.tv.material3.Glow pressedSelectedGlow);
+    method public androidx.tv.material3.ListItemScale scale(optional @FloatRange(from=0.0) float scale, optional @FloatRange(from=0.0) float focusedScale, optional @FloatRange(from=0.0) float pressedScale, optional @FloatRange(from=0.0) float selectedScale, optional @FloatRange(from=0.0) float disabledScale, optional @FloatRange(from=0.0) float focusedSelectedScale, optional @FloatRange(from=0.0) float focusedDisabledScale, optional @FloatRange(from=0.0) float pressedSelectedScale);
+    method public androidx.tv.material3.ListItemShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape selectedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedSelectedShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape, optional androidx.compose.ui.graphics.Shape pressedSelectedShape);
+    property public final float IconSize;
+    property public final float ListItemElevation;
+    field public static final androidx.tv.material3.ListItemDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemGlow {
+  }
+
+  public final class ListItemKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ListItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> headlineContent, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? overlineContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional float tonalElevation, optional androidx.tv.material3.ListItemShape shape, optional androidx.tv.material3.ListItemColors colors, optional androidx.tv.material3.ListItemScale scale, optional androidx.tv.material3.ListItemBorder border, optional androidx.tv.material3.ListItemGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemScale {
+    field public static final androidx.tv.material3.ListItemScale.Companion Companion;
+  }
+
+  public static final class ListItemScale.Companion {
+    method public androidx.tv.material3.ListItemScale getNone();
+    property public final androidx.tv.material3.ListItemScale None;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemShape {
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 11e4e98..d47bdc9 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -347,6 +347,44 @@
     method public androidx.compose.ui.Modifier immersiveListItem(androidx.compose.ui.Modifier, int index);
   }
 
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemBorder {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemColors {
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ListItemBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border selectedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedSelectedBorder, optional androidx.tv.material3.Border focusedDisabledBorder, optional androidx.tv.material3.Border pressedSelectedBorder);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ListItemColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor, optional long selectedContainerColor, optional long selectedContentColor, optional long disabledContainerColor, optional long disabledContentColor, optional long focusedSelectedContainerColor, optional long focusedSelectedContentColor, optional long pressedSelectedContainerColor, optional long pressedSelectedContentColor);
+    method public float getIconSize();
+    method public float getListItemElevation();
+    method public androidx.tv.material3.ListItemGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow, optional androidx.tv.material3.Glow selectedGlow, optional androidx.tv.material3.Glow focusedSelectedGlow, optional androidx.tv.material3.Glow pressedSelectedGlow);
+    method public androidx.tv.material3.ListItemScale scale(optional @FloatRange(from=0.0) float scale, optional @FloatRange(from=0.0) float focusedScale, optional @FloatRange(from=0.0) float pressedScale, optional @FloatRange(from=0.0) float selectedScale, optional @FloatRange(from=0.0) float disabledScale, optional @FloatRange(from=0.0) float focusedSelectedScale, optional @FloatRange(from=0.0) float focusedDisabledScale, optional @FloatRange(from=0.0) float pressedSelectedScale);
+    method public androidx.tv.material3.ListItemShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape selectedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedSelectedShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape, optional androidx.compose.ui.graphics.Shape pressedSelectedShape);
+    property public final float IconSize;
+    property public final float ListItemElevation;
+    field public static final androidx.tv.material3.ListItemDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemGlow {
+  }
+
+  public final class ListItemKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ListItem(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> headlineContent, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? overlineContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional float tonalElevation, optional androidx.tv.material3.ListItemShape shape, optional androidx.tv.material3.ListItemColors colors, optional androidx.tv.material3.ListItemScale scale, optional androidx.tv.material3.ListItemBorder border, optional androidx.tv.material3.ListItemGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemScale {
+    field public static final androidx.tv.material3.ListItemScale.Companion Companion;
+  }
+
+  public static final class ListItemScale.Companion {
+    method public androidx.tv.material3.ListItemScale getNone();
+    property public final androidx.tv.material3.ListItemScale None;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ListItemShape {
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt
new file mode 100644
index 0000000..41ea83f
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt
@@ -0,0 +1,424 @@
+/*
+ * 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.tv.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.KeyboardArrowRight
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalTvMaterial3Api::class)
+class ListItemScreenshotTest(private val scheme: ColorSchemeWrapper) {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    val wrapperModifier = Modifier
+        .testTag(ListItemWrapperTag)
+        .background(scheme.colorScheme.surface)
+        .padding(20.dp)
+
+    @Test
+    fun listItem_customColor() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("One line list item") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    },
+                    colors = ListItemDefaults.colors(containerColor = Color.Red)
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_customColor")
+    }
+
+    @Test
+    fun listItem_oneLine() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("One line list item") }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_oneLine")
+    }
+
+    @Test
+    fun listItem_oneLine_withIcon() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("One line list item") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_oneLine_withIcon")
+    }
+
+    @Test
+    fun listItem_twoLine() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Column(
+                modifier = wrapperModifier,
+                verticalArrangement = Arrangement.spacedBy(20.dp)
+            ) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Two line list item") },
+                    supportingContent = { Text("Secondary text") }
+                )
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Two line list item") },
+                    overlineContent = { Text("OVERLINE") }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_twoLine")
+    }
+
+    @Test
+    fun listItem_twoLine_withIcon() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Column(
+                modifier = wrapperModifier,
+                verticalArrangement = Arrangement.spacedBy(20.dp)
+            ) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Two line list item") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Two line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_twoLine_withIcon")
+    }
+
+    @Test
+    fun listItem_threeLine() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine")
+    }
+
+    @Test
+    fun listItem_threeLine_withIcon() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_withIcon")
+    }
+
+    @Test
+    fun listItem_threeLine_focused() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemWrapperTag)
+            .onChild()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.waitForIdle()
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_focused")
+    }
+
+    @Test
+    fun listItem_threeLine_disabled() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    enabled = false,
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_disabled")
+    }
+
+    @Test
+    fun listItem_threeLine_focusedDisabled() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    enabled = false,
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemWrapperTag)
+            .onChild()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.waitForIdle()
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_focusedDisabled")
+    }
+
+    @Test
+    fun listItem_threeLine_selected() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = true,
+                    onClick = {},
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_selected")
+    }
+
+    @Test
+    fun listItem_threeLine_focusedSelected() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = true,
+                    onClick = {},
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemWrapperTag)
+            .onChild()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.waitForIdle()
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_focusedSelected")
+    }
+
+    @Test
+    fun listItem_threeLine_withTrailingContent() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(modifier = wrapperModifier) {
+                ListItem(
+                    selected = false,
+                    onClick = {},
+                    headlineContent = { Text("Three line list item") },
+                    overlineContent = { Text("OVERLINE") },
+                    supportingContent = { Text("Secondary text") },
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    },
+                    trailingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.KeyboardArrowRight,
+                            contentDescription = null,
+                            modifier = Modifier.size(ListItemDefaults.IconSize)
+                        )
+                    }
+                )
+            }
+        }
+
+        assertAgainstGolden("listItem_${scheme.name}_threeLine_withTrailingContent")
+    }
+
+    private fun assertAgainstGolden(goldenName: String) {
+        rule.onNodeWithTag(ListItemWrapperTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenName)
+    }
+
+    // Provide the ColorScheme and their name parameter in a ColorSchemeWrapper.
+    // This makes sure that the default method name and the initial Scuba image generated
+    // name is as expected.
+    companion object {
+        @OptIn(ExperimentalTvMaterial3Api::class)
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters() = arrayOf(
+            ColorSchemeWrapper("lightTheme", lightColorScheme()),
+            ColorSchemeWrapper("darkTheme", darkColorScheme()),
+        )
+    }
+
+    @OptIn(ExperimentalTvMaterial3Api::class)
+    class ColorSchemeWrapper constructor(val name: String, val colorScheme: ColorScheme) {
+        override fun toString(): String {
+            return name
+        }
+    }
+}
+
+private const val ListItemWrapperTag = "listItem_wrapper"
\ No newline at end of file
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemTest.kt
new file mode 100644
index 0000000..e965a5f
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemTest.kt
@@ -0,0 +1,494 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+    ExperimentalTestApi::class,
+    ExperimentalTvMaterial3Api::class
+)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ListItemTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun listItem_findByTagAndClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            Box {
+                ListItem(
+                    modifier = Modifier.testTag(ListItemTag),
+                    headlineContent = { Text(text = "Test Text") },
+                    onClick = onClick,
+                    selected = false
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun listItem_clickIsIndependentBetweenItems() {
+        var openItemClickCounter = 0
+        val openItemOnClick: () -> Unit = { ++openItemClickCounter }
+        val openItemTag = "OpenItem"
+
+        var closeItemClickCounter = 0
+        val closeItemOnClick: () -> Unit = { ++closeItemClickCounter }
+        val closeItemTag = "CloseItem"
+
+        rule.setContent {
+            Column {
+                ListItem(
+                    modifier = Modifier.testTag(openItemTag),
+                    headlineContent = { Text(text = "Test Text") },
+                    onClick = openItemOnClick,
+                    selected = false
+                )
+                ListItem(
+                    modifier = Modifier.testTag(closeItemTag),
+                    headlineContent = { Text(text = "Test Text") },
+                    onClick = closeItemOnClick,
+                    selected = false
+                )
+            }
+        }
+
+        rule.onNodeWithTag(openItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(openItemClickCounter).isEqualTo(1)
+            Truth.assertThat(closeItemClickCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(closeItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(openItemClickCounter).isEqualTo(1)
+            Truth.assertThat(closeItemClickCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun listItem_longClickAction() {
+        var counter = 0
+        val onLongClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            Box {
+                ListItem(
+                    modifier = Modifier.testTag(ListItemTag),
+                    headlineContent = { Text(text = "Test Text") },
+                    onClick = { },
+                    onLongClick = onLongClick,
+                    selected = false
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performLongKeyPress(rule, Key.DirectionCenter)
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performLongKeyPress(rule, Key.DirectionCenter, count = 2)
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(3)
+        }
+    }
+
+    @Test
+    fun listItem_findByTagAndStateChangeCheck() {
+        var checkedState by mutableStateOf(true)
+        val onClick: () -> Unit = { checkedState = !checkedState }
+
+        rule.setContent {
+            ListItem(
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = { Text(text = "Test Text") },
+                onClick = onClick,
+                selected = checkedState
+            )
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(!checkedState)
+        }
+    }
+
+    @Test
+    fun listItem_contentPaddingHorizontal() {
+        rule.setContent {
+            ListItem(
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = {
+                    Text(
+                        text = "ListItemText",
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .semantics(mergeDescendants = true) {}
+                    )
+                },
+                onClick = {},
+                selected = false
+            )
+        }
+
+        val itemBounds = rule.onNodeWithTag(ListItemTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithText("ListItemText").getUnclippedBoundsInRoot()
+
+        (textBounds.left - itemBounds.left).assertIsEqualTo(
+            16.dp,
+            "padding between the start of the list item and the start of the text."
+        )
+
+        (itemBounds.right - textBounds.right).assertIsEqualTo(
+            16.dp,
+            "padding between the end of the text and the end of the list item."
+        )
+    }
+
+    @Test
+    fun listItem_contentPaddingVertical() {
+        rule.setContent {
+            ListItem(
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = {
+                    Text(
+                        text = "Test Text",
+                        modifier = Modifier
+                            .fillMaxHeight()
+                            .testTag(ListItemTextTag)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                },
+                onClick = {},
+                selected = false
+            )
+        }
+
+        val itemBounds = rule.onNodeWithTag(ListItemTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(ListItemTextTag).getUnclippedBoundsInRoot()
+
+        (textBounds.top - itemBounds.top).assertIsEqualTo(
+            12.dp,
+            "padding between the top of the list item and the top of the text."
+        )
+
+        (itemBounds.bottom - textBounds.bottom).assertIsEqualTo(
+            12.dp,
+            "padding between the bottom of the text and the bottom of the list item."
+        )
+    }
+
+    @Test
+    fun listItem_iconPadding() {
+        val testIconTag = "IconTag"
+
+        rule.setContent {
+            ListItem(
+                leadingContent = {
+                    Box(
+                        modifier = Modifier
+                            .size(ListItemDefaults.IconSize)
+                            .testTag(testIconTag)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                },
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = {
+                    Text(
+                        text = "Test Text",
+                        modifier = Modifier
+                            .testTag(ListItemTextTag)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                },
+                onClick = {},
+                selected = false
+            )
+        }
+
+        val itemBounds = rule.onNodeWithTag(ListItemTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(ListItemTextTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(testIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.top - itemBounds.top).assertIsEqualTo(
+            12.dp,
+            "padding between the top of the list item and the top of the icon."
+        )
+
+        (itemBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            12.dp,
+            "padding between the bottom of the icon and the bottom of the list item."
+        )
+
+        (iconBounds.left - itemBounds.left).assertIsEqualTo(
+            16.dp,
+            "padding between the start of the icon and the start of the list item."
+        )
+
+        (textBounds.left - iconBounds.right).assertIsEqualTo(
+            8.dp,
+            "padding between the end of the icon and the start of the text."
+        )
+    }
+
+    @Test
+    fun listItem_trailingContentPadding() {
+        val testTrailingContentTag = "TrailingIconTag"
+
+        rule.setContent {
+            ListItem(
+                leadingContent = {
+                    Box(
+                        modifier = Modifier
+                            .size(ListItemDefaults.IconSize)
+                            .testTag(testTrailingContentTag)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                },
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = {
+                    Text(
+                        text = "Test Text",
+                        modifier = Modifier
+                            .testTag(ListItemTextTag)
+                            .fillMaxWidth()
+                            .semantics(mergeDescendants = true) {}
+                    )
+                },
+                onClick = {},
+                selected = false
+            )
+        }
+
+        val itemBounds = rule.onNodeWithTag(ListItemTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(ListItemTextTag).getUnclippedBoundsInRoot()
+        val trailingContentBounds =
+            rule.onNodeWithTag(testTrailingContentTag).getUnclippedBoundsInRoot()
+
+        (trailingContentBounds.top - itemBounds.top).assertIsEqualTo(
+            12.dp,
+            "padding between the top of the list item and the top of the trailing content."
+        )
+
+        (itemBounds.bottom - trailingContentBounds.bottom).assertIsEqualTo(
+            12.dp,
+            "padding between the bottom of the trailing content and the bottom of the list item."
+        )
+
+        (trailingContentBounds.left - itemBounds.left).assertIsEqualTo(
+            16.dp,
+            "padding between the start of the trailing content and the start of the list item."
+        )
+
+        (textBounds.left - trailingContentBounds.right).assertIsEqualTo(
+            8.dp,
+            "padding between the end of the trailing content and the start of the text."
+        )
+    }
+
+    @Test
+    fun listItem_semantics() {
+        var selected by mutableStateOf(false)
+        rule.setContent {
+            Box {
+                ListItem(
+                    modifier = Modifier.testTag(ListItemTag),
+                    onClick = { selected = !selected },
+                    headlineContent = { Text(text = "List item") },
+                    selected = selected
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .assertHasClickAction()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Selected))
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .assertIsEnabled()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, true))
+        Truth.assertThat(selected).isEqualTo(true)
+    }
+
+    @Test
+    fun listItem_longClickSemantics() {
+        var selected by mutableStateOf(false)
+        rule.setContent {
+            Box {
+                ListItem(
+                    modifier = Modifier.testTag(ListItemTag),
+                    onClick = { },
+                    onLongClick = { selected = !selected },
+                    headlineContent = { Text(text = "List item") },
+                    selected = selected
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .assertHasClickAction()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Selected))
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .assertIsEnabled()
+            .performLongKeyPress(rule, Key.DirectionCenter)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, true))
+        Truth.assertThat(selected).isEqualTo(true)
+    }
+
+    @Test
+    fun listItem_disabledSemantics() {
+        rule.setContent {
+            Box {
+                ListItem(
+                    modifier = Modifier.testTag(ListItemTag),
+                    onClick = {},
+                    enabled = false,
+                    headlineContent = { Text(text = "List Item") },
+                    selected = false
+                )
+            }
+        }
+
+        rule.onNodeWithTag(ListItemTag)
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun listItem_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            Box {
+                ListItem(
+                    modifier = Modifier.testTag(ListItemTag),
+                    onClick = { enabled = false },
+                    enabled = enabled,
+                    headlineContent = { Text(text = "List Item") },
+                    selected = false
+                )
+            }
+        }
+        rule.onNodeWithTag(ListItemTag)
+            // Confirm the button starts off enabled, with a click action
+            .assertHasClickAction()
+            .assertIsEnabled()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            // Then confirm it's disabled with click action after clicking it
+            .assertHasClickAction()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun listItem_oneLineHeight() {
+        val expectedHeightNoIcon = 48.dp
+
+        rule.setContent {
+            ListItem(
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = { Text(text = "text") },
+                onClick = {},
+                selected = false
+            )
+        }
+
+        rule.onNodeWithTag(ListItemTag).assertHeightIsEqualTo(expectedHeightNoIcon)
+    }
+
+    @Test
+    fun listItem_width() {
+        rule.setContent {
+            ListItem(
+                modifier = Modifier.testTag(ListItemTag),
+                headlineContent = { Text(text = "text") },
+                onClick = {},
+                selected = false
+            )
+        }
+        rule.onNodeWithTag(ListItemTag)
+            .assertWidthIsEqualTo(rule.onRoot().getUnclippedBoundsInRoot().width)
+    }
+}
+
+private const val ListItemTag = "ListItem"
+private const val ListItemTextTag = "ListItemText"
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt
new file mode 100644
index 0000000..5ba1481
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ListItem.kt
@@ -0,0 +1,383 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+
+/**
+ * Lists are continuous, vertical indexes of text or images.
+ *
+ * This component can be used to achieve the list item templates existing in the spec. One-line list
+ * items have a singular line of headline content. Two-line list items additionally have either
+ * supporting or overline content. Three-line list items have either both supporting and overline
+ * content, or extended (two-line) supporting text.
+ *
+ * This ListItem handles click events, calling its [onClick] lambda. It also support selected state
+ * which can be toggled using the [selected] param.
+ *
+ * @param selected defines whether this ListItem is selected or not
+ * @param onClick called when this ListItem is clicked
+ * @param headlineContent the [Composable] headline content of the list item
+ * @param modifier [Modifier] to be applied to the list item
+ * @param enabled controls the enabled state of this list item. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param onLongClick called when this ListItem is long clicked (long-pressed).
+ * @param leadingContent the [Composable] leading content of the list item
+ * @param overlineContent the [Composable] content displayed above the headline content
+ * @param supportingContent the [Composable] content displayed below the headline content
+ * @param trailingContent the [Composable] trailing meta text, icon, switch or checkbox
+ * @param tonalElevation the tonal elevation of this list item
+ * @param shape [ListItemShape] defines the shape of ListItem's container in different interaction
+ * states. See [ListItemDefaults.shape].
+ * @param colors [ListItemColors] defines the background and content colors used in the list item
+ * for different interaction states. See [ListItemDefaults.colors]
+ * @param scale [ListItemScale] defines the size of the list item relative to its original size in
+ * different interaction states. See [ListItemDefaults.scale]
+ * @param border [ListItemBorder] defines a border around the list item in different interaction
+ * states. See [ListItemDefaults.border]
+ * @param glow [ListItemGlow] defines a shadow to be shown behind the list item for different
+ * interaction states. See [ListItemDefaults.glow]
+ * @param interactionSource the [MutableInteractionSource] representing the stream of
+ * [Interaction]s for this component. You can create and pass in your own [remember]ed instance
+ * to observe [Interaction]s and customize the appearance / behavior of this list item in different
+ * states.
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun ListItem(
+    selected: Boolean,
+    onClick: () -> Unit,
+    headlineContent: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    onLongClick: (() -> Unit)? = null,
+    overlineContent: (@Composable () -> Unit)? = null,
+    supportingContent: (@Composable () -> Unit)? = null,
+    leadingContent: (@Composable BoxScope.() -> Unit)? = null,
+    trailingContent: (@Composable () -> Unit)? = null,
+    tonalElevation: Dp = ListItemDefaults.ListItemElevation,
+    shape: ListItemShape = ListItemDefaults.shape(),
+    colors: ListItemColors = ListItemDefaults.colors(),
+    scale: ListItemScale = ListItemDefaults.scale(),
+    border: ListItemBorder = ListItemDefaults.border(),
+    glow: ListItemGlow = ListItemDefaults.glow(),
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+) {
+    BaseListItem(
+        selected = selected,
+        onClick = onClick,
+        headlineContent = headlineContent,
+        contentPadding = ListItemDefaults.ContentPadding,
+        modifier = modifier,
+        enabled = enabled,
+        onLongClick = onLongClick,
+        leadingContent = leadingContent,
+        overlineContent = overlineContent,
+        supportingContent = supportingContent,
+        trailingContent = trailingContent,
+        tonalElevation = tonalElevation,
+        shape = shape,
+        colors = colors,
+        scale = scale,
+        border = border,
+        glow = glow,
+        interactionSource = interactionSource
+    )
+}
+
+/**
+ * Base composable for [ListItem]
+ *
+ * @param selected defines whether this ListItem is selected or not
+ * @param onClick called when this ListItem is clicked
+ * @param contentPadding [PaddingValues] defines the inner padding applied to the ListItem's content
+ * @param headlineContent the [Composable] headline content of the list item
+ * @param modifier [Modifier] to be applied to the list item
+ * @param enabled controls the enabled state of this list item. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param onLongClick called when this ListItem is long clicked (long-pressed).
+ * @param leadingContent the [Composable] leading content of the list item
+ * @param overlineContent the [Composable] content displayed above the headline content
+ * @param supportingContent the [Composable] content displayed below the headline content
+ * @param trailingContent the [Composable] trailing meta text, icon, switch or checkbox
+ * @param tonalElevation the tonal elevation of this list item
+ * @param shape [ListItemShape] defines the shape of ListItem's container in different interaction
+ * states.
+ * @param colors [ListItemColors] defines the background and content colors used in the list item
+ * for different interaction states.
+ * @param scale [ListItemScale] defines the size of the list item relative to its original size in
+ * different interaction states.
+ * @param border [ListItemBorder] defines a border around the list item in different interaction
+ * states.
+ * @param glow [ListItemGlow] defines a shadow to be shown behind the list item for different
+ * interaction states.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of
+ * [Interaction]s for this component. You can create and pass in your own [remember]ed instance
+ * to observe [Interaction]s and customize the appearance / behavior of this list item in different
+ * states.
+ */
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun BaseListItem(
+    selected: Boolean,
+    onClick: () -> Unit,
+    contentPadding: PaddingValues,
+    headlineContent: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    onLongClick: (() -> Unit)?,
+    overlineContent: (@Composable () -> Unit)?,
+    supportingContent: (@Composable () -> Unit)?,
+    leadingContent: (@Composable BoxScope.() -> Unit)?,
+    trailingContent: (@Composable () -> Unit)?,
+    tonalElevation: Dp,
+    shape: ListItemShape,
+    colors: ListItemColors,
+    scale: ListItemScale,
+    border: ListItemBorder,
+    glow: ListItemGlow,
+    interactionSource: MutableInteractionSource
+) {
+    val semanticModifier = Modifier
+        .semantics(mergeDescendants = true) {
+            this.selected = selected
+        }
+        .then(modifier)
+    Surface(
+        checked = selected,
+        onCheckedChange = { onClick.invoke() },
+        modifier = semanticModifier,
+        enabled = enabled,
+        onLongClick = onLongClick,
+        tonalElevation = tonalElevation,
+        shape = shape.toToggleableSurfaceShape(),
+        colors = colors.toToggleableSurfaceColors(),
+        scale = scale.toToggleableSurfaceScale(),
+        border = border.toToggleableSurfaceBorder(),
+        glow = glow.toToggleableSurfaceGlow(),
+        interactionSource = interactionSource
+    ) {
+        Row(
+            modifier = Modifier
+                .defaultMinSize(
+                    minHeight = listItemMinHeight(
+                        hasLeadingContent = leadingContent != null,
+                        hasSupportingContent = supportingContent != null,
+                        hasOverlineContent = overlineContent != null
+                    )
+                )
+                .padding(contentPadding),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            leadingContent?.let {
+                Box(
+                    modifier = Modifier
+                        .defaultMinSize(
+                            minWidth = ListItemDefaults.IconSize,
+                            minHeight = ListItemDefaults.IconSize
+                        )
+                        .graphicsLayer {
+                            alpha = ListItemDefaults.LeadingContentOpacity
+                        },
+                    contentAlignment = Alignment.Center,
+                    content = it
+                )
+                Spacer(
+                    modifier = Modifier.padding(end = ListItemDefaults.LeadingContentEndPadding)
+                )
+            }
+
+            Box(
+                Modifier
+                    .weight(1f)
+                    .align(Alignment.CenterVertically)
+            ) {
+                Column {
+                    overlineContent?.let {
+                        CompositionLocalProvider(
+                            LocalContentColor provides LocalContentColor.current.copy(
+                                alpha = ListItemDefaults.OverlineContentOpacity
+                            )
+                        ) {
+                            ProvideTextStyle(
+                                value = MaterialTheme.typography.labelSmall,
+                                content = it
+                            )
+                        }
+                    }
+
+                    ProvideTextStyle(
+                        value = MaterialTheme.typography.titleMedium,
+                        content = headlineContent
+                    )
+
+                    supportingContent?.let {
+                        CompositionLocalProvider(
+                            LocalContentColor provides LocalContentColor.current.copy(
+                                alpha = ListItemDefaults.SupportingContentOpacity
+                            )
+                        ) {
+                            ProvideTextStyle(
+                                value = MaterialTheme.typography.bodySmall,
+                                content = it
+                            )
+                        }
+                    }
+                }
+            }
+
+            trailingContent?.let {
+                Box(
+                    modifier = Modifier
+                        .padding(start = ListItemDefaults.TrailingContentStartPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides LocalContentColor.current
+                    ) {
+                        ProvideTextStyle(
+                            value = MaterialTheme.typography.labelLarge,
+                            content = it
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Calculates the minimum container height for [ListItem] based on whether the list items
+ * are One-Line, Two-Line or Three-Line list items.
+ *
+ * @param hasLeadingContent if the leading supporting visual of the list item exists.
+ * @param hasSupportingContent if the supporting text composable of the list item exists.
+ * @param hasOverlineContent if the text composable displayed above the headline text exists.
+ *
+ * @return The minimum container height for the given list item (to be used with
+ * [Modifier.defaultMinSize]).
+ */
+@OptIn(ExperimentalTvMaterial3Api::class)
+private fun listItemMinHeight(
+    hasLeadingContent: Boolean,
+    hasSupportingContent: Boolean,
+    hasOverlineContent: Boolean
+): Dp {
+    return when {
+        hasSupportingContent && hasOverlineContent -> ListItemDefaults.MinContainerHeightThreeLine
+
+        hasSupportingContent || hasOverlineContent -> ListItemDefaults.MinContainerHeightTwoLine
+
+        hasLeadingContent -> ListItemDefaults.MinContainerHeightLeadingContent
+
+        else -> ListItemDefaults.MinContainerHeight
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ListItemShape.toToggleableSurfaceShape() =
+    ToggleableSurfaceShape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        selectedShape = selectedShape,
+        disabledShape = disabledShape,
+        focusedSelectedShape = focusedSelectedShape,
+        focusedDisabledShape = focusedDisabledShape,
+        pressedSelectedShape = pressedSelectedShape,
+        selectedDisabledShape = disabledShape,
+        focusedSelectedDisabledShape = focusedDisabledShape
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ListItemColors.toToggleableSurfaceColors() =
+    ToggleableSurfaceColors(
+        containerColor = containerColor,
+        contentColor = contentColor,
+        focusedContainerColor = focusedContainerColor,
+        focusedContentColor = focusedContentColor,
+        pressedContainerColor = pressedContainerColor,
+        pressedContentColor = pressedContentColor,
+        selectedContainerColor = selectedContainerColor,
+        selectedContentColor = selectedContentColor,
+        disabledContainerColor = disabledContainerColor,
+        disabledContentColor = disabledContentColor,
+        focusedSelectedContainerColor = focusedSelectedContainerColor,
+        focusedSelectedContentColor = focusedSelectedContentColor,
+        pressedSelectedContainerColor = pressedSelectedContainerColor,
+        pressedSelectedContentColor = pressedSelectedContentColor
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ListItemScale.toToggleableSurfaceScale() =
+    ToggleableSurfaceScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        selectedScale = selectedScale,
+        disabledScale = disabledScale,
+        focusedSelectedScale = focusedSelectedScale,
+        focusedDisabledScale = focusedDisabledScale,
+        pressedSelectedScale = pressedSelectedScale,
+        selectedDisabledScale = disabledScale,
+        focusedSelectedDisabledScale = focusedDisabledScale
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ListItemBorder.toToggleableSurfaceBorder() =
+    ToggleableSurfaceBorder(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        selectedBorder = selectedBorder,
+        disabledBorder = disabledBorder,
+        focusedSelectedBorder = focusedSelectedBorder,
+        focusedDisabledBorder = focusedDisabledBorder,
+        pressedSelectedBorder = pressedSelectedBorder,
+        selectedDisabledBorder = disabledBorder,
+        focusedSelectedDisabledBorder = focusedDisabledBorder
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ListItemGlow.toToggleableSurfaceGlow() =
+    ToggleableSurfaceGlow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow,
+        selectedGlow = selectedGlow,
+        focusedSelectedGlow = focusedSelectedGlow,
+        pressedSelectedGlow = pressedSelectedGlow
+    )
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt
new file mode 100644
index 0000000..e05e944
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ListItemDefaults.kt
@@ -0,0 +1,273 @@
+/*
+ * 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.tv.material3
+
+import androidx.annotation.FloatRange
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.tokens.Elevation
+
+/**
+ * Contains the default values used by list items.
+ */
+@ExperimentalTvMaterial3Api
+object ListItemDefaults {
+    /**
+     * The default Icon size used by [ListItem].
+     */
+    val IconSize = 32.dp
+
+    /**
+     * The default elevation used by [ListItem].
+     */
+    val ListItemElevation = Elevation.Level0
+
+    /**
+     * The default content padding [PaddingValues] used by [ListItem]
+     */
+    internal val ContentPadding = PaddingValues(
+        horizontal = 16.dp,
+        vertical = 12.dp
+    )
+
+    internal const val LeadingContentOpacity = 0.8f
+    internal const val OverlineContentOpacity = 0.6f
+    internal const val SupportingContentOpacity = 0.8f
+
+    internal val LeadingContentEndPadding = 8.dp
+    internal val TrailingContentStartPadding = 8.dp
+
+    internal val MinContainerHeight = 48.dp
+    internal val MinContainerHeightLeadingContent = 56.dp
+    internal val MinContainerHeightTwoLine = 64.dp
+    internal val MinContainerHeightThreeLine = 80.dp
+
+    private val ListItemShape = RoundedCornerShape(8.dp)
+
+    private val DefaultBorder
+        @ReadOnlyComposable
+        @Composable get() = Border(
+            border = BorderStroke(
+                width = 2.dp,
+                color = MaterialTheme.colorScheme.border
+            ),
+            shape = ListItemShape
+        )
+
+    /**
+     * Creates a [ListItemShape] that represents the default container shapes used in a ListItem
+     *
+     * @param shape the default shape used when the ListItem is enabled
+     * @param focusedShape the shape used when the ListItem is enabled and focused
+     * @param pressedShape the shape used when the ListItem is enabled and pressed
+     * @param selectedShape the shape used when the ListItem is enabled and selected
+     * @param disabledShape the shape used when the ListItem is not enabled
+     * @param focusedSelectedShape the shape used when the ListItem is enabled, focused and
+     * selected
+     * @param focusedDisabledShape the shape used when the ListItem is not enabled and focused
+     * @param pressedSelectedShape the shape used when the ListItem is enabled, pressed and
+     * selected
+     */
+    fun shape(
+        shape: Shape = ListItemShape,
+        focusedShape: Shape = shape,
+        pressedShape: Shape = shape,
+        selectedShape: Shape = shape,
+        disabledShape: Shape = shape,
+        focusedSelectedShape: Shape = shape,
+        focusedDisabledShape: Shape = disabledShape,
+        pressedSelectedShape: Shape = shape
+    ) = ListItemShape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        selectedShape = selectedShape,
+        disabledShape = disabledShape,
+        focusedSelectedShape = focusedSelectedShape,
+        focusedDisabledShape = focusedDisabledShape,
+        pressedSelectedShape = pressedSelectedShape
+    )
+
+    /**
+     * Creates a [ListItemColors] that represents the default container & content colors used in a
+     * ListItem
+     *
+     * @param containerColor the default container color used when the ListItem is enabled
+     * @param contentColor the default content color used when the ListItem is enabled
+     * @param focusedContainerColor the container color used when the ListItem is enabled and
+     * focused
+     * @param focusedContentColor the content color used when the ListItem is enabled and focused
+     * @param pressedContainerColor the container color used when the ListItem is enabled and
+     * pressed
+     * @param pressedContentColor the content color used when the ListItem is enabled and pressed
+     * @param selectedContainerColor the container color used when the ListItem is enabled and
+     * selected
+     * @param selectedContentColor the content color used when the ListItem is enabled and selected
+     * @param disabledContainerColor the container color used when the ListItem is not enabled
+     * @param disabledContentColor the content color used when the ListItem is not enabled
+     * @param focusedSelectedContainerColor the container color used when the ListItem is enabled,
+     * focused and selected
+     * @param focusedSelectedContentColor the content color used when the ListItem is enabled,
+     * focused and selected
+     * @param pressedSelectedContainerColor the container color used when the ListItem is enabled,
+     * pressed and selected
+     * @param pressedSelectedContentColor the content color used when the ListItem is enabled,
+     * pressed and selected
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun colors(
+        containerColor: Color = Color.Transparent,
+        contentColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedContainerColor: Color = MaterialTheme.colorScheme.inverseSurface,
+        focusedContentColor: Color = contentColorFor(focusedContainerColor),
+        pressedContainerColor: Color = focusedContainerColor,
+        pressedContentColor: Color = contentColorFor(focusedContainerColor),
+        selectedContainerColor: Color = MaterialTheme.colorScheme.secondaryContainer
+            .copy(alpha = 0.4f),
+        selectedContentColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,
+        disabledContainerColor: Color = Color.Transparent,
+        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedSelectedContainerColor: Color = focusedContainerColor,
+        focusedSelectedContentColor: Color = focusedContentColor,
+        pressedSelectedContainerColor: Color = pressedContainerColor,
+        pressedSelectedContentColor: Color = pressedContentColor
+    ) = ListItemColors(
+        containerColor = containerColor,
+        contentColor = contentColor,
+        focusedContainerColor = focusedContainerColor,
+        focusedContentColor = focusedContentColor,
+        pressedContainerColor = pressedContainerColor,
+        pressedContentColor = pressedContentColor,
+        selectedContainerColor = selectedContainerColor,
+        selectedContentColor = selectedContentColor,
+        disabledContainerColor = disabledContainerColor,
+        disabledContentColor = disabledContentColor,
+        focusedSelectedContainerColor = focusedSelectedContainerColor,
+        focusedSelectedContentColor = focusedSelectedContentColor,
+        pressedSelectedContainerColor = pressedSelectedContainerColor,
+        pressedSelectedContentColor = pressedSelectedContentColor
+    )
+
+    /**
+     * Creates a [ListItemScale] that represents the default scales used in a ListItem.
+     * scales are used to modify the size of a composable in different [Interaction] states
+     * e.g. 1f (original) in default state, 1.2f (scaled up) in focused state, 0.8f (scaled down)
+     * in pressed state, etc.
+     *
+     * @param scale the scale used when the ListItem is enabled.
+     * @param focusedScale the scale used when the ListItem is enabled and focused.
+     * @param pressedScale the scale used when the ListItem is enabled and pressed.
+     * @param selectedScale the scale used when the ListItem is enabled and selected.
+     * @param disabledScale the scale used when the ListItem is not enabled.
+     * @param focusedSelectedScale the scale used when the ListItem is enabled, focused and
+     * selected.
+     * @param focusedDisabledScale the scale used when the ListItem is not enabled and
+     * focused.
+     * @param pressedSelectedScale the scale used when the ListItem is enabled, pressed and
+     * selected.
+     */
+    fun scale(
+        @FloatRange(from = 0.0) scale: Float = 1f,
+        @FloatRange(from = 0.0) focusedScale: Float = 1.05f,
+        @FloatRange(from = 0.0) pressedScale: Float = scale,
+        @FloatRange(from = 0.0) selectedScale: Float = scale,
+        @FloatRange(from = 0.0) disabledScale: Float = scale,
+        @FloatRange(from = 0.0) focusedSelectedScale: Float = focusedScale,
+        @FloatRange(from = 0.0) focusedDisabledScale: Float = disabledScale,
+        @FloatRange(from = 0.0) pressedSelectedScale: Float = scale
+    ) = ListItemScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        selectedScale = selectedScale,
+        disabledScale = disabledScale,
+        focusedSelectedScale = focusedSelectedScale,
+        focusedDisabledScale = focusedDisabledScale,
+        pressedSelectedScale = pressedSelectedScale
+    )
+
+    /**
+     * Creates a [ListItemBorder] that represents the default [Border]s applied on a ListItem in
+     * different [Interaction] states
+     *
+     * @param border the default [Border] used when the ListItem is enabled
+     * @param focusedBorder the [Border] used when the ListItem is enabled and focused
+     * @param pressedBorder the [Border] used when the ListItem is enabled and pressed
+     * @param selectedBorder the [Border] used when the ListItem is enabled and selected
+     * @param disabledBorder the [Border] used when the ListItem is not enabled
+     * @param focusedSelectedBorder the [Border] used when the ListItem is enabled, focused and
+     * selected
+     * @param focusedDisabledBorder the [Border] used when the ListItem is not enabled and focused
+     * @param pressedSelectedBorder the [Border] used when the ListItem is enabled, pressed and
+     * selected
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun border(
+        border: Border = Border.None,
+        focusedBorder: Border = border,
+        pressedBorder: Border = focusedBorder,
+        selectedBorder: Border = border,
+        disabledBorder: Border = border,
+        focusedSelectedBorder: Border = focusedBorder,
+        focusedDisabledBorder: Border = DefaultBorder,
+        pressedSelectedBorder: Border = border
+    ) = ListItemBorder(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        selectedBorder = selectedBorder,
+        disabledBorder = disabledBorder,
+        focusedSelectedBorder = focusedSelectedBorder,
+        focusedDisabledBorder = focusedDisabledBorder,
+        pressedSelectedBorder = pressedSelectedBorder
+    )
+
+    /**
+     * Creates a [ListItemGlow] that represents the default [Glow]s used in a ListItem.
+     *
+     * @param glow the [Glow] used when the ListItem is enabled, and has no other [Interaction]s.
+     * @param focusedGlow the [Glow] used when the ListItem is enabled and focused.
+     * @param pressedGlow the [Glow] used when the ListItem is enabled and pressed.
+     * @param selectedGlow the [Glow] used when the ListItem is enabled and selected.
+     * @param focusedSelectedGlow the [Glow] used when the ListItem is enabled, focused and selected.
+     * @param pressedSelectedGlow the [Glow] used when the ListItem is enabled, pressed and selected.
+     */
+    fun glow(
+        glow: Glow = Glow.None,
+        focusedGlow: Glow = glow,
+        pressedGlow: Glow = glow,
+        selectedGlow: Glow = glow,
+        focusedSelectedGlow: Glow = focusedGlow,
+        pressedSelectedGlow: Glow = glow
+    ) = ListItemGlow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow,
+        selectedGlow = selectedGlow,
+        focusedSelectedGlow = focusedSelectedGlow,
+        pressedSelectedGlow = pressedSelectedGlow
+    )
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ListItemStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ListItemStyles.kt
new file mode 100644
index 0000000..03ae04a
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ListItemStyles.kt
@@ -0,0 +1,345 @@
+/*
+ * 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.tv.material3
+
+import androidx.annotation.FloatRange
+import androidx.compose.foundation.Indication
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+
+/**
+ * Defines [Shape] for all TV [Indication] states of a ListItem.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ListItemShape internal constructor(
+    internal val shape: Shape,
+    internal val focusedShape: Shape,
+    internal val pressedShape: Shape,
+    internal val selectedShape: Shape,
+    internal val disabledShape: Shape,
+    internal val focusedSelectedShape: Shape,
+    internal val focusedDisabledShape: Shape,
+    internal val pressedSelectedShape: Shape
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ListItemShape
+
+        if (shape != other.shape) return false
+        if (focusedShape != other.focusedShape) return false
+        if (pressedShape != other.pressedShape) return false
+        if (selectedShape != other.selectedShape) return false
+        if (disabledShape != other.disabledShape) return false
+        if (focusedSelectedShape != other.focusedSelectedShape) return false
+        if (focusedDisabledShape != other.focusedDisabledShape) return false
+        if (pressedSelectedShape != other.pressedSelectedShape) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = shape.hashCode()
+        result = 31 * result + focusedShape.hashCode()
+        result = 31 * result + pressedShape.hashCode()
+        result = 31 * result + selectedShape.hashCode()
+        result = 31 * result + disabledShape.hashCode()
+        result = 31 * result + focusedSelectedShape.hashCode()
+        result = 31 * result + focusedDisabledShape.hashCode()
+        result = 31 * result + pressedSelectedShape.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ListItemShape(shape=$shape, " +
+            "focusedShape=$focusedShape," +
+            "pressedShape=$pressedShape, " +
+            "selectedShape=$selectedShape, " +
+            "disabledShape=$disabledShape, " +
+            "focusedSelectedShape=$focusedSelectedShape, " +
+            "focusedDisabledShape=$focusedDisabledShape, " +
+            "pressedSelectedShape=$pressedSelectedShape)"
+    }
+}
+
+/**
+ * Defines container & content color [Color] for all TV [Indication] states of a ListItem.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ListItemColors internal constructor(
+    internal val containerColor: Color,
+    internal val contentColor: Color,
+    internal val focusedContainerColor: Color,
+    internal val focusedContentColor: Color,
+    internal val pressedContainerColor: Color,
+    internal val pressedContentColor: Color,
+    internal val selectedContainerColor: Color,
+    internal val selectedContentColor: Color,
+    internal val disabledContainerColor: Color,
+    internal val disabledContentColor: Color,
+    internal val focusedSelectedContainerColor: Color,
+    internal val focusedSelectedContentColor: Color,
+    internal val pressedSelectedContainerColor: Color,
+    internal val pressedSelectedContentColor: Color
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ListItemColors
+
+        if (containerColor != other.containerColor) return false
+        if (contentColor != other.contentColor) return false
+        if (focusedContainerColor != other.focusedContainerColor) return false
+        if (focusedContentColor != other.focusedContentColor) return false
+        if (pressedContainerColor != other.pressedContainerColor) return false
+        if (pressedContentColor != other.pressedContentColor) return false
+        if (selectedContainerColor != other.selectedContainerColor) return false
+        if (selectedContentColor != other.selectedContentColor) return false
+        if (disabledContainerColor != other.disabledContainerColor) return false
+        if (disabledContentColor != other.disabledContentColor) return false
+        if (focusedSelectedContainerColor != other.focusedSelectedContainerColor) return false
+        if (focusedSelectedContentColor != other.focusedSelectedContentColor) return false
+        if (pressedSelectedContainerColor != other.pressedSelectedContainerColor) return false
+        if (pressedSelectedContentColor != other.pressedSelectedContentColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = containerColor.hashCode()
+        result = 31 * result + contentColor.hashCode()
+        result = 31 * result + focusedContainerColor.hashCode()
+        result = 31 * result + focusedContentColor.hashCode()
+        result = 31 * result + pressedContainerColor.hashCode()
+        result = 31 * result + pressedContentColor.hashCode()
+        result = 31 * result + selectedContainerColor.hashCode()
+        result = 31 * result + selectedContentColor.hashCode()
+        result = 31 * result + disabledContainerColor.hashCode()
+        result = 31 * result + disabledContentColor.hashCode()
+        result = 31 * result + focusedSelectedContainerColor.hashCode()
+        result = 31 * result + focusedSelectedContentColor.hashCode()
+        result = 31 * result + pressedSelectedContainerColor.hashCode()
+        result = 31 * result + pressedSelectedContentColor.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ListItemColors(containerColor=$containerColor, " +
+            "contentColor=$contentColor, " +
+            "focusedContainerColor=$focusedContainerColor, " +
+            "focusedContentColor=$focusedContentColor, " +
+            "pressedContainerColor=$pressedContainerColor, " +
+            "pressedContentColor=$pressedContentColor, " +
+            "selectedContainerColor=$selectedContainerColor, " +
+            "selectedContentColor=$selectedContentColor, " +
+            "disabledContainerColor=$disabledContainerColor, " +
+            "disabledContentColor=$disabledContentColor, " +
+            "focusedSelectedContainerColor=$focusedSelectedContainerColor, " +
+            "focusedSelectedContentColor=$focusedSelectedContentColor, " +
+            "pressedSelectedContainerColor=$pressedSelectedContainerColor, " +
+            "pressedSelectedContentColor=$pressedSelectedContentColor)"
+    }
+}
+
+/**
+ * Defines the scale for all TV [Indication] states of a ListItem.
+ * Note: This scale must always be a non-negative float.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ListItemScale internal constructor(
+    @FloatRange(from = 0.0) internal val scale: Float,
+    @FloatRange(from = 0.0) internal val focusedScale: Float,
+    @FloatRange(from = 0.0) internal val pressedScale: Float,
+    @FloatRange(from = 0.0) internal val selectedScale: Float,
+    @FloatRange(from = 0.0) internal val disabledScale: Float,
+    @FloatRange(from = 0.0) internal val focusedSelectedScale: Float,
+    @FloatRange(from = 0.0) internal val focusedDisabledScale: Float,
+    @FloatRange(from = 0.0) internal val pressedSelectedScale: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ListItemScale
+
+        if (scale != other.scale) return false
+        if (focusedScale != other.focusedScale) return false
+        if (pressedScale != other.pressedScale) return false
+        if (selectedScale != other.selectedScale) return false
+        if (disabledScale != other.disabledScale) return false
+        if (focusedSelectedScale != other.focusedSelectedScale) return false
+        if (focusedDisabledScale != other.focusedDisabledScale) return false
+        if (pressedSelectedScale != other.pressedSelectedScale) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = scale.hashCode()
+        result = 31 * result + focusedScale.hashCode()
+        result = 31 * result + pressedScale.hashCode()
+        result = 31 * result + selectedScale.hashCode()
+        result = 31 * result + disabledScale.hashCode()
+        result = 31 * result + focusedSelectedScale.hashCode()
+        result = 31 * result + focusedDisabledScale.hashCode()
+        result = 31 * result + pressedSelectedScale.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ListItemScale(scale=$scale, " +
+            "focusedScale=$focusedScale, " +
+            "pressedScale=$pressedScale, " +
+            "selectedScale=$selectedScale, " +
+            "disabledScale=$disabledScale, " +
+            "focusedSelectedScale=$focusedSelectedScale, " +
+            "focusedDisabledScale=$focusedDisabledScale, " +
+            "pressedSelectedScale=$pressedSelectedScale)"
+    }
+
+    companion object {
+        /**
+         * Signifies the absence of a [ScaleIndication] in ListItem component.
+         */
+        val None = ListItemScale(
+            scale = 1f,
+            focusedScale = 1f,
+            pressedScale = 1f,
+            selectedScale = 1f,
+            disabledScale = 1f,
+            focusedSelectedScale = 1f,
+            focusedDisabledScale = 1f,
+            pressedSelectedScale = 1f
+        )
+    }
+}
+
+/**
+ * Defines [Border] for all TV [Indication] states of a ListItem.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ListItemBorder internal constructor(
+    internal val border: Border,
+    internal val focusedBorder: Border,
+    internal val pressedBorder: Border,
+    internal val selectedBorder: Border,
+    internal val disabledBorder: Border,
+    internal val focusedSelectedBorder: Border,
+    internal val focusedDisabledBorder: Border,
+    internal val pressedSelectedBorder: Border
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ListItemBorder
+
+        if (border != other.border) return false
+        if (focusedBorder != other.focusedBorder) return false
+        if (pressedBorder != other.pressedBorder) return false
+        if (selectedBorder != other.selectedBorder) return false
+        if (disabledBorder != other.disabledBorder) return false
+        if (focusedSelectedBorder != other.focusedSelectedBorder) return false
+        if (focusedDisabledBorder != other.focusedDisabledBorder) return false
+        if (pressedSelectedBorder != other.pressedSelectedBorder) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = border.hashCode()
+        result = 31 * result + focusedBorder.hashCode()
+        result = 31 * result + pressedBorder.hashCode()
+        result = 31 * result + selectedBorder.hashCode()
+        result = 31 * result + disabledBorder.hashCode()
+        result = 31 * result + focusedSelectedBorder.hashCode()
+        result = 31 * result + focusedDisabledBorder.hashCode()
+        result = 31 * result + pressedSelectedBorder.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ListItemBorder(border=$border, " +
+            "focusedBorder=$focusedBorder, " +
+            "pressedBorder=$pressedBorder, " +
+            "selectedBorder=$selectedBorder, " +
+            "disabledBorder=$disabledBorder, " +
+            "focusedSelectedBorder=$focusedSelectedBorder, " +
+            "focusedDisabledBorder=$focusedDisabledBorder, " +
+            "pressedSelectedBorder=$pressedSelectedBorder)"
+    }
+}
+
+/**
+ * Defines [Glow] for all TV [Indication] states of a ListItem.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ListItemGlow internal constructor(
+    internal val glow: Glow,
+    internal val focusedGlow: Glow,
+    internal val pressedGlow: Glow,
+    internal val selectedGlow: Glow,
+    internal val focusedSelectedGlow: Glow,
+    internal val pressedSelectedGlow: Glow
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ListItemGlow
+
+        if (glow != other.glow) return false
+        if (focusedGlow != other.focusedGlow) return false
+        if (pressedGlow != other.pressedGlow) return false
+        if (selectedGlow != other.selectedGlow) return false
+        if (focusedSelectedGlow != other.focusedSelectedGlow) return false
+        if (pressedSelectedGlow != other.pressedSelectedGlow) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = glow.hashCode()
+        result = 31 * result + focusedGlow.hashCode()
+        result = 31 * result + pressedGlow.hashCode()
+        result = 31 * result + selectedGlow.hashCode()
+        result = 31 * result + focusedSelectedGlow.hashCode()
+        result = 31 * result + pressedSelectedGlow.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ListItemGlow(glow=$glow, " +
+            "focusedGlow=$focusedGlow, " +
+            "pressedGlow=$pressedGlow, " +
+            "selectedGlow=$selectedGlow, " +
+            "focusedSelectedGlow=$focusedSelectedGlow, " +
+            "pressedSelectedGlow=$pressedSelectedGlow)"
+    }
+}
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TextTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TextTest.kt
index e96362a..59ece90 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TextTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TextTest.kt
@@ -74,11 +74,11 @@
 
         assertThat(
             localTextStyle?.platformStyle?.paragraphStyle?.includeFontPadding
-        ).isEqualTo(true)
+        ).isEqualTo(false)
 
         assertThat(
             display1TextStyle?.platformStyle?.paragraphStyle?.includeFontPadding
-        ).isEqualTo(true)
+        ).isEqualTo(false)
     }
 
     @Test
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Typography.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Typography.kt
index 420b834..5bbfa4c 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Typography.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Typography.kt
@@ -257,7 +257,7 @@
     return if (fontFamily != null) this else copy(fontFamily = default)
 }
 
-private const val DefaultIncludeFontPadding = true
+private const val DefaultIncludeFontPadding = false
 
 internal val DefaultTextStyle = TextStyle.Default.copy(
     platformStyle = PlatformTextStyle(
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.kt
new file mode 100644
index 0000000..e1bcf92
--- /dev/null
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconButtonScreenshotTest.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.
+ */
+
+package androidx.wear.compose.material3.test
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Home
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.material3.FilledIconButton
+import androidx.wear.compose.material3.FilledTonalIconButton
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.IconButton
+import androidx.wear.compose.material3.IconButtonDefaults
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.OutlinedIconButton
+import androidx.wear.compose.material3.SCREENSHOT_GOLDEN_PATH
+import androidx.wear.compose.material3.TEST_TAG
+import androidx.wear.compose.material3.setContentWithTheme
+import androidx.wear.compose.material3.touchTargetAwareSize
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class IconButtonScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule
+    val testName = TestName()
+
+    @Test
+    fun filled_icon_button_enabled() = verifyScreenshot {
+        sampleFilledIconButton(enabled = true, isCompact = false)
+    }
+
+    @Test
+    fun filled_icon_button_disabled() = verifyScreenshot("icon_button_disabled") {
+        sampleFilledIconButton(enabled = false, isCompact = false)
+    }
+
+    @Test
+    fun filled_tonal_icon_button_enabled() = verifyScreenshot {
+        sampleFilledTonalIconButton(enabled = true, isCompact = false)
+    }
+
+    @Test
+    fun filled_tonal_icon_button_disabled() = verifyScreenshot("icon_button_disabled") {
+        sampleFilledTonalIconButton(enabled = false, isCompact = false)
+    }
+
+    @Test
+    fun outlined_icon_button_enabled() = verifyScreenshot {
+        sampleOutlinedIconButton(enabled = true, isCompact = false)
+    }
+
+    @Test
+    fun outlined_icon_button_disabled() = verifyScreenshot {
+        sampleOutlinedIconButton(enabled = false, isCompact = false)
+    }
+
+    @Test
+    fun icon_button_enabled() = verifyScreenshot {
+        sampleIconButton(enabled = true, isCompact = false)
+    }
+
+    @Test
+    fun icon_button_disabled() = verifyScreenshot {
+        sampleIconButton(enabled = false, isCompact = false)
+    }
+
+    @Test
+    fun filled_compact_icon_button_enabled() = verifyScreenshot {
+        sampleFilledIconButton(enabled = true, isCompact = true)
+    }
+
+    @Test
+    fun filled_compact_icon_button_disabled() = verifyScreenshot("compact_icon_button_disabled") {
+        sampleFilledIconButton(enabled = false, isCompact = true)
+    }
+
+    @Test
+    fun filled_tonal_compact_icon_button_enabled() = verifyScreenshot {
+        sampleFilledTonalIconButton(enabled = true, isCompact = true)
+    }
+
+    @Test
+    fun filled_tonal_compact_icon_button_disabled() =
+        verifyScreenshot("compact_icon_button_disabled") {
+            sampleFilledTonalIconButton(enabled = false, isCompact = true)
+        }
+
+    @Test
+    fun outlined_compact_icon_button_enabled() = verifyScreenshot {
+        sampleOutlinedIconButton(enabled = true, isCompact = true)
+    }
+
+    @Test
+    fun outlined_compact_icon_button_disabled() = verifyScreenshot {
+        sampleOutlinedIconButton(enabled = false, isCompact = true)
+    }
+
+    @Test
+    fun compact_icon_button_enabled() = verifyScreenshot {
+        sampleIconButton(enabled = true, isCompact = true)
+    }
+
+    @Test
+    fun compact_icon_button_disabled() = verifyScreenshot {
+        sampleIconButton(enabled = false, isCompact = true)
+    }
+
+    @Composable
+    private fun sampleFilledIconButton(enabled: Boolean, isCompact: Boolean) {
+        FilledIconButton(
+            onClick = {}, enabled = enabled, modifier = Modifier
+                .testTag(TEST_TAG)
+                .then(
+                    if (isCompact)
+                        Modifier.touchTargetAwareSize(IconButtonDefaults.ExtraSmallButtonSize)
+                    else Modifier
+                )
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Home,
+                contentDescription = "Home",
+                modifier = if (isCompact) Modifier.size(
+                    IconButtonDefaults.iconSizeFor(IconButtonDefaults.SmallIconSize)
+                )
+                else Modifier
+            )
+        }
+    }
+
+    @Composable
+    private fun sampleFilledTonalIconButton(enabled: Boolean, isCompact: Boolean) {
+        FilledTonalIconButton(
+            onClick = {}, enabled = enabled, modifier = Modifier
+                .testTag(TEST_TAG)
+                .then(
+                    if (isCompact)
+                        Modifier.touchTargetAwareSize(IconButtonDefaults.ExtraSmallButtonSize)
+                    else Modifier
+                )
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Home,
+                contentDescription = "Home",
+                modifier = if (isCompact) Modifier.size(
+                    IconButtonDefaults.iconSizeFor(IconButtonDefaults.SmallIconSize)
+                )
+                else Modifier
+            )
+        }
+    }
+
+    @Composable
+    private fun sampleOutlinedIconButton(enabled: Boolean, isCompact: Boolean) {
+        OutlinedIconButton(
+            onClick = {}, enabled = enabled, modifier = Modifier
+                .testTag(TEST_TAG)
+                .then(
+                    if (isCompact)
+                        Modifier.touchTargetAwareSize(IconButtonDefaults.ExtraSmallButtonSize)
+                    else Modifier
+                )
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Home,
+                contentDescription = "Home",
+                modifier = if (isCompact) Modifier.size(
+                    IconButtonDefaults.iconSizeFor(IconButtonDefaults.SmallIconSize)
+                )
+                else Modifier
+            )
+        }
+    }
+
+    @Composable
+    private fun sampleIconButton(enabled: Boolean, isCompact: Boolean) {
+        IconButton(
+            onClick = {}, enabled = enabled, modifier = Modifier
+                .testTag(TEST_TAG)
+                .then(
+                    if (isCompact)
+                        Modifier.touchTargetAwareSize(IconButtonDefaults.ExtraSmallButtonSize)
+                    else Modifier
+                )
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Home,
+                contentDescription = "Home",
+                modifier = if (isCompact) Modifier.size(
+                    IconButtonDefaults.iconSizeFor(IconButtonDefaults.SmallIconSize)
+                )
+                else Modifier
+            )
+        }
+    }
+
+    private fun verifyScreenshot(
+        methodName: String = testName.methodName,
+        content: @Composable () -> Unit
+    ) {
+        rule.setContentWithTheme {
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.background)
+            ) {
+                content()
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).captureToImage()
+            .assertAgainstGolden(screenshotRule, methodName)
+    }
+}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextTest.kt
index 7287e5a..db9a5398 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/TextTest.kt
@@ -73,11 +73,11 @@
 
         assertThat(
             localTextStyle?.platformStyle?.paragraphStyle?.includeFontPadding
-        ).isEqualTo(true)
+        ).isEqualTo(false)
 
         assertThat(
             displayMediumTextStyle?.platformStyle?.paragraphStyle?.includeFontPadding
-        ).isEqualTo(true)
+        ).isEqualTo(false)
     }
 
     @Test
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Typography.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Typography.kt
index 2197873..b872577 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Typography.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Typography.kt
@@ -293,7 +293,7 @@
     return if (fontFamily != null) this else copy(fontFamily = default)
 }
 
-private const val DefaultIncludeFontPadding = true
+private const val DefaultIncludeFontPadding = false
 
 /**
  * Returns theme default [TextStyle] with default [PlatformTextStyle].
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
index 36b74a5..8c6d0e6 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
@@ -82,7 +82,7 @@
 
     @Test
     fun profile() {
-        baselineRule.collectBaselineProfile(
+        baselineRule.collect(
             packageName = PACKAGE_NAME,
             profileBlock = {
                 val intent = Intent()
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java
index 869004e..53a7daf 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java
@@ -386,11 +386,16 @@
                 // Skip content transition animations.
                 mChangedNodes.clear();
             }
-            parentView.post(
-                    () -> {
-                        mPipeline.initNewLayout();
-                        playEnterAnimations(parentView, isReattaching);
-                    });
+
+            Runnable runnable = () -> {
+                mPipeline.initNewLayout();
+                playEnterAnimations(parentView, isReattaching);
+            };
+            if (parentView.isInEditMode()) {
+                runnable.run();
+            } else {
+                parentView.post(runnable);
+            }
         }
 
         @UiThread
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index fff3924..79e0234 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -806,8 +806,7 @@
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
             public Builder setItalic(boolean italic) {
-                mImpl.setItalic(TypesProto.BoolProp.newBuilder().setValue(italic));
-                return this;
+                return setItalic(new BoolProp.Builder().setValue(italic).build());
             }
             /**
              * Sets whether the text should be rendered with an underline. If not specified,
@@ -829,8 +828,7 @@
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
             public Builder setUnderline(boolean underline) {
-                mImpl.setUnderline(TypesProto.BoolProp.newBuilder().setValue(underline));
-                return this;
+                return setUnderline(new BoolProp.Builder().setValue(underline).build());
             }
 
             /**
@@ -873,11 +871,7 @@
              */
             @NonNull
             public Builder setWeight(@FontWeight int weight) {
-                mImpl.setWeight(
-                        LayoutElementProto.FontWeightProp.newBuilder()
-                                .setValue(LayoutElementProto.FontWeight.forNumber(weight)));
-                mFingerprint.recordPropertyUpdate(5, weight);
-                return this;
+                return setWeight(new FontWeightProp.Builder().setValue(weight).build());
             }
 
             /**
@@ -1479,8 +1473,7 @@
              */
             @NonNull
             public Builder setText(@NonNull String text) {
-                mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
-                return this;
+                return setText(new StringProp.Builder(text).build());
             }
 
             /**
@@ -1529,8 +1522,7 @@
              */
             @NonNull
             public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
-                mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
-                return this;
+                return setMaxLines(new Int32Prop.Builder().setValue(maxLines).build());
             }
 
             /**
@@ -1561,12 +1553,8 @@
              */
             @NonNull
             public Builder setMultilineAlignment(@TextAlignment int multilineAlignment) {
-                mImpl.setMultilineAlignment(
-                        AlignmentProto.TextAlignmentProp.newBuilder()
-                                .setValue(
-                                        AlignmentProto.TextAlignment.forNumber(
-                                                multilineAlignment)));
-                return this;
+                return setMultilineAlignment(
+                        new TextAlignmentProp.Builder().setValue(multilineAlignment).build());
             }
 
             /**
@@ -1597,10 +1585,7 @@
              */
             @NonNull
             public Builder setOverflow(@TextOverflow int overflow) {
-                mImpl.setOverflow(
-                        LayoutElementProto.TextOverflowProp.newBuilder()
-                                .setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
-                return this;
+                return setOverflow(new TextOverflowProp.Builder().setValue(overflow).build());
             }
 
             /**
@@ -2048,8 +2033,7 @@
              */
             @NonNull
             public Builder setResourceId(@NonNull String resourceId) {
-                mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
-                return this;
+                return setResourceId(new StringProp.Builder(resourceId).build());
             }
 
             /**
@@ -2100,13 +2084,8 @@
              */
             @NonNull
             public Builder setContentScaleMode(@ContentScaleMode int contentScaleMode) {
-                mImpl.setContentScaleMode(
-                        LayoutElementProto.ContentScaleModeProp.newBuilder()
-                                .setValue(
-                                        LayoutElementProto.ContentScaleMode.forNumber(
-                                                contentScaleMode)));
-                mFingerprint.recordPropertyUpdate(4, contentScaleMode);
-                return this;
+                return setContentScaleMode(
+                       new ContentScaleModeProp.Builder().setValue(contentScaleMode).build());
             }
 
             /**
@@ -2640,13 +2619,9 @@
              */
             @NonNull
             public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
-                mImpl.setHorizontalAlignment(
-                        AlignmentProto.HorizontalAlignmentProp.newBuilder()
-                                .setValue(
-                                        AlignmentProto.HorizontalAlignment.forNumber(
-                                                horizontalAlignment)));
-                mFingerprint.recordPropertyUpdate(4, horizontalAlignment);
-                return this;
+                return setHorizontalAlignment(
+                        new HorizontalAlignmentProp.Builder()
+                                .setValue(horizontalAlignment).build());
             }
 
             /**
@@ -2671,13 +2646,8 @@
              */
             @NonNull
             public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
-                mImpl.setVerticalAlignment(
-                        AlignmentProto.VerticalAlignmentProp.newBuilder()
-                                .setValue(
-                                        AlignmentProto.VerticalAlignment.forNumber(
-                                                verticalAlignment)));
-                mFingerprint.recordPropertyUpdate(5, verticalAlignment);
-                return this;
+                return setVerticalAlignment(
+                        new VerticalAlignmentProp.Builder().setValue(verticalAlignment).build());
             }
 
             /**
@@ -2859,8 +2829,7 @@
              */
             @NonNull
             public Builder setText(@NonNull String text) {
-                mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
-                return this;
+                return setText(new StringProp.Builder(text).build());
             }
 
             /**
@@ -3085,8 +3054,7 @@
              */
             @NonNull
             public Builder setResourceId(@NonNull String resourceId) {
-                mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
-                return this;
+                return setResourceId(new StringProp.Builder(resourceId).build());
             }
 
             /**
@@ -3160,13 +3128,8 @@
              */
             @NonNull
             public Builder setAlignment(@SpanVerticalAlignment int alignment) {
-                mImpl.setAlignment(
-                        LayoutElementProto.SpanVerticalAlignmentProp.newBuilder()
-                                .setValue(
-                                        LayoutElementProto.SpanVerticalAlignment.forNumber(
-                                                alignment)));
-                mFingerprint.recordPropertyUpdate(5, alignment);
-                return this;
+                return setAlignment(
+                        new SpanVerticalAlignmentProp.Builder().setValue(alignment).build());
             }
 
             @Override
@@ -3457,8 +3420,7 @@
              */
             @NonNull
             public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
-                mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
-                return this;
+                return setMaxLines(new Int32Prop.Builder().setValue(maxLines).build());
             }
 
             /**
@@ -3490,13 +3452,8 @@
              */
             @NonNull
             public Builder setMultilineAlignment(@HorizontalAlignment int multilineAlignment) {
-                mImpl.setMultilineAlignment(
-                        AlignmentProto.HorizontalAlignmentProp.newBuilder()
-                                .setValue(
-                                        AlignmentProto.HorizontalAlignment.forNumber(
-                                                multilineAlignment)));
-                mFingerprint.recordPropertyUpdate(4, multilineAlignment);
-                return this;
+                return setMultilineAlignment(
+                        new HorizontalAlignmentProp.Builder().setValue(multilineAlignment).build());
             }
 
             /**
@@ -3527,11 +3484,7 @@
              */
             @NonNull
             public Builder setOverflow(@TextOverflow int overflow) {
-                mImpl.setOverflow(
-                        LayoutElementProto.TextOverflowProp.newBuilder()
-                                .setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
-                mFingerprint.recordPropertyUpdate(5, overflow);
-                return this;
+                return setOverflow(new TextOverflowProp.Builder().setValue(overflow).build());
             }
 
             /**
@@ -3765,13 +3718,9 @@
              */
             @NonNull
             public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
-                mImpl.setHorizontalAlignment(
-                        AlignmentProto.HorizontalAlignmentProp.newBuilder()
-                                .setValue(
-                                        AlignmentProto.HorizontalAlignment.forNumber(
-                                                horizontalAlignment)));
-                mFingerprint.recordPropertyUpdate(2, horizontalAlignment);
-                return this;
+                return setHorizontalAlignment(
+                        new HorizontalAlignmentProp.Builder()
+                                .setValue(horizontalAlignment).build());
             }
 
             /**
@@ -4314,11 +4263,7 @@
              */
             @NonNull
             public Builder setAnchorType(@ArcAnchorType int anchorType) {
-                mImpl.setAnchorType(
-                        AlignmentProto.ArcAnchorTypeProp.newBuilder()
-                                .setValue(AlignmentProto.ArcAnchorType.forNumber(anchorType)));
-                mFingerprint.recordPropertyUpdate(3, anchorType);
-                return this;
+                return setAnchorType(new ArcAnchorTypeProp.Builder().setValue(anchorType).build());
             }
 
             /**
@@ -4347,12 +4292,8 @@
              */
             @NonNull
             public Builder setVerticalAlign(@VerticalAlignment int verticalAlign) {
-                mImpl.setVerticalAlign(
-                        AlignmentProto.VerticalAlignmentProp.newBuilder()
-                                .setValue(
-                                        AlignmentProto.VerticalAlignment.forNumber(verticalAlign)));
-                mFingerprint.recordPropertyUpdate(4, verticalAlign);
-                return this;
+                return setVerticalAlign(
+                        new VerticalAlignmentProp.Builder().setValue(verticalAlign).build());
             }
 
             /**
@@ -4515,8 +4456,7 @@
             /** Sets the text to render. */
             @NonNull
             public Builder setText(@NonNull String text) {
-                mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
-                return this;
+                return setText(new StringProp.Builder(text).build());
             }
 
             /**
@@ -4819,11 +4759,7 @@
              */
             @NonNull
             public Builder setStrokeCap(@StrokeCap int strokeCap) {
-                mImpl.setStrokeCap(
-                        LayoutElementProto.StrokeCapProp.newBuilder()
-                                .setValue(LayoutElementProto.StrokeCap.forNumber(strokeCap)));
-                mFingerprint.recordPropertyUpdate(6, strokeCap);
-                return this;
+                return setStrokeCap(new StrokeCapProp.Builder().setValue(strokeCap).build());
             }
 
             @Override
@@ -5236,8 +5172,7 @@
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
             public Builder setRotateContents(boolean rotateContents) {
-                mImpl.setRotateContents(TypesProto.BoolProp.newBuilder().setValue(rotateContents));
-                return this;
+                return setRotateContents(new BoolProp.Builder().setValue(rotateContents).build());
             }
 
             @Override
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
index 5493def..13ea322 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
@@ -36,7 +36,6 @@
 import androidx.wear.protolayout.expression.Fingerprint;
 import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.proto.ModifiersProto;
-import androidx.wear.protolayout.proto.TypesProto;
 import androidx.wear.protolayout.protobuf.ByteString;
 
 import java.lang.annotation.Retention;
@@ -585,11 +584,7 @@
             @SuppressWarnings(
                     "deprecation") // Updating a deprecated field for backward compatibility
             public Builder setContentDescription(@NonNull String contentDescription) {
-                mImpl.setObsoleteContentDescription(contentDescription);
-                mImpl.mergeContentDescription(
-                        TypesProto.StringProp.newBuilder().setValue(contentDescription).build());
-                mFingerprint.recordPropertyUpdate(4, contentDescription.hashCode());
-                return this;
+                return setContentDescription(new StringProp.Builder(contentDescription).build());
             }
 
             /**
@@ -864,9 +859,7 @@
             @SuppressLint("MissingGetterMatchingBuilder")
             @NonNull
             public Builder setRtlAware(boolean rtlAware) {
-                mImpl.setRtlAware(TypesProto.BoolProp.newBuilder().setValue(rtlAware));
-                mFingerprint.recordPropertyUpdate(5, Boolean.hashCode(rtlAware));
-                return this;
+                return setRtlAware(new BoolProp.Builder().setValue(rtlAware).build());
             }
 
             /**
diff --git a/webkit/README.md b/webkit/README.md
index dedcbe1..7b564c0 100644
--- a/webkit/README.md
+++ b/webkit/README.md
@@ -24,16 +24,23 @@
 ```sh
 cd frameworks/support/
 # Build the library/compile changes
-./gradlew :webkit:assembleDebug
+./gradlew :webkit:webkit:assembleDebug
 # Run integration tests with the WebView installed on the device
-./gradlew :webkit:connectedAndroidTest
+./gradlew :webkit:integration-tests:instrumentation:connectedAndroidTest
 # Update API files (only necessary if you changed public APIs)
-./gradlew :webkit:updateApi
+./gradlew :webkit:webkit:updateApi
 ```
 
 For more a detailed developer guide, Googlers should read
 http://go/wvsl-contribute.
 
+## Instrumentation tests
+The instrumentation tests for `androidx.webkit` are located in the
+`:webkit:integration-tests:instrumentation` project. The tests have been split out into a separate
+project to facilitate testing against different targetSdk versions.
+
+Any new tests should be added to that project. To run the test, use the command above.
+
 ## API demo code
 
 We also maintain a demo app ([demo
diff --git a/webkit/integration-tests/instrumentation/README.md b/webkit/integration-tests/instrumentation/README.md
new file mode 100644
index 0000000..fc40d86
--- /dev/null
+++ b/webkit/integration-tests/instrumentation/README.md
@@ -0,0 +1,27 @@
+# Webkit library instrumentation tests
+
+This project contains the instrumentation tests for the [webkit](/webkit/webkit) library.
+
+The tests are located in a separate module to allow the use of multiple
+[product flavors](https://developer.android.com/build/build-variants#product-flavors)
+to build and run the tests against different targetSdk versions.
+
+This is necessary in order to test dark mode functionality, which changes depending on `targetSdk`
+between `32` and `33`.
+
+
+## Source sets
+Tests that do not depend on a particular `targetSdk` version should be added to the default `androidTest` source set.
+
+Tests that require a particular `targetSdk` version should be added to the appropriate
+[source set](https://developer.android.com/build/build-variants#sourcesets).
+
+## Running tests from Android Studio
+Tests can be run as normal in Android Studio. You must use the "Build Variants" menu to select
+the product flavor to run. Use one of
+
+* `targetSdkLatestDebug`
+* `targetSdk32Debug`
+
+You must select the corresponding build variant in order to run tests located outside the shared
+source set.
\ No newline at end of file
diff --git a/webkit/integration-tests/instrumentation/build.gradle b/webkit/integration-tests/instrumentation/build.gradle
new file mode 100644
index 0000000..3c0f930
--- /dev/null
+++ b/webkit/integration-tests/instrumentation/build.gradle
@@ -0,0 +1,50 @@
+import androidx.build.Publish
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id('com.android.library')
+}
+android {
+    namespace 'androidx.webkit.instrumentation'
+
+    defaultConfig {
+        multiDexEnabled = true
+        targetSdkVersion 33  // This should be the latest SDK version at all times.
+    }
+    flavorDimensions = ["targetSdk"]
+
+    productFlavors {
+        targetSdk32 {
+            dimension "targetSdk"
+            targetSdkVersion 32
+        }
+        targetSdkLatest {
+            dimension "targetSdk"
+            // uses default config
+        }
+    }
+}
+dependencies {
+    androidTestImplementation(project(":webkit:webkit"))
+
+    androidTestImplementation(libs.okhttpMockwebserver)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation("androidx.appcompat:appcompat:1.1.0")
+    androidTestImplementation("androidx.concurrent:concurrent-futures:1.0.0")
+    androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
+    androidTestImplementation(libs.multidex)
+
+    // Hamcrest matchers:
+    androidTestImplementation(libs.bundles.espressoContrib, excludes.espresso)
+}
+
+androidx {
+    type = type = LibraryType.INTERNAL_TEST_LIBRARY
+    publish = Publish.NONE  // This library exists for CI-testing only - do not publish.
+    additionalDeviceTestApkKeys.add("chrome")
+}
diff --git a/webkit/webkit/src/androidTest/AndroidManifest.xml b/webkit/integration-tests/instrumentation/src/androidTest/AndroidManifest.xml
similarity index 86%
rename from webkit/webkit/src/androidTest/AndroidManifest.xml
rename to webkit/integration-tests/instrumentation/src/androidTest/AndroidManifest.xml
index 86da6b2..a1eefc5 100644
--- a/webkit/webkit/src/androidTest/AndroidManifest.xml
+++ b/webkit/integration-tests/instrumentation/src/androidTest/AndroidManifest.xml
@@ -22,13 +22,13 @@
          http://localhost URLs (and P defaults to blocking cleartext traffic).
          -->
     <application android:label="AndroidX Webkit Unittests"
-                 android:hardwareAccelerated="true"
-                 android:usesCleartextTraffic="true">
+        android:hardwareAccelerated="true"
+        android:usesCleartextTraffic="true">
 
         <activity android:name="androidx.webkit.WebViewTestActivity"/>
         <activity android:name="androidx.webkit.WebViewDarkThemeTestActivity"
-             android:theme="@style/Theme.AppCompat"/>
+            android:theme="@style/Theme.AppCompat"/>
         <activity android:name="androidx.webkit.WebViewLightThemeTestActivity"
-             android:theme="@style/Theme.AppCompat.Light"/>
+            android:theme="@style/Theme.AppCompat.Light"/>
     </application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/webkit/webkit/src/androidTest/assets/star.svg b/webkit/integration-tests/instrumentation/src/androidTest/assets/star.svg
similarity index 100%
rename from webkit/webkit/src/androidTest/assets/star.svg
rename to webkit/integration-tests/instrumentation/src/androidTest/assets/star.svg
diff --git a/webkit/webkit/src/androidTest/assets/star.svgz b/webkit/integration-tests/instrumentation/src/androidTest/assets/star.svgz
similarity index 100%
rename from webkit/webkit/src/androidTest/assets/star.svgz
rename to webkit/integration-tests/instrumentation/src/androidTest/assets/star.svgz
Binary files differ
diff --git a/webkit/webkit/src/androidTest/assets/text/test.txt b/webkit/integration-tests/instrumentation/src/androidTest/assets/text/test.txt
similarity index 100%
rename from webkit/webkit/src/androidTest/assets/text/test.txt
rename to webkit/integration-tests/instrumentation/src/androidTest/assets/text/test.txt
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/BoundaryInterfaceTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/BoundaryInterfaceTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/BoundaryInterfaceTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/BoundaryInterfaceTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/CookieManagerCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/CookieManagerCompatTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/CookieManagerCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/CookieManagerCompatTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/IncompatibilityTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/IncompatibilityTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/IncompatibilityTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/IncompatibilityTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/PollingCheck.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PollingCheck.java
similarity index 94%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/PollingCheck.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PollingCheck.java
index f7098fb..9cf05c9 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/PollingCheck.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PollingCheck.java
@@ -16,6 +16,8 @@
 
 package androidx.webkit;
 
+import android.annotation.SuppressLint;
+
 import androidx.annotation.NonNull;
 
 import org.junit.Assert;
@@ -40,6 +42,7 @@
 
     protected abstract boolean check();
 
+    @SuppressLint("BanThreadSleep")
     public void run() {
         if (check()) {
             return;
@@ -63,6 +66,7 @@
         Assert.fail("unexpected timeout");
     }
 
+    @SuppressLint("BanThreadSleep")
     public static void check(@NonNull CharSequence message, long timeout,
             @NonNull Callable<Boolean> condition)
             throws Exception {
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/PostMessageTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PostMessageTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/PostMessageTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/PostMessageTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerClientCompatTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
similarity index 97%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
index e2c7976..e7f9feb 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/ServiceWorkerWebSettingsCompatTest.java
@@ -18,6 +18,7 @@
 
 import static androidx.webkit.WebViewFeature.isFeatureSupported;
 
+import android.annotation.SuppressLint;
 import android.os.Build;
 import android.os.SystemClock;
 import android.webkit.WebSettings;
@@ -48,6 +49,7 @@
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class ServiceWorkerWebSettingsCompatTest {
 
+    public static final String TEST_APK_NAME = "androidx.webkit.instrumentation.test";
     private ServiceWorkerWebSettingsCompat mSettings;
 
     private static final long POLL_TIMEOUT_DURATION_MS = 5000;
@@ -263,7 +265,7 @@
                 request = server.takeRequest(5, TimeUnit.SECONDS);
             } while (request != null && !TEXT_CONTENT_PATH.equals(request.getPath()));
             Assert.assertNotNull("Test timed out while waiting for expected request", request);
-            Assert.assertEquals("androidx.webkit.test", request.getHeader("X-Requested-With"));
+            Assert.assertEquals(TEST_APK_NAME, request.getHeader("X-Requested-With"));
             webViewOnUiThread.setCleanupTask(() -> waitForServiceWorkerDone(webViewOnUiThread));
         }
     }
@@ -311,6 +313,7 @@
      * for other tests.
      * See b/230078824.
      */
+    @SuppressLint("BanThreadSleep")
     private void waitForServiceWorkerDone(final WebViewOnUiThread webViewOnUiThread) {
         long timeout = SystemClock.uptimeMillis() + POLL_TIMEOUT_DURATION_MS;
         while (SystemClock.uptimeMillis() < timeout
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/TracingControllerTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/TracingControllerTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/TracingControllerTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/TracingControllerTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebMessageCompatUnitTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebMessageCompatUnitTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebMessageCompatUnitTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebMessageCompatUnitTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
similarity index 94%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
index be61919..1b50075 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkModeTestBase.java
@@ -18,7 +18,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.Build;
@@ -44,7 +43,7 @@
  * Base class for dark mode related test.
  */
 @RequiresApi(Build.VERSION_CODES.KITKAT)
-public class WebSettingsCompatDarkModeTestBase<T extends Activity> {
+public class WebSettingsCompatDarkModeTestBase<T extends WebViewTestActivity> {
 
     // The size of WebViews to use in the app.
     private static final int WEBVIEW_SIZE = 128;
@@ -76,17 +75,18 @@
     // representation.
     @SuppressWarnings("deprecation")
     @Rule
-    public final TargetSdkActivityTestRule<T> mActivityRule;
+    public final androidx.test.rule.ActivityTestRule<T> mActivityRule;
 
-    public WebSettingsCompatDarkModeTestBase(@NonNull Class<T> activityClass, int targetSdk) {
-        mActivityRule = new TargetSdkActivityTestRule<T>(activityClass,
-                targetSdk);
+    /** @noinspection deprecation*/
+    @SuppressWarnings("deprecation")
+    public WebSettingsCompatDarkModeTestBase(@NonNull Class<T> activityClass) {
+        mActivityRule = new androidx.test.rule.ActivityTestRule<>(activityClass);
     }
 
     @Before
     public void setUp() {
         mWebViewOnUiThread = new WebViewOnUiThread(
-                ((WebViewTestActivity) mActivityRule.getActivity()).getWebView());
+                mActivityRule.getActivity().getWebView());
         mWebViewOnUiThread.getSettings().setJavaScriptEnabled(true);
     }
 
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
similarity index 97%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
index b283da8..f0b21a0 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -46,6 +46,7 @@
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class WebSettingsCompatTest {
+    public static final String TEST_APK_NAME = "androidx.webkit.instrumentation.test";
     WebViewOnUiThread mWebViewOnUiThread;
 
     @Before
@@ -180,7 +181,7 @@
             mWebViewOnUiThread.loadUrl(requestUrl);
             RecordedRequest recordedRequest = mockWebServer.takeRequest();
             String headerValue = recordedRequest.getHeader("X-Requested-With");
-            Assert.assertEquals("androidx.webkit.test", headerValue);
+            Assert.assertEquals(TEST_APK_NAME, headerValue);
         }
     }
 }
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewApkTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewApkTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewApkTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewApkTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewClientCompatTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewCompatTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDarkThemeTestActivity.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDarkThemeTestActivity.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDarkThemeTestActivity.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDarkThemeTestActivity.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewLightThemeTestActivity.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewLightThemeTestActivity.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewLightThemeTestActivity.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewLightThemeTestActivity.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
similarity index 98%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index 69f6cd5..d22e732 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -271,6 +271,9 @@
                 () -> WebViewCompat.removeWebMessageListener(mWebView, jsObjectName));
     }
 
+    /**
+     * @deprecated unreleased API to be removed
+     */
     @NonNull
     @Deprecated
     @SuppressWarnings("deprecation") // To be removed in 1.9.0
@@ -290,7 +293,7 @@
      * Test fails if the load timeout elapses.
      * @param url The URL to load.
      */
-    void loadUrlAndWaitForCompletion(final String url) {
+    public void loadUrlAndWaitForCompletion(@NonNull final String url) {
         callAndWait(() -> mWebView.loadUrl(url));
     }
 
@@ -393,7 +396,8 @@
         return WebkitUtils.onMainThreadSync(() -> WebViewCompat.getWebChromeClient(webView));
     }
 
-    WebView getWebViewOnCurrentThread() {
+    @NonNull
+    public WebView getWebViewOnCurrentThread() {
         return mWebView;
     }
 
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewTestActivity.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewTestActivity.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewTestActivity.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewTestActivity.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewVersion.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewVersion.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewVersion.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewVersion.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageCompatTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebViewWebMessageListenerTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebkitUtils.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebkitUtils.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebkitUtils.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebkitUtils.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
similarity index 100%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/AssetHelperTest.java
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/internal/WebViewFeatureInternalTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/WebViewFeatureInternalTest.java
similarity index 97%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/internal/WebViewFeatureInternalTest.java
rename to webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/WebViewFeatureInternalTest.java
index 3a1ba43..a406145 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/internal/WebViewFeatureInternalTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/internal/WebViewFeatureInternalTest.java
@@ -22,8 +22,6 @@
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.webkit.internal.ConditionallySupportedFeature;
-import androidx.webkit.internal.WebViewFeatureInternal;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/webkit/webkit/src/androidTest/res/raw/test.txt b/webkit/integration-tests/instrumentation/src/androidTest/res/raw/test.txt
similarity index 100%
rename from webkit/webkit/src/androidTest/res/raw/test.txt
rename to webkit/integration-tests/instrumentation/src/androidTest/res/raw/test.txt
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java b/webkit/integration-tests/instrumentation/src/androidTestTargetSdk32/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
similarity index 74%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
rename to webkit/integration-tests/instrumentation/src/androidTestTargetSdk32/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
index e78dafc..f7085fe 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTestTargetSdk32/java/androidx/webkit/WebSettingsCompatForceDarkTest.java
@@ -28,20 +28,21 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
 
-import org.junit.Ignore;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 
+/**
+ * Instrumentation tests for pre-33 dark mode behavior.
+ */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = VERSION_CODES.LOLLIPOP)
 public class WebSettingsCompatForceDarkTest extends
         WebSettingsCompatDarkModeTestBase<WebViewLightThemeTestActivity> {
     public WebSettingsCompatForceDarkTest() {
-        // Set targetSdkVersion to the max version the force dark API works on.
-        // TODO(http://b/214741472): Use VERSION_CODES.S_V2 once Android X supports it.
-        super(WebViewLightThemeTestActivity.class, VERSION_CODES.S);
+        super(WebViewLightThemeTestActivity.class);
     }
 
     /**
@@ -54,7 +55,7 @@
     public void testForceDark_default() throws Throwable {
         WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
 
-        assertEquals("The default force dark state should be AUTO",
+        Assert.assertEquals("The default force dark state should be AUTO",
                 WebSettingsCompat.FORCE_DARK_AUTO,
                 WebSettingsCompat.getForceDark(getSettingsOnUiThread()));
     }
@@ -65,7 +66,6 @@
      * should be reflected in that test as necessary. See http://go/modifying-webview-cts.
      */
     @SuppressWarnings("deprecation")
-    @Ignore("Disabled due to b/230480958")
     @Test
     public void testForceDark_rendersDark() throws Throwable {
         WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
@@ -100,7 +100,6 @@
      * i.e. web contents are always darkened by a user agent.
      */
     @SuppressWarnings("deprecation")
-    @Ignore("Disabled due to b/240432254")
     @Test
     public void testForceDark_userAgentDarkeningOnly() {
         WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
@@ -133,8 +132,6 @@
      * i.e. web contents are darkened only by web theme.
      */
     @SuppressWarnings("deprecation")
-    @Ignore("Disabled due to b/260586583")
-    @SdkSuppress(maxSdkVersion = 32) // b/254572377
     @Test
     public void testForceDark_webThemeDarkeningOnly() {
         WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
@@ -161,39 +158,4 @@
         assertThat("Bitmap colour should be green", getWebPageColor(), isGreen());
         assertTrue(prefersDarkTheme());
     }
-
-    /**
-     * Test to exercise PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING option,
-     * i.e. web contents are darkened by a user agent if there is no dark web theme.
-     */
-    @SuppressWarnings("deprecation")
-    @Test
-    @Ignore("Disabled due to b/202546063")
-    public void testForceDark_preferWebThemeOverUADarkening() {
-        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK);
-        WebkitUtils.checkFeature(WebViewFeature.FORCE_DARK_STRATEGY);
-        WebkitUtils.checkFeature(WebViewFeature.OFF_SCREEN_PRERASTER);
-        setWebViewSize();
-
-        getWebViewOnUiThread().loadUrlAndWaitForCompletion("about:blank");
-
-        WebSettingsCompat.setForceDark(
-                getSettingsOnUiThread(), WebSettingsCompat.FORCE_DARK_ON);
-        WebSettingsCompat.setForceDarkStrategy(getSettingsOnUiThread(),
-                WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING);
-
-        getWebViewOnUiThread().loadUrlAndWaitForCompletion("about:blank");
-        // Loading a page without dark-theme support should result in a dark background as
-        // web page is darken by a user agent
-        assertTrue("Bitmap colour should be dark",
-                ColorUtils.calculateLuminance(getWebPageColor()) < 0.5f);
-        assertFalse(prefersDarkTheme());
-
-        // Loading a page with dark-theme support should result in a green background (as
-        // specified in media-query)
-        getWebViewOnUiThread().loadDataAndWaitForCompletion(mDarkThemeSupport, "text/html",
-                "base64");
-        assertThat("Bitmap colour should be green", getWebPageColor(), isGreen());
-        assertTrue(prefersDarkTheme());
-    }
 }
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkThemeTest.java b/webkit/integration-tests/instrumentation/src/androidTestTargetSdkLatest/java/androidx/webkit/WebSettingsCompatDarkThemeTest.java
similarity index 91%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkThemeTest.java
rename to webkit/integration-tests/instrumentation/src/androidTestTargetSdkLatest/java/androidx/webkit/WebSettingsCompatDarkThemeTest.java
index 48840fe..2e7f311 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatDarkThemeTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTestTargetSdkLatest/java/androidx/webkit/WebSettingsCompatDarkThemeTest.java
@@ -30,18 +30,16 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-
+/**
+ * Instrumentation tests for dark mode on targetSdk >= 33.
+ */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class WebSettingsCompatDarkThemeTest extends
         WebSettingsCompatDarkModeTestBase<WebViewDarkThemeTestActivity> {
     public WebSettingsCompatDarkThemeTest() {
-        // targetSdkVersion to T, it is min version the algorithmic darkening works.
-        // VERSION_CODES.TIRAMISU can't be compiled, follows the pattern in
-        // core/core/src/main/java/androidx/core/os/BuildCompat.java,
-        // uses 33 instead of VERSION_CODES.TIRAMISU
-        super(WebViewDarkThemeTestActivity.class, 33);
+        super(WebViewDarkThemeTestActivity.class);
     }
 
     /**
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatLightThemeTest.java b/webkit/integration-tests/instrumentation/src/androidTestTargetSdkLatest/java/androidx/webkit/WebSettingsCompatLightThemeTest.java
similarity index 95%
rename from webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatLightThemeTest.java
rename to webkit/integration-tests/instrumentation/src/androidTestTargetSdkLatest/java/androidx/webkit/WebSettingsCompatLightThemeTest.java
index d16d15c..68ae3ff 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatLightThemeTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTestTargetSdkLatest/java/androidx/webkit/WebSettingsCompatLightThemeTest.java
@@ -29,16 +29,16 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-
+/**
+ * Instrumentation tests for light mode on targetSdk >= 33.
+ */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class WebSettingsCompatLightThemeTest extends
         WebSettingsCompatDarkModeTestBase<WebViewLightThemeTestActivity> {
     public WebSettingsCompatLightThemeTest() {
-        // targetSdkVersion to T, it is min version the algorithmic darkening works.
-        // TODO(http://b/214741472): Use VERSION_CODES.TIRAMISU once available.
-        super(WebViewLightThemeTestActivity.class, 33);
+        super(WebViewLightThemeTestActivity.class);
     }
 
     /**
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/TargetSdkActivityTestRule.java b/webkit/webkit/src/androidTest/java/androidx/webkit/TargetSdkActivityTestRule.java
deleted file mode 100644
index 2059802..0000000
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/TargetSdkActivityTestRule.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-/**
- * This class is used to override the default targetSdkVersion value in ApplicationInfo.
- */
-@SuppressWarnings("deprecation")
-public class TargetSdkActivityTestRule<T extends Activity> extends
-        androidx.test.rule.ActivityTestRule<T> {
-    private int mTargetSdk;
-    private Context mAppContext;
-
-    public TargetSdkActivityTestRule(@NonNull Class<T> activityClass, int targetSdk) {
-        super(activityClass);
-        mTargetSdk = targetSdk;
-    }
-
-    @Override
-    protected void beforeActivityLaunched() {
-        try {
-            runOnUiThread(() -> {
-                        mAppContext = spy(
-                                InstrumentationRegistry.getInstrumentation().getTargetContext()
-                                        .getApplicationContext());
-                        ApplicationInfo appInfo = mAppContext.getApplicationInfo();
-                        appInfo.targetSdkVersion = mTargetSdk;
-                        when(mAppContext.getApplicationInfo()).thenReturn(appInfo);
-                    }
-            );
-        } catch (Throwable throwable) {
-            throw new RuntimeException(throwable);
-        }
-    }
-}
diff --git a/work/work-runtime/lint-baseline.xml b/work/work-runtime/lint-baseline.xml
index 8ab7d19..9b1a604 100644
--- a/work/work-runtime/lint-baseline.xml
+++ b/work/work-runtime/lint-baseline.xml
@@ -182,87 +182,6 @@
     </issue>
 
     <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(2000L); // Async wait duration."
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(SleepTestWorker.SLEEP_DURATION);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(1000L);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(200);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(1000L);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(1000L);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(1000L);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(6000L);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(1000L);"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java"/>
-    </issue>
-
-    <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public final void addListener(Runnable listener, Executor executor) {"
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
index cf09c13..0bf15bf 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
@@ -17,7 +17,6 @@
 package androidx.work
 
 import android.content.Context
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.work.impl.Processor
@@ -31,7 +30,8 @@
 import androidx.work.impl.model.WorkSpec
 import androidx.work.impl.testutils.TrackingWorkerFactory
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
 import androidx.work.worker.FailureWorker
 import androidx.work.worker.LatchWorker
 import androidx.work.worker.StopAwareWorker
@@ -45,17 +45,12 @@
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class SchedulersTest {
-    val context = ApplicationProvider.getApplicationContext<Context>().applicationContext
     val factory = TrackingWorkerFactory()
     val configuration = Configuration.Builder().setWorkerFactory(factory).build()
-    val taskExecutor = WorkManagerTaskExecutor(configuration.taskExecutor)
-    val db = WorkDatabase.create(
-        context, taskExecutor.serialTaskExecutor, configuration.clock, true)
-    val processor = Processor(context, configuration, taskExecutor, db)
-    val launcher = WorkLauncherImpl(processor, taskExecutor)
-    val trackers = Trackers(context, taskExecutor)
-    val greedyScheduler = GreedyScheduler(context, configuration, trackers, processor,
-        launcher, taskExecutor)
+    val env = TestEnv(configuration)
+    val context = env.context
+    val trackers = Trackers(context, env.taskExecutor)
+    val greedyScheduler = GreedyScheduler(env, trackers)
 
     @Test
     fun runDependency() {
@@ -69,7 +64,7 @@
             override fun hasLimitedSchedulingSlots() = false
         }
         val wm = WorkManagerImpl(
-            context, configuration, taskExecutor, db,
+            context, configuration, env.taskExecutor, env.db,
         ) { context: Context,
             configuration: Configuration,
             taskExecutor: TaskExecutor,
@@ -87,7 +82,7 @@
         val dependency = OneTimeWorkRequest.from(TestWorker::class.java)
         wm.beginWith(workRequest).then(dependency).enqueue()
         val finishedLatch = CountDownLatch(1)
-        taskExecutor.mainThreadExecutor.execute {
+        env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(dependency.id).observeForever {
                 if (it.state == WorkInfo.State.SUCCEEDED) finishedLatch.countDown()
             }
@@ -108,14 +103,14 @@
             override fun hasLimitedSchedulingSlots() = false
         }
         val wm = WorkManagerImpl(
-            context, configuration, taskExecutor, db,
-            listOf(trackingScheduler, greedyScheduler), processor, trackers
+            context, configuration, env.taskExecutor, env.db,
+            listOf(trackingScheduler, greedyScheduler), env.processor, trackers
         )
 
         val workRequest = OneTimeWorkRequest.from(FailureWorker::class.java)
         wm.enqueue(workRequest)
         val finishedLatch = CountDownLatch(1)
-        taskExecutor.mainThreadExecutor.execute {
+        env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(workRequest.id).observeForever {
                 if (it.state == WorkInfo.State.FAILED) finishedLatch.countDown()
             }
@@ -128,10 +123,11 @@
     fun interruptionReschedules() {
         val schedulers = mutableListOf<Scheduler>()
         val wm = WorkManagerImpl(
-            context, configuration, taskExecutor, db, schedulers, processor, trackers
+            context, configuration, env.taskExecutor, env.db, schedulers, env.processor, trackers
         )
         val scheduledSpecs = mutableListOf<WorkSpec>()
         val cancelledIds = mutableListOf<String>()
+        val launcher = WorkLauncherImpl(env.processor, env.taskExecutor)
         val scheduler = object : Scheduler {
             val tokens = StartStopTokens()
             override fun schedule(vararg workSpecs: WorkSpec) {
@@ -153,7 +149,7 @@
         wm.enqueue(request)
         val reenqueuedLatch = CountDownLatch(1)
         var running = false
-        taskExecutor.mainThreadExecutor.execute {
+        env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(request.id).observeForever {
                 when (it.state) {
                     WorkInfo.State.RUNNING -> {
@@ -179,11 +175,12 @@
     fun periodicReschedules() {
         val schedulers = mutableListOf<Scheduler>()
         val wm = WorkManagerImpl(
-            context, configuration, taskExecutor, db,
-            schedulers, processor, trackers
+            context, configuration, env.taskExecutor, env.db,
+            schedulers, env.processor, trackers
         )
         val scheduledSpecs = mutableListOf<WorkSpec>()
         val cancelledIds = mutableListOf<String>()
+        val launcher = WorkLauncherImpl(env.processor, env.taskExecutor)
         val scheduler = object : Scheduler {
             val tokens = StartStopTokens()
             override fun schedule(vararg workSpecs: WorkSpec) {
@@ -207,7 +204,7 @@
         val reenqueuedLatch = CountDownLatch(1)
         var running = false
         val worker = factory.awaitWorker(request.id) as LatchWorker
-        taskExecutor.mainThreadExecutor.execute {
+        env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(request.id).observeForever {
                 when (it.state) {
                     WorkInfo.State.RUNNING -> {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt
index 607a64f..fde52d5 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt
@@ -18,20 +18,16 @@
 
 import android.app.job.JobParameters.STOP_REASON_CANCELLED_BY_APP
 import android.app.job.JobParameters.STOP_REASON_CONSTRAINT_CHARGING
-import android.content.Context
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
-import androidx.work.impl.Processor
-import androidx.work.impl.WorkDatabase
-import androidx.work.impl.WorkLauncherImpl
 import androidx.work.impl.WorkManagerImpl
-import androidx.work.impl.background.greedy.GreedyScheduler
 import androidx.work.impl.constraints.trackers.Trackers
 import androidx.work.impl.testutils.TestConstraintTracker
 import androidx.work.impl.testutils.TrackingWorkerFactory
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
 import androidx.work.worker.InfiniteTestWorker
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executors
@@ -44,28 +40,17 @@
 @SmallTest
 @SdkSuppress(minSdkVersion = 31)
 class StopReasonTest {
-    val context = ApplicationProvider.getApplicationContext<Context>()
     val workerFactory = TrackingWorkerFactory()
-    val configuration = Configuration.Builder().setWorkerFactory(workerFactory).build()
-    val executor = Executors.newSingleThreadExecutor()
-    val taskExecutor = WorkManagerTaskExecutor(executor)
-    val fakeChargingTracker = TestConstraintTracker(false, context, taskExecutor)
+    val configuration = Configuration.Builder().setWorkerFactory(workerFactory)
+        .setTaskExecutor(Executors.newSingleThreadExecutor()).build()
+    val env = TestEnv(configuration)
+    val fakeChargingTracker = TestConstraintTracker(false, env.context, env.taskExecutor)
     val trackers = Trackers(
-        context = context,
-        taskExecutor = taskExecutor,
+        context = env.context,
+        taskExecutor = env.taskExecutor,
         batteryChargingTracker = fakeChargingTracker
     )
-    val db = WorkDatabase.create(context, executor, configuration.clock, true)
-
-    val processor = Processor(context, configuration, taskExecutor, db)
-    val launcher = WorkLauncherImpl(processor, taskExecutor)
-    val greedyScheduler = GreedyScheduler(
-        context, configuration, trackers, processor, launcher,
-        taskExecutor
-    )
-    val workManager = WorkManagerImpl(
-        context, configuration, taskExecutor, db, listOf(greedyScheduler), processor, trackers
-    )
+    val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
 
     init {
         WorkManagerImpl.setDelegate(workManager)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
index 38a29b4..1d2b7d7 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
@@ -16,22 +16,17 @@
 
 package androidx.work
 
-import android.content.Context
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.work.ExistingWorkPolicy.APPEND
 import androidx.work.ExistingWorkPolicy.KEEP
-import androidx.work.impl.Processor
-import androidx.work.impl.Scheduler
-import androidx.work.impl.WorkDatabase
-import androidx.work.impl.WorkLauncherImpl
 import androidx.work.impl.WorkManagerImpl
-import androidx.work.impl.background.greedy.GreedyScheduler
 import androidx.work.impl.constraints.trackers.Trackers
 import androidx.work.impl.testutils.TestConstraintTracker
 import androidx.work.impl.testutils.TrackingWorkerFactory
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
 import androidx.work.testutils.launchTester
 import androidx.work.worker.LatchWorker
 import androidx.work.worker.TestWorker
@@ -45,31 +40,20 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class WorkInfoFlowsTest {
-    val context = ApplicationProvider.getApplicationContext<Context>()
     val workerFactory = TrackingWorkerFactory()
-    val configuration = Configuration.Builder().setWorkerFactory(workerFactory).build()
-    val executor = Executors.newSingleThreadExecutor()
-    val taskExecutor = WorkManagerTaskExecutor(executor)
-    val fakeChargingTracker = TestConstraintTracker(false, context, taskExecutor)
+    val configuration = Configuration.Builder().setWorkerFactory(workerFactory)
+        .setTaskExecutor(Executors.newSingleThreadExecutor())
+        .build()
+    val env = TestEnv(configuration)
+    val fakeChargingTracker = TestConstraintTracker(false, env.context, env.taskExecutor)
     val trackers = Trackers(
-        context = context,
-        taskExecutor = taskExecutor,
+        context = env.context,
+        taskExecutor = env.taskExecutor,
         batteryChargingTracker = fakeChargingTracker
     )
-    val db = WorkDatabase.create(context, executor, configuration.clock, true)
-
-    // ugly, ugly hack because of circular dependency:
-    // Schedulers need WorkManager, WorkManager needs schedulers
-    val schedulers = mutableListOf<Scheduler>()
-    val processor = Processor(context, configuration, taskExecutor, db)
-    val workManager = WorkManagerImpl(
-        context, configuration, taskExecutor, db, schedulers, processor, trackers
-    )
-    val greedyScheduler = GreedyScheduler(context, configuration, trackers,
-        processor, WorkLauncherImpl(processor, taskExecutor), taskExecutor)
+    val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
 
     init {
-        schedulers.add(greedyScheduler)
         WorkManagerImpl.setDelegate(workManager)
     }
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
index 1eacdd5..4185694 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Observer
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -28,20 +27,18 @@
 import androidx.work.WorkManager.UpdateResult.APPLIED_FOR_NEXT_RUN
 import androidx.work.WorkManager.UpdateResult.APPLIED_IMMEDIATELY
 import androidx.work.WorkManager.UpdateResult.NOT_APPLIED
-import androidx.work.impl.Processor
-import androidx.work.impl.WorkDatabase
-import androidx.work.impl.WorkLauncherImpl
 import androidx.work.impl.WorkManagerImpl
-import androidx.work.impl.background.greedy.GreedyScheduler
 import androidx.work.impl.constraints.ConstraintsState.ConstraintsMet
 import androidx.work.impl.constraints.trackers.Trackers
 import androidx.work.impl.testutils.TestConstraintTracker
 import androidx.work.impl.testutils.TestOverrideClock
 import androidx.work.impl.testutils.TrackingWorkerFactory
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
 import androidx.work.impl.workers.ARGUMENT_CLASS_NAME
 import androidx.work.impl.workers.ConstraintTrackingWorker
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
 import androidx.work.worker.LatchWorker
 import androidx.work.worker.RetryWorker
 import androidx.work.worker.TestWorker
@@ -59,28 +56,21 @@
 
 @RunWith(AndroidJUnit4::class)
 class WorkUpdateTest {
-    val context = ApplicationProvider.getApplicationContext<Context>()
     val workerFactory = TrackingWorkerFactory()
     val testClock = TestOverrideClock()
     val configuration =
-        Configuration.Builder().setClock(testClock).setWorkerFactory(workerFactory).build()
-    val executor = Executors.newSingleThreadExecutor()
-    val taskExecutor = WorkManagerTaskExecutor(executor)
-    val fakeChargingTracker = TestConstraintTracker(false, context, taskExecutor)
+        Configuration.Builder().setClock(testClock).setWorkerFactory(workerFactory)
+            .setTaskExecutor(Executors.newSingleThreadExecutor()).build()
+    val env = TestEnv(configuration)
+    val taskExecutor = env.taskExecutor
+    val fakeChargingTracker = TestConstraintTracker(false, env.context, env.taskExecutor)
     val trackers = Trackers(
-        context = context,
-        taskExecutor = taskExecutor,
+        context = env.context,
+        taskExecutor = env.taskExecutor,
         batteryChargingTracker = fakeChargingTracker
     )
-    val db = WorkDatabase.create(context, executor, configuration.clock, true)
-
-    val processor = Processor(context, configuration, taskExecutor, db)
-    val launcher = WorkLauncherImpl(processor, taskExecutor)
-    val greedyScheduler = GreedyScheduler(context, configuration, trackers, processor, launcher,
-        taskExecutor)
-    val workManager = WorkManagerImpl(
-        context, configuration, taskExecutor, db, listOf(greedyScheduler), processor, trackers
-    )
+    val greedyScheduler = GreedyScheduler(env, trackers)
+    val workManager = WorkManager(env, listOf(greedyScheduler), trackers)
 
     init {
         WorkManagerImpl.setDelegate(workManager)
@@ -409,7 +399,7 @@
         val request = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
             .addTag("original").build()
         val onExecutedLatch = CountDownLatch(1)
-        processor.addExecutionListener { id, _ ->
+        env.processor.addExecutionListener { id, _ ->
             if (id.workSpecId == request.stringId) onExecutedLatch.countDown()
         }
         workManager.enqueue(request).result.get()
@@ -427,7 +417,7 @@
         val newTags = workManager.getWorkInfoById(request.id).get().tags
         assertThat(newTags).contains("updated")
         assertThat(newTags).doesNotContain("original")
-        val workSpec = db.workSpecDao().getWorkSpec(request.stringId)!!
+        val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
         assertThat(workSpec.periodCount).isEqualTo(1)
     }
 
@@ -495,7 +485,7 @@
             .setConstraints(Constraints(requiresBatteryNotLow = true))
             .build()
         workManager.updateWork(updateRequest).get()
-        val workSpec = db.workSpecDao().getWorkSpec(originRequest.stringId)!!
+        val workSpec = env.db.workSpecDao().getWorkSpec(originRequest.stringId)!!
         assertThat(workSpec.workerClassName).isEqualTo(ConstraintTrackingWorker::class.java.name)
         assertThat(workSpec.input.getString(ARGUMENT_CLASS_NAME))
             .isEqualTo(RetryWorker::class.java.name)
@@ -601,7 +591,7 @@
 
         val workInfo = workManager.getWorkInfoById(request.id).get()
         assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis)
-        val workSpec = db.workSpecDao().getWorkSpec(request.stringId)!!
+        val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
         // attemptCount is still kept, just not used in the schedule time calculation.
         assertThat(workSpec.runAttemptCount).isEqualTo(1)
     }
@@ -633,7 +623,7 @@
             testClock.currentTimeMillis + DAYS.toMillis(2)
         )
 
-        val workSpec = db.workSpecDao().getWorkSpec(request.stringId)!!
+        val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
         assertThat(workSpec.nextScheduleTimeOverride).isEqualTo(Long.MAX_VALUE)
         // Still needs to increment the generation to propagate the new cleared value.
         assertThat(workSpec.nextScheduleTimeOverrideGeneration).isEqualTo(2)
@@ -657,7 +647,7 @@
                 .build()
         ).get()
 
-        val workSpec = db.workSpecDao().getWorkSpec(request.stringId)!!
+        val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
         assertThat(workSpec.nextScheduleTimeOverride).isEqualTo(Long.MAX_VALUE)
 
         // Technically I believe any 'clear' call could leave the generation the same, since it's
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index 1819c89..d3e769e 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -20,11 +20,9 @@
 
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -39,18 +37,14 @@
 import androidx.arch.core.executor.TaskExecutor;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 import androidx.work.Configuration;
-import androidx.work.Data;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.WorkContinuation;
 import androidx.work.WorkInfo;
 import androidx.work.WorkManagerTest;
-import androidx.work.impl.foreground.ForegroundProcessor;
 import androidx.work.impl.model.WorkSpec;
-import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.SynchronousExecutor;
 import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
 import androidx.work.worker.TestWorker;
@@ -262,84 +256,6 @@
     }
 
     @Test
-    @LargeTest
-    @SuppressWarnings("unchecked")
-    public void testContinuation_joinPassesAllOutput()
-            throws ExecutionException, InterruptedException {
-
-        final String intTag = "myint";
-        final String stringTag = "mystring";
-        ForegroundProcessor foregroundProcessor = mock(ForegroundProcessor.class);
-
-        OneTimeWorkRequest firstWork = new OneTimeWorkRequest.Builder(TestWorker.class)
-                .setInitialState(WorkInfo.State.SUCCEEDED)
-                .build();
-        OneTimeWorkRequest secondWork = new OneTimeWorkRequest.Builder(TestWorker.class)
-                .setInitialState(WorkInfo.State.SUCCEEDED)
-                .build();
-
-        WorkSpecDao workSpecDao = mDatabase.workSpecDao();
-        workSpecDao.insertWorkSpec(firstWork.getWorkSpec());
-        workSpecDao.insertWorkSpec(secondWork.getWorkSpec());
-
-        workSpecDao.setOutput(
-                firstWork.getStringId(),
-                new Data.Builder().putInt(intTag, 0).build());
-        workSpecDao.setOutput(
-                secondWork.getStringId(),
-                new Data.Builder().putInt(intTag, 1).putString(stringTag, "hello").build());
-
-        WorkContinuation firstContinuation =
-                new WorkContinuationImpl(mWorkManagerImpl, Collections.singletonList(firstWork));
-        WorkContinuation secondContinuation =
-                new WorkContinuationImpl(mWorkManagerImpl, Collections.singletonList(secondWork));
-        WorkContinuationImpl dependentContinuation =
-                (WorkContinuationImpl) WorkContinuation.combine(
-                        Arrays.asList(firstContinuation, secondContinuation));
-        dependentContinuation.enqueue().getResult().get();
-
-        String joinId = null;
-        for (String id : dependentContinuation.getAllIds()) {
-            if (!firstWork.getStringId().equals(id) && !secondWork.getStringId().equals(id)) {
-                joinId = id;
-                break;
-            }
-        }
-
-        Thread.sleep(5000L);
-
-        // TODO(sumir): I can't seem to get this kicked off automatically, so I'm running it myself.
-        // Figure out what's going on here.
-        Context context = ApplicationProvider.getApplicationContext();
-        new WorkerWrapper.Builder(
-                context,
-                mConfiguration,
-                new InstantWorkTaskExecutor(),
-                foregroundProcessor,
-                mDatabase,
-                workSpecDao.getWorkSpec(joinId),
-                new ArrayList<>())
-                .build()
-                .run();
-
-        assertThat(joinId, is(not(nullValue())));
-        WorkSpec joinWorkSpec = mDatabase.workSpecDao().getWorkSpec(joinId);
-        assertThat(joinWorkSpec, is(not(nullValue())));
-        assertThat(joinWorkSpec.state, is(WorkInfo.State.SUCCEEDED));
-
-        Data output = joinWorkSpec.output;
-        int[] intArray = output.getIntArray(intTag);
-
-        assertThat(intArray, is(not(nullValue())));
-        Arrays.sort(intArray);
-        assertThat(Arrays.binarySearch(intArray, 0), is(not(-1)));
-        assertThat(Arrays.binarySearch(intArray, 1), is(not(-1)));
-        assertThat(output.getStringArray(stringTag), is(not(nullValue())));
-        assertThat(Arrays.asList(output.getStringArray(stringTag)), contains("hello"));
-
-    }
-
-    @Test
     @SmallTest
     public void testContinuation_hasCycles() {
         OneTimeWorkRequest aWork = createTestWorker(); // A
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt
new file mode 100644
index 0000000..45c8b78
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.work.impl
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.work.Configuration
+import androidx.work.ListenableWorker.Result.Success
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkContinuation
+import androidx.work.WorkInfo
+import androidx.work.await
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.testutils.TrackingWorkerFactory
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
+import androidx.work.workDataOf
+import androidx.work.worker.CompletableWorker
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executors
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class WorkContinuationImplTestKt {
+    val workerFactory = TrackingWorkerFactory()
+    val configuration =
+        Configuration.Builder().setWorkerFactory(workerFactory)
+            .setTaskExecutor(Executors.newSingleThreadExecutor()).build()
+    val env = TestEnv(configuration)
+    val taskExecutor = env.taskExecutor
+    val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+    val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+
+    init {
+        WorkManagerImpl.setDelegate(workManager)
+    }
+
+    @Test
+    fun testContinuation_joinPassesAllOutput() = runBlocking<Unit> {
+        val intTag = "myint"
+        val stringTag = "mystring"
+        val firstWork = OneTimeWorkRequest.from(CompletableWorker::class.java)
+        val secondWork = OneTimeWorkRequest.from(CompletableWorker::class.java)
+        val firstContinuation = workManager.beginWith(firstWork)
+        val secondContinuation = workManager.beginWith(secondWork)
+        val combined = WorkContinuation.combine(listOf(firstContinuation, secondContinuation))
+        combined.enqueue().await()
+        val thirdId = combined.workInfos.await().map { it.id }
+            .first { it != firstWork.id && it != secondWork.id }
+        (workerFactory.await(firstWork.id) as CompletableWorker).result
+            .complete(Success(workDataOf(intTag to 1, stringTag to "hello")))
+        (workerFactory.await(secondWork.id) as CompletableWorker).result
+            .complete(Success(workDataOf(intTag to 3)))
+        val info = workManager.getWorkInfoByIdFlow(thirdId)
+            .first { it.state == WorkInfo.State.SUCCEEDED }
+        assertThat(info.outputData.size()).isEqualTo(2)
+        assertThat(info.outputData.getStringArray(stringTag)).isEqualTo(arrayOf("hello"))
+        val intArray = info.outputData.getIntArray(intTag)!!.sortedArray()
+        assertThat(intArray).isEqualTo(intArrayOf(1, 3))
+    }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index e522280..882d802 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -1910,41 +1910,6 @@
     }
 
     @Test
-    @LargeTest
-    @SuppressWarnings("unchecked")
-    @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
-    public void testCancelAllWork_updatesLastCancelAllTimeLiveData() throws InterruptedException {
-        if (Build.VERSION.SDK_INT == 33 && !"REL".equals(Build.VERSION.CODENAME)) {
-            return; // b/262909049: Do not run this test on pre-release Android U.
-        }
-
-        PreferenceUtils preferenceUtils = new PreferenceUtils(mWorkManagerImpl.getWorkDatabase());
-        preferenceUtils.setLastCancelAllTimeMillis(0L);
-
-        TestLifecycleOwner testLifecycleOwner = new TestLifecycleOwner();
-        LiveData<Long> cancelAllTimeLiveData =
-                mWorkManagerImpl.getLastCancelAllTimeMillisLiveData();
-        Observer<Long> mockObserver = mock(Observer.class);
-        cancelAllTimeLiveData.observe(testLifecycleOwner, mockObserver);
-
-        ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
-        verify(mockObserver).onChanged(captor.capture());
-        assertThat(captor.getValue(), is(0L));
-
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
-        insertWorkSpecAndTags(work);
-
-        clearInvocations(mockObserver);
-        CancelWorkRunnable.forAll(mWorkManagerImpl).run();
-
-        Thread.sleep(1000L);
-        verify(mockObserver).onChanged(captor.capture());
-        assertThat(captor.getValue(), is(greaterThan(0L)));
-
-        cancelAllTimeLiveData.removeObservers(testLifecycleOwner);
-    }
-
-    @Test
     @MediumTest
     public void pruneFinishedWork() throws InterruptedException, ExecutionException {
         OneTimeWorkRequest enqueuedWork = new OneTimeWorkRequest.Builder(TestWorker.class).build();
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 6ceeaca..fc6fc8f 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -86,8 +86,6 @@
 import androidx.work.worker.LatchWorker;
 import androidx.work.worker.RetryWorker;
 import androidx.work.worker.ReturnNullResultWorker;
-import androidx.work.worker.SleepTestWorker;
-import androidx.work.worker.StopAwareWorker;
 import androidx.work.worker.TestWorker;
 import androidx.work.worker.UsedWorker;
 
@@ -318,20 +316,6 @@
     }
 
     @Test
-    @LargeTest
-    public void testRunning() throws InterruptedException {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build();
-        insertWork(work);
-        WorkerWrapper wrapper = createBuilder(work.getStringId()).build();
-        FutureListener listener = createAndAddFutureListener(wrapper);
-        mExecutorService.submit(wrapper);
-        Thread.sleep(2000L); // Async wait duration.
-        assertThat(mWorkSpecDao.getState(work.getStringId()), is(RUNNING));
-        Thread.sleep(SleepTestWorker.SLEEP_DURATION);
-        assertThat(listener.mResult, is(false));
-    }
-
-    @Test
     @SmallTest
     public void testRunning_onlyWhenEnqueued() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
@@ -1119,65 +1103,6 @@
     }
 
     @Test
-    @LargeTest
-    public void testInterruption() throws InterruptedException {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
-        insertWork(work);
-
-        WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
-        FutureListener listener = createAndAddFutureListener(workerWrapper);
-        mExecutorService.submit(workerWrapper);
-        workerWrapper.interrupt(0);
-        Thread.sleep(1000L);
-        assertThat(listener.mResult, is(true));
-        assertThat(mWorkSpecDao.getState(work.getStringId()), is(ENQUEUED));
-    }
-
-    @Test
-    @LargeTest
-    public void testInterruption2() throws InterruptedException {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(StopAwareWorker.class).build();
-        insertWork(work);
-
-        WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
-        FutureListener listener = createAndAddFutureListener(workerWrapper);
-        mExecutorService.submit(workerWrapper);
-        Thread.sleep(200);
-        workerWrapper.interrupt(0);
-        Thread.sleep(1000L);
-        assertThat(listener.mResult, is(true));
-        assertThat(mWorkSpecDao.getState(work.getStringId()), is(ENQUEUED));
-    }
-
-    @Test
-    @LargeTest
-    public void testPruneWhileRunning_callsSchedulerCancel() throws InterruptedException {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(LatchWorker.class).build();
-        insertWork(work);
-
-        ExecutorService backgroundExecutor = Executors.newSingleThreadExecutor();
-        LatchWorker latchWorker = getLatchWorker(work, backgroundExecutor);
-
-        WorkerWrapper workerWrapper =
-                createBuilder(work.getStringId()).withWorker(latchWorker).build();
-        FutureListener listener = createAndAddFutureListener(workerWrapper);
-        mExecutorService.submit(workerWrapper);
-
-        Thread.sleep(1000L);
-
-        mDatabase.workSpecDao().delete(work.getStringId());
-        assertThat(latchWorker.mLatch.getCount(), is(greaterThan(0L)));
-
-        latchWorker.mLatch.countDown();
-
-        Thread.sleep(1000L);
-
-        assertThat(listener.mResult, is(notNullValue()));
-        backgroundExecutor.shutdown();
-        assertThat(backgroundExecutor.awaitTermination(3, TimeUnit.SECONDS), is(true));
-    }
-
-    @Test
     @SmallTest
     public void testInterruptionWithoutCancellation_isMarkedOnRunningWorker() {
         OneTimeWorkRequest work =
@@ -1259,37 +1184,6 @@
     }
 
     @Test
-    @LargeTest
-    public void testWorkerWrapper_handlesWorkSpecDeletion() throws InterruptedException {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build();
-        insertWork(work);
-
-        WorkerWrapper workerWrapper =
-                createBuilder(work.getStringId()).build();
-        FutureListener listener = createAndAddFutureListener(workerWrapper);
-        mExecutorService.submit(workerWrapper);
-        mWorkSpecDao.delete(work.getStringId());
-        Thread.sleep(6000L);
-        assertThat(listener.mResult, is(false));
-    }
-
-    @Test
-    @LargeTest
-    public void testWorker_getsRunAttemptCount() throws InterruptedException {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(LatchWorker.class)
-                .setInitialRunAttemptCount(10)
-                .build();
-        insertWork(work);
-
-        WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build();
-
-        mExecutorService.submit(workerWrapper);
-        Thread.sleep(1000L);
-        assertThat(workerWrapper.mWorker.getRunAttemptCount(), is(10));
-        ((LatchWorker) workerWrapper.mWorker).mLatch.countDown();
-    }
-
-    @Test
     @SmallTest
     public void testWorkerThatReturnsNullResult() {
         OneTimeWorkRequest work =
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt
new file mode 100644
index 0000000..b7274e9
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTestKt.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.work.impl
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.work.Configuration
+import androidx.work.CoroutineWorker
+import androidx.work.ListenableWorker.Result.Success
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkInfo.State.ENQUEUED
+import androidx.work.WorkInfo.State.RUNNING
+import androidx.work.WorkerParameters
+import androidx.work.await
+import androidx.work.impl.model.WorkSpec
+import androidx.work.impl.testutils.TrackingWorkerFactory
+import androidx.work.testutils.TestEnv
+import androidx.work.worker.CompletableWorker
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class WorkerWrapperTestKt {
+    val factory = TrackingWorkerFactory()
+    val configuration = Configuration.Builder().setWorkerFactory(factory).build()
+    val testEnv = TestEnv(configuration)
+
+    @Test
+    fun testWorkerWrapper_handlesWorkSpecDeletion() = runBlocking {
+        val workRequest = OneTimeWorkRequest.from(CompletableWorker::class.java)
+        testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+        val workerWrapper = WorkerWrapper(workRequest.workSpec)
+        testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+        val completableWorker = factory.await(workRequest.id) as CompletableWorker
+        testEnv.db.workSpecDao().delete(workRequest.stringId)
+        completableWorker.result.complete(Success())
+        assertThat(workerWrapper.future.await()).isFalse()
+        assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isNull()
+    }
+
+    @Test
+    fun testRunning() = runBlocking {
+        val workRequest = OneTimeWorkRequest.from(DoWorkAwareWorker::class.java)
+        testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+        val workerWrapper = WorkerWrapper(workRequest.workSpec)
+        testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+        val worker = factory.await(workRequest.id) as DoWorkAwareWorker
+        worker.doWork.await()
+        assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isEqualTo(RUNNING)
+        worker.result.complete(Success())
+        assertThat(workerWrapper.future.await()).isFalse()
+    }
+
+    @Test
+    fun testInterruptionRunning() = runBlocking {
+        val workRequest = OneTimeWorkRequest.from(DoWorkAwareWorker::class.java)
+        testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+        val workerWrapper = WorkerWrapper(workRequest.workSpec)
+        testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+        val worker = factory.await(workRequest.id) as DoWorkAwareWorker
+        worker.doWork.await()
+        workerWrapper.interrupt(0)
+        assertThat(workerWrapper.future.await()).isTrue()
+        assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isEqualTo(ENQUEUED)
+    }
+
+    @Test
+    fun testInterruptionPreStartWork() = runBlocking {
+        val workRequest = OneTimeWorkRequest.from(CompletableWorker::class.java)
+        testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+        val workerWrapper = WorkerWrapper(workRequest.workSpec)
+        // gonna block main thread, so startWork() can't be called.
+        val mainThreadBlocker = CountDownLatch(1)
+        testEnv.taskExecutor.mainThreadExecutor.execute {
+            mainThreadBlocker.await()
+            workerWrapper.interrupt(0)
+        }
+        testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+        factory.await(workRequest.id)
+        // worker is created, but can't start work because main thread is blocked
+        // this call will unblock main thread, but interrupt worker
+        mainThreadBlocker.countDown()
+        assertThat(workerWrapper.future.await()).isTrue()
+        assertThat(testEnv.db.workSpecDao().getState(workRequest.stringId)).isEqualTo(ENQUEUED)
+    }
+
+    @Test
+    fun testWorker_getsRunAttemptCount() = runBlocking<Unit> {
+        val workRequest = OneTimeWorkRequest.Builder(CompletableWorker::class.java)
+            .setInitialRunAttemptCount(10).build()
+
+        testEnv.db.workSpecDao().insertWorkSpec(workRequest.workSpec)
+        val workerWrapper = WorkerWrapper(workRequest.workSpec)
+        testEnv.taskExecutor.serialTaskExecutor.execute(workerWrapper)
+        val worker = factory.await(workRequest.id) as CompletableWorker
+        assertThat(worker.runAttemptCount).isEqualTo(10)
+        worker.result.complete(Success())
+    }
+
+    fun WorkerWrapper(spec: WorkSpec) = WorkerWrapper.Builder(
+        testEnv.context, configuration, testEnv.taskExecutor,
+        NoOpForegroundProcessor, testEnv.db, spec, emptyList()
+    ).build()
+}
+
+class DoWorkAwareWorker(
+    appContext: Context,
+    params: WorkerParameters
+) : CoroutineWorker(appContext, params) {
+    val doWork = CompletableDeferred<Unit>()
+    val result = CompletableDeferred<Result>()
+    override suspend fun doWork(): Result {
+        doWork.complete(Unit)
+        return result.await()
+    }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt
new file mode 100644
index 0000000..1fd0a4bf
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.work.impl.background
+
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.work.Configuration
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.testutils.TestOverrideClock
+import androidx.work.impl.utils.PreferenceUtils
+import androidx.work.testutils.GreedyScheduler
+import androidx.work.testutils.TestEnv
+import androidx.work.testutils.WorkManager
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class WorkManagerImplTestKt {
+    val clock = TestOverrideClock()
+    val configuration =
+        Configuration.Builder().setClock(clock)
+            .setTaskExecutor(Executors.newSingleThreadExecutor()).build()
+    val env = TestEnv(configuration)
+    val taskExecutor = env.taskExecutor
+    val trackers = Trackers(context = env.context, taskExecutor = env.taskExecutor)
+    val workManager = WorkManager(env, listOf(GreedyScheduler(env, trackers)), trackers)
+
+    @Test
+    fun testCancelAllWork_updatesLastCancelAllTimeLiveData() {
+        clock.currentTimeMillis = 30
+        val preferenceUtils = PreferenceUtils(env.db)
+        preferenceUtils.lastCancelAllTimeMillis = 0L
+
+        val testLifecycleOwner = TestLifecycleOwner()
+        val cancelAllTimeLiveData = workManager.lastCancelAllTimeMillisLiveData
+        val firstValueLatch = CountDownLatch(1)
+        val secondValueLatch = CountDownLatch(1)
+        var firstCancelAll = -1L
+        var secondCancelAll = -1L
+        var counter = 0
+        env.taskExecutor.mainThreadExecutor.execute {
+            cancelAllTimeLiveData.observe(testLifecycleOwner) {
+                if (counter == 0) {
+                    firstCancelAll = it
+                    firstValueLatch.countDown()
+                } else {
+                    secondCancelAll = it
+                    secondValueLatch.countDown()
+                }
+                counter++
+            }
+        }
+
+        firstValueLatch.await()
+        assertThat(firstCancelAll).isEqualTo(0)
+        clock.currentTimeMillis = 50
+        workManager.cancelAllWork()
+        secondValueLatch.await()
+        assertThat(secondCancelAll).isEqualTo(50)
+    }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/testutils/TestEnv.kt b/work/work-runtime/src/androidTest/java/androidx/work/testutils/TestEnv.kt
new file mode 100644
index 0000000..15288f2
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/testutils/TestEnv.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.work.testutils
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.work.Configuration
+import androidx.work.impl.Processor
+import androidx.work.impl.Scheduler
+import androidx.work.impl.WorkDatabase
+import androidx.work.impl.WorkLauncherImpl
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.background.greedy.GreedyScheduler
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+
+class TestEnv(
+    val configuration: Configuration
+) {
+    val context = ApplicationProvider.getApplicationContext<Context>()
+    val taskExecutor = WorkManagerTaskExecutor(configuration.taskExecutor)
+    val db = WorkDatabase.create(
+        context,
+        taskExecutor.serialTaskExecutor, configuration.clock, true
+    )
+    val processor = Processor(context, configuration, taskExecutor, db)
+}
+
+fun GreedyScheduler(env: TestEnv, trackers: Trackers): GreedyScheduler {
+    val launcher = WorkLauncherImpl(env.processor, env.taskExecutor)
+    return GreedyScheduler(
+        env.context, env.configuration, trackers,
+        env.processor, launcher,
+        env.taskExecutor
+    )
+}
+
+fun WorkManager(env: TestEnv, schedulers: List<Scheduler>, trackers: Trackers) = WorkManagerImpl(
+    env.context, env.configuration, env.taskExecutor, env.db, schedulers,
+    env.processor, trackers
+)
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java b/work/work-runtime/src/androidTest/java/androidx/work/worker/CompletableWorker.kt
similarity index 60%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java
copy to work/work-runtime/src/androidTest/java/androidx/work/worker/CompletableWorker.kt
index 03934ba..39577a9 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/worker/CompletableWorker.kt
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
-package androidx.appactions.interaction.capabilities.core.impl;
+package androidx.work.worker
 
-import androidx.annotation.NonNull;
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.CompletableDeferred
 
-/**
- * A builder of objects of some specific type.
- *
- * @param <T>
- */
-public interface BuilderOf<T> {
-    @NonNull
-    T build();
-}
+class CompletableWorker(
+    appContext: Context,
+    params: WorkerParameters
+) : CoroutineWorker(appContext, params) {
+    val result = CompletableDeferred<Result>()
+    override suspend fun doWork() = result.await()
+}
\ No newline at end of file
diff --git a/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java b/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
index ca861ee..8e31393 100644
--- a/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
+++ b/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerTest.java
@@ -367,6 +367,12 @@
         WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
         assertThat(workInfo.getRunAttemptCount(), is(1));
         assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
+
+        // Can be tried again by setting constraint on TestDriver.
+        mTestDriver.setAllConstraintsMet(request.getId());
+        WorkInfo retryWorkInfo = workManager.getWorkInfoById(request.getId()).get();
+        assertThat(retryWorkInfo.getRunAttemptCount(), is(2));
+        assertThat(retryWorkInfo.getState(), is(WorkInfo.State.ENQUEUED));
     }
 
     @Test
@@ -378,6 +384,13 @@
         WorkInfo workInfo = workManager.getWorkInfoById(request.getId()).get();
         assertThat(workInfo.getRunAttemptCount(), is(1));
         assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
+
+        // Can be tried again by setting constraint on TestDriver.
+        mTestDriver.setPeriodDelayMet(
+                request.getId());
+        WorkInfo retryWorkInfo = workManager.getWorkInfoById(request.getId()).get();
+        assertThat(retryWorkInfo.getRunAttemptCount(), is(2));
+        assertThat(retryWorkInfo.getState(), is(WorkInfo.State.ENQUEUED));
     }
 
     private static OneTimeWorkRequest createWorkRequest() {
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
index 48954ed..5f8cfdc 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
@@ -75,6 +75,11 @@
             }
         }
         toSchedule.forEach { (spec, state) ->
+            if (executorsMode != ExecutorsMode.USE_TIME_BASED_SCHEDULING) {
+                if (spec.isBackedOff && spec.calculateNextRunTime() > clock.currentTimeMillis()) {
+                    return@forEach
+                }
+            }
             maybeScheduleInternal(spec, state)
         }
     }
@@ -168,9 +173,6 @@
                 delayedWorkTracker.schedule(spec, spec.calculateNextRunTime())
             }
         } else {
-            if (spec.isBackedOff && spec.calculateNextRunTime() > clock.currentTimeMillis()) {
-                return
-            }
             if (isRunnableInternalState(spec, state)) {
                 workDatabase.rewindLastEnqueueTimeIfNecessary(spec.id, clock)
                 launcher.startWork(generateStartStopToken(spec, generationalId))