Merge "Migrate PreparedStatementWriter and PreparedQueryAdapter to XPoet" into androidx-main
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
index 65be75b..ef20bed 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
@@ -49,6 +49,7 @@
 import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
 import org.jetbrains.uast.UAnnotated
 import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UAnonymousClass
 import org.jetbrains.uast.UArrayAccessExpression
 import org.jetbrains.uast.UBinaryExpression
 import org.jetbrains.uast.UCallExpression
@@ -638,16 +639,24 @@
         val elementLabel = when (element) {
             is UMethod -> "'${element.name}'"
             is UClass -> "containing class '${element.name}'"
+            is UAnonymousClass -> "containing anonymous class"
             else -> throw IllegalArgumentException("Unsupported element type")
         }
 
+        // If the element has a modifier list, e.g. not an anonymous class or lambda, then place the
+        // insertion point at the beginning of the modifiers list. This ensures that we don't insert
+        // the annotation in an invalid position, such as after the "public" or "fun" keywords. We
+        // also don't want to place it on the element range itself, since that would place it before
+        // the comments.
+        val elementLocation = context.getLocation(element.modifierList ?: element as UElement)
+
         return fix()
-            .name("Add '$annotation' annotation to $elementLabel")
             .replace()
-            .range(context.getLocation(element as UElement))
+            .name("Add '$annotation' annotation to $elementLabel")
+            .range(elementLocation)
             .beginning()
-            .with("$annotation ")
             .shortenNames()
+            .with("$annotation ")
             .build()
     }
 
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
index 2344e4d..167577e 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
@@ -24,7 +24,6 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles.xml
 import com.android.tools.lint.checks.infrastructure.TestLintResult
 import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -74,11 +73,11 @@
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) int getDateUnsafe() {
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @ExperimentalDateTime getDateUnsafe() {
++     @ExperimentalDateTime int getDateUnsafe() {
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
@@ -86,35 +85,35 @@
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) int getDateUnsafe() {
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @ExperimentalDateTime getDateUnsafe() {
++     @ExperimentalDateTime int getDateUnsafe() {
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
 + @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
--     int getDateExperimentalLocationUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) getDateExperimentalLocationUnsafe() {
+@@ -50 +50
+-     @ExperimentalDateTime
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
--     int getDateExperimentalLocationUnsafe() {
-+     int @ExperimentalLocation getDateExperimentalLocationUnsafe() {
+@@ -50 +50
+-     @ExperimentalDateTime
++     @ExperimentalLocation @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
 + @ExperimentalLocation @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
--     int getDateExperimentalLocationUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) getDateExperimentalLocationUnsafe() {
+@@ -50 +50
+-     @ExperimentalDateTime
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -51 +51
--     int getDateExperimentalLocationUnsafe() {
-+     int @ExperimentalLocation getDateExperimentalLocationUnsafe() {
+@@ -50 +50
+-     @ExperimentalDateTime
++     @ExperimentalLocation @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
@@ -190,49 +189,49 @@
 
         val expectedFix = """
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -28 +28
--     fun getDateUnsafe(): Int {
-+     fun @androidx.annotation.OptIn(ExperimentalDateTime::class) getDateUnsafe(): Int {
+@@ -1 +1
+- /*
++ @androidx.annotation.OptIn(ExperimentalDateTime::class) /*
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
-@@ -28 +28
--     fun getDateUnsafe(): Int {
-+     fun @ExperimentalDateTime getDateUnsafe(): Int {
+@@ -1 +1
+- /*
++ @ExperimentalDateTime /*
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromKt':
 @@ -23 +23
 - @Suppress("unused", "MemberVisibilityCanBePrivate")
 + @ExperimentalDateTime @Suppress("unused", "MemberVisibilityCanBePrivate")
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -28 +28
--     fun getDateUnsafe(): Int {
-+     fun @androidx.annotation.OptIn(ExperimentalDateTime::class) getDateUnsafe(): Int {
+@@ -1 +1
+- /*
++ @androidx.annotation.OptIn(ExperimentalDateTime::class) /*
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
-@@ -28 +28
--     fun getDateUnsafe(): Int {
-+     fun @ExperimentalDateTime getDateUnsafe(): Int {
+@@ -1 +1
+- /*
++ @ExperimentalDateTime /*
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromKt':
 @@ -23 +23
 - @Suppress("unused", "MemberVisibilityCanBePrivate")
 + @ExperimentalDateTime @Suppress("unused", "MemberVisibilityCanBePrivate")
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -55 +55
--     fun getDateExperimentalLocationUnsafe(): Int {
-+     fun @androidx.annotation.OptIn(ExperimentalLocation::class) getDateExperimentalLocationUnsafe(): Int {
+@@ -54 +54
+-     @ExperimentalDateTime
++     @androidx.annotation.OptIn(ExperimentalLocation::class) @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -55 +55
--     fun getDateExperimentalLocationUnsafe(): Int {
-+     fun @ExperimentalLocation getDateExperimentalLocationUnsafe(): Int {
+@@ -54 +54
+-     @ExperimentalDateTime
++     @ExperimentalLocation @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromKt':
 @@ -23 +23
 - @Suppress("unused", "MemberVisibilityCanBePrivate")
 + @ExperimentalLocation @Suppress("unused", "MemberVisibilityCanBePrivate")
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -55 +55
--     fun getDateExperimentalLocationUnsafe(): Int {
-+     fun @androidx.annotation.OptIn(ExperimentalLocation::class) getDateExperimentalLocationUnsafe(): Int {
+@@ -54 +54
+-     @ExperimentalDateTime
++     @androidx.annotation.OptIn(ExperimentalLocation::class) @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -55 +55
--     fun getDateExperimentalLocationUnsafe(): Int {
-+     fun @ExperimentalLocation getDateExperimentalLocationUnsafe(): Int {
+@@ -54 +54
+-     @ExperimentalDateTime
++     @ExperimentalLocation @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromKt':
 @@ -23 +23
 - @Suppress("unused", "MemberVisibilityCanBePrivate")
@@ -243,7 +242,6 @@
         check(*input).expect(expected).expectFixDiffs(expectedFix)
     }
 
-    @Ignore("b/196881523")
     @Test
     fun useKtExperimentalFromJava() {
         val input = arrayOf(
@@ -289,11 +287,11 @@
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) int getDateUnsafe() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @ExperimentalDateTimeKt getDateUnsafe() {
++     @ExperimentalDateTimeKt int getDateUnsafe() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
@@ -301,35 +299,35 @@
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) int getDateUnsafe() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
 -     int getDateUnsafe() {
-+     int @ExperimentalDateTimeKt getDateUnsafe() {
++     @ExperimentalDateTimeKt int getDateUnsafe() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
 + @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -52 +52
--     int getDateExperimentalLocationUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) getDateExperimentalLocationUnsafe() {
+@@ -51 +51
+-     @ExperimentalDateTimeKt
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -52 +52
--     int getDateExperimentalLocationUnsafe() {
-+     int @ExperimentalLocationKt getDateExperimentalLocationUnsafe() {
+@@ -51 +51
+-     @ExperimentalDateTimeKt
++     @ExperimentalLocationKt @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
 + @ExperimentalLocationKt @SuppressWarnings({"unused", "WeakerAccess"})
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -52 +52
--     int getDateExperimentalLocationUnsafe() {
-+     int @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) getDateExperimentalLocationUnsafe() {
+@@ -51 +51
+-     @ExperimentalDateTimeKt
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -52 +52
--     int getDateExperimentalLocationUnsafe() {
-+     int @ExperimentalLocationKt getDateExperimentalLocationUnsafe() {
+@@ -51 +51
+-     @ExperimentalDateTimeKt
++     @ExperimentalLocationKt @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
@@ -337,11 +335,11 @@
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
 -     void regressionTestStaticUsage() {
-+     void @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) regressionTestStaticUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestStaticUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
 -     void regressionTestStaticUsage() {
-+     void @ExperimentalDateTimeKt regressionTestStaticUsage() {
++     @ExperimentalDateTimeKt void regressionTestStaticUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
@@ -349,11 +347,11 @@
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
 -     void regressionTestStaticUsage() {
-+     void @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) regressionTestStaticUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestStaticUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
 -     void regressionTestStaticUsage() {
-+     void @ExperimentalDateTimeKt regressionTestStaticUsage() {
++     @ExperimentalDateTimeKt void regressionTestStaticUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
@@ -361,11 +359,11 @@
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
 -     void regressionTestInlineUsage() {
-+     void @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) regressionTestInlineUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestInlineUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
 -     void regressionTestInlineUsage() {
-+     void @ExperimentalDateTimeKt regressionTestInlineUsage() {
++     @ExperimentalDateTimeKt void regressionTestInlineUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
@@ -373,11 +371,11 @@
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
 -     void regressionTestInlineUsage() {
-+     void @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) regressionTestInlineUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) void regressionTestInlineUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
 -     void regressionTestInlineUsage() {
-+     void @ExperimentalDateTime regressionTestInlineUsage() {
++     @ExperimentalDateTime void regressionTestInlineUsage() {
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
 - @SuppressWarnings({"unused", "WeakerAccess"})
@@ -452,7 +450,7 @@
          */
         val ANDROIDX_EXPERIMENTAL_KT: TestFile = kotlin(
             """
-            package androidx.annotation.experimental;
+            package androidx.annotation.experimental
 
             import kotlin.annotation.Retention
             import kotlin.annotation.Target
@@ -478,7 +476,7 @@
          */
         val ANDROIDX_USE_EXPERIMENTAL_KT: TestFile = kotlin(
             """
-            package androidx.annotation.experimental;
+            package androidx.annotation.experimental
 
             import kotlin.annotation.Retention
             import kotlin.annotation.Target
diff --git a/benchmark/benchmark-darwin-gradle-plugin/README.md b/benchmark/benchmark-darwin-gradle-plugin/README.md
new file mode 100644
index 0000000..3fce72d
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/README.md
@@ -0,0 +1,70 @@
+# Introduction
+
+A Gradle Plugin to build and generate benchmarking results for KMP iOS benchmarks.
+
+* Generates Skia Dashboard compatible results.
+* Automatically generates the XCode project, and runs benchmarks on a target device running iOS
+  or macOS (simulator or physical devices).
+
+# Usage
+
+A KMP project needs to do something like:
+
+```groovy
+plugins {
+    id("androidx.benchmark.darwin")
+}
+```
+
+and then it can use the `darwinBenchmark` block like so:
+
+```groovy
+darwinBenchmark {
+    // XCodegen Schema YAML
+    xcodeGenConfigFile = project.rootProject.file(
+            "benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml"
+    )
+    // XCode project name
+    xcodeProjectName = "benchmark-darwin-samples-xcode"
+    // iOS app scheme
+    scheme = "testapp-ios"
+    // ios 13, 15.2
+    destination = "id=7F61C467-4E4A-437C-B6EF-026FEEF3904C"
+    // The XCFrameworkConfig name
+    xcFrameworkConfig = "AndroidXDarwinSampleBenchmarks"
+}
+```
+
+Example metrics look like:
+
+```json
+{
+  "key": {
+    "testDescription": "Allocate an ArrayList of size 1000",
+    "metricName": "Memory Peak Physical",
+    "metricIdentifier": "com.apple.dt.XCTMetric_Memory.physical_peak",
+    "polarity": "prefers smaller",
+    "units": "kB"
+  },
+  "measurements": {
+    "stat": [
+      {
+        "value": "min",
+        "measurement": 0.0
+      },
+      {
+        "value": "median",
+        "measurement": 0.0
+      },
+      {
+        "value": "max",
+        "measurement": 0.0
+      },
+      {
+        "value": "stddev",
+        "measurement": 0.0
+      }
+    ]
+  }
+}
+```
diff --git a/benchmark/benchmark-darwin-gradle-plugin/build.gradle b/benchmark/benchmark-darwin-gradle-plugin/build.gradle
new file mode 100644
index 0000000..42825133
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/build.gradle
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+import androidx.build.LibraryType
+import androidx.build.SdkResourceGenerator
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+    id("java-gradle-plugin")
+}
+
+dependencies {
+    api(gradleApi())
+    implementation(libs.gson)
+    implementation(libs.apacheCommonsMath)
+    testImplementation(gradleTestKit())
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
+}
+
+SdkResourceGenerator.generateForHostTest(project)
+
+gradlePlugin {
+    plugins {
+        darwinBenchmark {
+            id = "androidx.benchmark.darwin"
+            implementationClass = "androidx.benchmark.darwin.gradle.DarwinBenchmarkPlugin"
+        }
+    }
+}
+
+androidx {
+    name = "AndroidX Benchmarks - Darwin Gradle Plugin"
+    type = LibraryType.GRADLE_PLUGIN
+    mavenGroup = LibraryGroups.BENCHMARK
+    inceptionYear = "2022"
+    description = "AndroidX Benchmarks - Darwin Gradle Plugin"
+}
+
+validatePlugins {
+    enableStricterValidation = true
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt
new file mode 100644
index 0000000..5974f14
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPlugin.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.benchmark.darwin.gradle
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * The Darwin benchmark plugin that helps run KMP benchmarks on iOS devices, and extracts benchmark
+ * results in `json`.
+ */
+class DarwinBenchmarkPlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        val extension =
+            project.extensions.create("darwinBenchmark", DarwinBenchmarkPluginExtension::class.java)
+
+        val xcodeProjectPath = extension.xcodeProjectName.flatMap { name ->
+            project.layout.buildDirectory.dir("$name.xcodeproj")
+        }
+
+        val xcResultPath = extension.xcodeProjectName.flatMap { name ->
+            project.layout.buildDirectory.dir("$name.xcresult")
+        }
+
+        val generateXCodeProjectTask = project.tasks.register(
+            GENERATE_XCODE_PROJECT_TASK, GenerateXCodeProjectTask::class.java
+        ) {
+            it.yamlFile.set(extension.xcodeGenConfigFile)
+            it.projectName.set(extension.xcodeProjectName)
+            it.xcProjectPath.set(xcodeProjectPath)
+            it.infoPlistPath.set(project.layout.buildDirectory.file("Info.plist"))
+        }
+
+        val runDarwinBenchmarks = project.tasks.register(
+            RUN_DARWIN_BENCHMARKS_TASK, RunDarwinBenchmarksTask::class.java
+        ) {
+            it.xcodeProjectPath.set(generateXCodeProjectTask.flatMap { task ->
+                task.xcProjectPath
+            })
+            it.destination.set(extension.destination)
+            it.scheme.set(extension.scheme)
+            it.xcResultPath.set(xcResultPath)
+            it.dependsOn("assemble${extension.xcFrameworkConfig.get()}ReleaseXCFramework")
+        }
+
+        project.tasks.register(
+            DARWIN_BENCHMARK_RESULTS_TASK, DarwinBenchmarkResultsTask::class.java
+        ) {
+            it.xcResultPath.set(runDarwinBenchmarks.flatMap { task ->
+                task.xcResultPath
+            })
+            it.outputFile.set(
+                project.layout.buildDirectory.file(
+                    "${extension.xcodeProjectName.get()}-benchmark-result.json"
+                )
+            )
+        }
+    }
+
+    private companion object {
+        const val GENERATE_XCODE_PROJECT_TASK = "generateXCodeProject"
+        const val RUN_DARWIN_BENCHMARKS_TASK = "runDarwinBenchmarks"
+        const val DARWIN_BENCHMARK_RESULTS_TASK = "darwinBenchmarkResults"
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPluginExtension.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPluginExtension.kt
new file mode 100644
index 0000000..83781e8
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkPluginExtension.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.benchmark.darwin.gradle
+
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+
+/**
+ * The [DarwinBenchmarkPlugin] extension.
+ */
+abstract class DarwinBenchmarkPluginExtension {
+    /**
+     * The path to the YAML file that can be used to generate the XCode project.
+     */
+    abstract val xcodeGenConfigFile: RegularFileProperty
+
+    /**
+     * The project name as defined in the YAML file.
+     */
+    abstract val xcodeProjectName: Property<String>
+
+    /**
+     * The project scheme as defined in the YAML file that is the unit test target.
+     */
+    abstract val scheme: Property<String>
+
+    /**
+     * The benchmark device `id`.
+     * This is typically discovered by using `xcrun xctrace list devices`.
+     */
+    abstract val destination: Property<String>
+
+    /**
+     * The name of the `XCFrameworkConfig` defined in the benchmark's module build.gradle.
+     * This is used to derive the name of the `release` task to generate the `XCFramework`.
+     */
+    abstract val xcFrameworkConfig: Property<String>
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt
new file mode 100644
index 0000000..0ecdb27
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/DarwinBenchmarkResultsTask.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.benchmark.darwin.gradle
+
+import androidx.benchmark.darwin.gradle.skia.Metrics
+import androidx.benchmark.darwin.gradle.xcode.Models
+import androidx.benchmark.darwin.gradle.xcode.XcResultParser
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import org.gradle.process.ExecOperations
+
+@CacheableTask
+abstract class DarwinBenchmarkResultsTask @Inject constructor(
+    private val execOperations: ExecOperations
+) : DefaultTask() {
+    @get:InputDirectory
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val xcResultPath: DirectoryProperty
+
+    @get:OutputFile
+    abstract val outputFile: RegularFileProperty
+
+    @TaskAction
+    fun benchmarkResults() {
+        val xcResultFile = xcResultPath.get().asFile
+        val parser = XcResultParser(xcResultFile) { args ->
+            val output = ByteArrayOutputStream()
+            val result = execOperations.exec { spec ->
+                spec.commandLine = args
+                spec.standardOutput = output
+            }
+            result.assertNormalExitValue()
+            output.use {
+                val input = ByteArrayInputStream(output.toByteArray())
+                input.use {
+                    input.reader().readText()
+                }
+            }
+        }
+        val (record, summaries) = parser.parseResults()
+        val metrics = Metrics.buildMetrics(record, summaries)
+        val output = Models.gsonBuilder()
+            .setPrettyPrinting()
+            .create()
+            .toJson(metrics)
+
+        outputFile.get().asFile.writeText(output)
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt
new file mode 100644
index 0000000..3dec4f1
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/GenerateXCodeProjectTask.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.benchmark.darwin.gradle
+
+import java.io.File
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import org.gradle.process.ExecOperations
+
+@CacheableTask
+abstract class GenerateXCodeProjectTask @Inject constructor(
+    private val execOperations: ExecOperations
+) : DefaultTask() {
+
+    @get:InputFile
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val yamlFile: RegularFileProperty
+
+    @get:Input
+    abstract val projectName: Property<String>
+
+    @get:OutputFile
+    abstract val infoPlistPath: RegularFileProperty
+
+    @get:OutputDirectory
+    abstract val xcProjectPath: DirectoryProperty
+
+    @TaskAction
+    fun buildXCodeProject() {
+        val outputFile = xcProjectPath.get().asFile
+        if (outputFile.exists()) {
+            require(outputFile.deleteRecursively()) {
+                "Unable to delete xcode project $outputFile"
+            }
+        }
+        val args = listOf(
+            "xcodegen",
+            "--spec",
+            yamlFile.get().asFile.absolutePath,
+            "--project",
+            outputFile.parent
+        )
+        val result = execOperations.exec { spec ->
+            spec.commandLine = args
+        }
+        result.assertNormalExitValue()
+        require(outputFile.exists()) {
+            "Project $projectName must match the `name` declaration in $yamlFile"
+        }
+        copyProjectMetadata()
+    }
+
+    private fun copyProjectMetadata() {
+        val sourceFile = File(yamlFile.get().asFile.parent, "Info.plist")
+        require(sourceFile.exists())
+        val targetFile = infoPlistPath.get().asFile
+        val copied = sourceFile.copyRecursively(targetFile, overwrite = true)
+        require(copied) {
+            "Unable to copy $sourceFile to $targetFile"
+        }
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt
new file mode 100644
index 0000000..55aec880
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/RunDarwinBenchmarksTask.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.benchmark.darwin.gradle
+
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import org.gradle.process.ExecOperations
+
+@CacheableTask
+abstract class RunDarwinBenchmarksTask @Inject constructor(
+    private val execOperations: ExecOperations
+) : DefaultTask() {
+    @get:InputDirectory
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val xcodeProjectPath: DirectoryProperty
+
+    @get:Input
+    abstract val destination: Property<String>
+
+    @get:Input
+    abstract val scheme: Property<String>
+
+    @get:OutputDirectory
+    abstract val xcResultPath: DirectoryProperty
+
+    @TaskAction
+    fun runBenchmarks() {
+        requireXcodeBuild()
+        val xcodeProject = xcodeProjectPath.get().asFile
+        val xcResultFile = xcResultPath.get().asFile
+        if (xcResultFile.exists()) {
+            xcResultFile.deleteRecursively()
+        }
+        val args = listOf(
+            "xcodebuild",
+            "test",
+            "-project", xcodeProject.absolutePath.toString(),
+            "-scheme", scheme.get(),
+            "-destination", destination.get(),
+            "-resultBundlePath", xcResultFile.absolutePath,
+        )
+        logger.info("Command : ${args.joinToString(" ")}")
+        val result = execOperations.exec { spec ->
+            spec.commandLine = args
+        }
+        result.assertNormalExitValue()
+    }
+
+    private fun requireXcodeBuild() {
+        val result = execOperations.exec { spec ->
+            spec.commandLine = listOf("which", "xcodebuild")
+            // Ignore exit value here to return a better exception message
+            spec.isIgnoreExitValue = true
+        }
+        require(result.exitValue == 0) {
+            "xcodebuild is missing on this machine."
+        }
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/skia/Metric.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/skia/Metric.kt
new file mode 100644
index 0000000..6bed2de
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/skia/Metric.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.benchmark.darwin.gradle.skia
+
+import androidx.benchmark.darwin.gradle.xcode.ActionTestSummary
+import androidx.benchmark.darwin.gradle.xcode.ActionsInvocationRecord
+import com.google.gson.annotations.SerializedName
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics
+
+/**
+ * https://skia.googlesource.com/buildbot/+/refs/heads/main/perf/FORMAT.md
+ */
+
+data class Stat(val value: String, val measurement: Double)
+data class Measurements(
+    @SerializedName("stat")
+    val stats: List<Stat>
+)
+
+data class Metric(val key: Map<String, String>, val measurements: Measurements)
+data class Metrics(val key: Map<String, String>, val results: List<Metric>) {
+    companion object {
+        fun buildMetrics(
+            record: ActionsInvocationRecord,
+            summaries: List<ActionTestSummary>
+        ): Metrics {
+            require(record.actions.actionRecords.isNotEmpty())
+            val runDestination = record.actions.actionRecords.first().runDestination
+            val metricsKeys = mapOf(
+                "destination" to runDestination.displayName.value,
+                "arch" to runDestination.targetArchitecture.value,
+                "targetSdk" to runDestination.targetSDKRecord.identifier.value,
+                "identifier" to runDestination.localComputerRecord.identifier.value,
+                "modelName" to runDestination.localComputerRecord.modelName.value,
+                "modelCode" to runDestination.localComputerRecord.modelCode.value
+            )
+            val results = summaries.flatMap { it.toMetrics() }
+            return Metrics(metricsKeys, results)
+        }
+
+        private fun ActionTestSummary.toMetrics(): List<Metric> {
+            return performanceMetrics.values.map { metricSummary ->
+                val key = mutableMapOf(
+                    "testDescription" to (title() ?: "No description"),
+                    "metricName" to metricSummary.displayName.value,
+                    "metricIdentifier" to metricSummary.identifier.value,
+                    "polarity" to metricSummary.polarity.value,
+                    "units" to metricSummary.unitOfMeasurement.value,
+                )
+                val statistics = DescriptiveStatistics()
+                metricSummary.measurements.values.forEach {
+                    statistics.addValue(it.value)
+                }
+                val min = Stat("min", statistics.min)
+                // The 50th percentile is the median
+                val median = Stat("median", statistics.getPercentile(50.0))
+                val max = Stat("max", statistics.max)
+                val deviation = Stat("stddev", statistics.standardDeviation)
+                Metric(key, Measurements(listOf(min, median, max, deviation)))
+            }
+        }
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/Models.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/Models.kt
new file mode 100644
index 0000000..863da27
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/Models.kt
@@ -0,0 +1,341 @@
+/*
+ * 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.benchmark.darwin.gradle.xcode
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.google.gson.JsonDeserializationContext
+import com.google.gson.JsonDeserializer
+import com.google.gson.JsonElement
+import com.google.gson.annotations.SerializedName
+import java.lang.reflect.Type
+
+// Rather unfortunate that all values types are wrapped in a property bag containing a single
+// key called "_value". This is as per the JSON schema used by `xcresulttool`.
+
+data class StringTypedValue(
+    @SerializedName("_value") val value: String
+)
+
+data class IntTypedValue(
+    @SerializedName("_value") val value: Int
+)
+
+data class DoubleTypedValue(
+    @SerializedName("_value")
+    val value: Double
+)
+
+data class BooleanTypedValue(
+    @SerializedName("_value")
+    val value: Boolean
+)
+
+data class Metrics(
+    private val testsCount: IntTypedValue
+) {
+    fun size(): Int {
+        return testsCount.value
+    }
+}
+
+data class TestReference(
+    val id: StringTypedValue,
+)
+
+data class ActionResult(
+    private val status: StringTypedValue,
+    @SerializedName("testsRef") private val testsReference: TestReference
+) {
+    fun isSuccessful(): Boolean {
+        return status.value == "succeeded"
+    }
+
+    fun testsReferenceId(): String {
+        return testsReference.id.value
+    }
+}
+
+data class ActionPlatformRecord(
+    val identifier: StringTypedValue,
+    val userDescription: StringTypedValue,
+)
+
+data class ActionSDKRecord(
+    val name: StringTypedValue,
+    val identifier: StringTypedValue,
+    val operatingSystemVersion: StringTypedValue,
+    val isInternal: BooleanTypedValue,
+)
+
+data class ActionDeviceRecord(
+    val name: StringTypedValue,
+    val isConcreteDevice: BooleanTypedValue,
+    val operatingSystemVersion: StringTypedValue,
+    val operatingSystemVersionWithBuildNumber: StringTypedValue,
+    val nativeArchitecture: StringTypedValue,
+    val modelName: StringTypedValue,
+    val modelCode: StringTypedValue,
+    val identifier: StringTypedValue,
+    val isWireless: BooleanTypedValue,
+    val cpuKind: StringTypedValue,
+    val cpuCount: IntTypedValue?,
+    val cpuSpeedInMHz: IntTypedValue?,
+    val busSpeedInMHz: IntTypedValue?,
+    val ramSizeInMegabytes: IntTypedValue?,
+    val physicalCPUCoresPerPackage: IntTypedValue?,
+    val logicalCPUCoresPerPackage: IntTypedValue?,
+    val platformRecord: ActionPlatformRecord,
+)
+
+data class ActionRunDestinationRecord(
+    val displayName: StringTypedValue,
+    val targetArchitecture: StringTypedValue,
+    val targetDeviceRecord: ActionDeviceRecord,
+    val localComputerRecord: ActionDeviceRecord,
+    val targetSDKRecord: ActionSDKRecord,
+)
+
+data class ActionRecord(
+    val actionResult: ActionResult,
+    val runDestination: ActionRunDestinationRecord
+)
+
+data class Actions(
+    @SerializedName("_values") val actionRecords: List<ActionRecord>
+) {
+    fun testReferences(): List<String> {
+        return actionRecords.asSequence()
+            .map { it.actionResult.testsReferenceId() }
+            .toList()
+    }
+
+    fun isSuccessful(): Boolean {
+        return actionRecords.all { it.actionResult.isSuccessful() }
+    }
+}
+
+data class ActionsInvocationRecord(
+    val metrics: Metrics,
+    val actions: Actions
+)
+
+// Test Plan Summaries
+
+data class ActionTestMetadataSummary(
+    val id: StringTypedValue
+)
+
+data class TypeDefinition(
+    @SerializedName("_name")
+    val name: String
+)
+
+// Marker interface
+sealed interface ActionsTestSummaryGroupOrMeta
+
+data class ActionsTestSummaryGroupOrMetaArray(
+    @SerializedName("_values")
+    val values: List<ActionsTestSummaryGroupOrMeta>
+)
+
+data class ActionTestSummaryGroup(
+    val duration: DoubleTypedValue,
+    val identifier: StringTypedValue,
+    val name: StringTypedValue,
+    @SerializedName("subtests")
+    val subTests: ActionsTestSummaryGroupOrMetaArray
+) : ActionsTestSummaryGroupOrMeta {
+    fun summaries(): List<ActionTestSummaryMeta> {
+        return buildSummaries(mutableListOf(), this)
+    }
+
+    companion object {
+        internal fun buildSummaries(
+            summaries: MutableList<ActionTestSummaryMeta>,
+            group: ActionTestSummaryGroup
+        ): MutableList<ActionTestSummaryMeta> {
+            for (subTest in group.subTests.values) {
+                when (subTest) {
+                    is ActionTestSummaryGroup -> buildSummaries(summaries, subTest)
+                    is ActionTestSummaryMeta -> summaries += subTest
+                }
+            }
+            return summaries
+        }
+    }
+}
+
+data class ActionTestSummaryMeta(
+    val duration: DoubleTypedValue,
+    val identifier: StringTypedValue,
+    val name: StringTypedValue,
+    val summaryRef: ActionTestMetadataSummary,
+    val testStatus: StringTypedValue
+) : ActionsTestSummaryGroupOrMeta {
+    fun isSuccessful(): Boolean {
+        return testStatus.value == "Success"
+    }
+
+    fun summaryRefId(): String {
+        return summaryRef.id.value
+    }
+}
+
+class ActionTestSummaryDeserializer : JsonDeserializer<ActionsTestSummaryGroupOrMeta> {
+    override fun deserialize(
+        jsonElement: JsonElement,
+        typeOfT: Type,
+        context: JsonDeserializationContext
+    ): ActionsTestSummaryGroupOrMeta {
+        return if (checkType(jsonElement, ACTION_TEST_SUMMARY_GROUP)) {
+            val adapter = Models.gson().getAdapter(ActionTestSummaryGroup::class.java)
+            adapter.fromJson(jsonElement.toString())
+        } else if (checkType(jsonElement, ACTION_TEST_SUMMARY_META)) {
+            val adapter = Models.gson().getAdapter(ActionTestSummaryMeta::class.java)
+            adapter.fromJson(jsonElement.toString())
+        } else {
+            reportException(jsonElement)
+        }
+    }
+
+    private fun reportException(jsonElement: JsonElement): Nothing {
+        throw IllegalStateException("Unable to deserialize to ActionTestSummary ($jsonElement)")
+    }
+
+    companion object {
+        private const val TYPE = "_type"
+        private const val ACTION_TEST_SUMMARY_GROUP = "ActionTestSummaryGroup"
+        private const val ACTION_TEST_SUMMARY_META = "ActionTestMetadata"
+
+        internal fun checkType(jsonElement: JsonElement, name: String): Boolean {
+            if (!jsonElement.isJsonObject) return false
+            val json = jsonElement.asJsonObject
+            val jsonType: JsonElement? = json.get(TYPE)
+            if (jsonType != null && jsonType.isJsonObject) {
+                val adapter = Models.gson().getAdapter(TypeDefinition::class.java)
+                val type = adapter.fromJson(jsonType.toString())
+                return type.name == name
+            }
+            return false
+        }
+    }
+}
+
+data class ActionTestSummaryGroupArray(
+    @SerializedName("_values")
+    val values: List<ActionTestSummaryGroup>
+)
+
+data class ActionTestableSummary(
+    val diagnosticsDirectoryName: StringTypedValue,
+    val name: StringTypedValue,
+    val projectRelativePath: StringTypedValue,
+    val targetName: StringTypedValue,
+    val tests: ActionTestSummaryGroupArray
+)
+
+data class ActionTestableSummaryArray(
+    @SerializedName("_values")
+    val values: List<ActionTestableSummary>
+)
+
+data class ActionTestPlanRunSummary(
+    val testableSummaries: ActionTestableSummaryArray
+)
+
+data class ActionTestPlanSummaryArray(
+    @SerializedName("_values")
+    val values: List<ActionTestPlanRunSummary>
+)
+
+data class ActionTestPlanRunSummaries(
+    val summaries: ActionTestPlanSummaryArray
+) {
+    fun testSummaries(): List<ActionTestSummaryMeta> {
+        return summaries.values.flatMap { testPlanSummary ->
+            testPlanSummary.testableSummaries.values.flatMap { testableSummary ->
+                testableSummary.tests.values.flatMap { summaryGroup ->
+                    summaryGroup.summaries()
+                }
+            }
+        }
+    }
+}
+
+// Test Metrics
+
+data class ActionTestActivitySummary(
+    val title: StringTypedValue
+)
+
+data class ActionTestActivitySummaryArray(
+    @SerializedName("_values")
+    val values: List<ActionTestActivitySummary>
+) {
+    fun title(): String? {
+        return values.firstOrNull()?.title?.value
+    }
+}
+
+data class MeasurementArray(
+    @SerializedName("_values")
+    val values: List<DoubleTypedValue>
+)
+
+data class ActionTestPerformanceMetricSummary(
+    val displayName: StringTypedValue,
+    val identifier: StringTypedValue,
+    val measurements: MeasurementArray,
+    val polarity: StringTypedValue,
+    val unitOfMeasurement: StringTypedValue,
+)
+
+data class ActionTestPerformanceMetricSummaryArray(
+    @SerializedName("_values")
+    val values: List<ActionTestPerformanceMetricSummary>
+)
+
+data class ActionTestSummary(
+    val activitySummaries: ActionTestActivitySummaryArray,
+    val name: StringTypedValue,
+    val testStatus: StringTypedValue,
+    val performanceMetrics: ActionTestPerformanceMetricSummaryArray
+) {
+    fun isSuccessful(): Boolean {
+        return testStatus.value == "Success"
+    }
+
+    fun title(): String? {
+        return activitySummaries.title()
+    }
+}
+
+object Models {
+    internal fun gsonBuilder(): GsonBuilder {
+        val builder = GsonBuilder()
+        builder.registerTypeAdapter(
+            ActionsTestSummaryGroupOrMeta::class.java,
+            ActionTestSummaryDeserializer()
+        )
+        return builder
+    }
+
+    fun gson(): Gson {
+        return gsonBuilder().create()
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt
new file mode 100644
index 0000000..c0c3c15
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin/androidx/benchmark/darwin/gradle/xcode/XcResultParser.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.benchmark.darwin.gradle.xcode
+
+import java.io.File
+
+/**
+ * Parses benchmark results from the xcresult file.
+ * @param xcResultFile The XCResult output file to parse.
+ * @param commandExecutor An executor that can invoke the `xcrun` and get the results from `stdout`.
+ */
+class XcResultParser(
+    private val xcResultFile: File,
+    private val commandExecutor: (args: List<String>) -> String
+) {
+    fun parseResults(): Pair<ActionsInvocationRecord, List<ActionTestSummary>> {
+        val json = commandExecutor(xcRunCommand())
+        val gson = Models.gson()
+        val record = gson.fromJson(json, ActionsInvocationRecord::class.java)
+        val summaries = record.actions.testReferences().flatMap { testRef ->
+            val summary = commandExecutor(xcRunCommand(testRef))
+            val testPlanSummaries = gson.fromJson(summary, ActionTestPlanRunSummaries::class.java)
+            testPlanSummaries.testSummaries().map { summaryMeta ->
+                val output = commandExecutor(xcRunCommand(summaryMeta.summaryRefId()))
+                val testSummary = gson.fromJson(output, ActionTestSummary::class.java)
+                testSummary
+            }
+        }
+        return record to summaries
+    }
+
+    /**
+     * Builds an `xcrun` command that can parse both the xcresult scaffold, and additionally
+     * traverse nested `plist`s to pull additional benchmark result metadata.
+     */
+    private fun xcRunCommand(id: String? = null): List<String> {
+        val args = mutableListOf(
+            "xcrun",
+            "xcresulttool",
+            "get",
+            "--path",
+            xcResultFile.absolutePath,
+            "--format",
+            "json"
+        )
+
+        if (!id.isNullOrEmpty()) {
+            args += listOf("--id", id)
+        }
+
+        return args
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt
new file mode 100644
index 0000000..663b7ea
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/ModelsTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+import androidx.benchmark.darwin.gradle.xcode.ActionTestPlanRunSummaries
+import androidx.benchmark.darwin.gradle.xcode.ActionTestSummary
+import androidx.benchmark.darwin.gradle.xcode.ActionsInvocationRecord
+import androidx.benchmark.darwin.gradle.xcode.Models
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ModelsTest {
+    @Test
+    fun parseXcResultOutputs() {
+        val json = testData(XCRESULT_OUTPUT_JSON).readText()
+        val gson = Models.gson()
+        val record = gson.fromJson(json, ActionsInvocationRecord::class.java)
+        assertThat(record.actions.testReferences().size).isEqualTo(1)
+        assertThat(record.metrics.size()).isEqualTo(1)
+        assertThat(record.actions.isSuccessful()).isTrue()
+    }
+
+    @Test
+    fun parseTestsReferenceOutput() {
+        val json = testData(XC_TESTS_REFERENCE_OUTPUT_JSON).readText()
+        val gson = Models.gson()
+        val testPlanSummaries = gson.fromJson(json, ActionTestPlanRunSummaries::class.java)
+        val testSummaryMetas = testPlanSummaries.testSummaries()
+        assertThat(testSummaryMetas.size).isEqualTo(1)
+        assertThat(testSummaryMetas[0].summaryRefId()).isNotEmpty()
+        assertThat(testSummaryMetas[0].isSuccessful()).isTrue()
+    }
+
+    @Test
+    fun parseTestOutput() {
+        val json = testData(XC_TEST_OUTPUT_JSON).readText()
+        val gson = Models.gson()
+        val testSummary = gson.fromJson(json, ActionTestSummary::class.java)
+        assertThat(testSummary.title()).isNotEmpty()
+        assertThat(testSummary.isSuccessful()).isTrue()
+    }
+
+    companion object {
+        private const val XCRESULT_OUTPUT_JSON = "xcresult_output.json"
+        private const val XC_TESTS_REFERENCE_OUTPUT_JSON = "tests_reference_output.json"
+        private const val XC_TEST_OUTPUT_JSON = "test_output.json"
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/TestExtensions.kt b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/TestExtensions.kt
new file mode 100644
index 0000000..28003fa
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/TestExtensions.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+import java.io.File
+
+internal fun testData(name: String): File {
+    return File("src/test/test-data", name)
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt
new file mode 100644
index 0000000..00468a9
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/kotlin/XcResultParserTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+import androidx.benchmark.darwin.gradle.skia.Metrics
+import androidx.benchmark.darwin.gradle.xcode.Models
+import androidx.benchmark.darwin.gradle.xcode.XcResultParser
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XcResultParserTest {
+    @Test
+    fun parseXcResultFileTest() {
+        val operatingSystem = System.getProperty("os.name")
+        // Only run this test on an `Mac OS X` machine.
+        assumeTrue(operatingSystem.contains("Mac", ignoreCase = true))
+        val xcResultFile = testData("sample-xcode.xcresult")
+        val parser = XcResultParser(xcResultFile) { args ->
+            val builder = ProcessBuilder(*args.toTypedArray())
+            val process = builder.start()
+            val resultCode = process.waitFor()
+            require(resultCode == 0) {
+                "Process terminated unexpectedly (${args.joinToString(separator = " ")})"
+            }
+            process.inputStream.use {
+                it.reader().readText()
+            }
+        }
+        val (record, summaries) = parser.parseResults()
+        // Usually corresponds to the size of the test suite
+        // In the case of KMP benchmarks, this is always 1 per module.
+        assertThat(record.actions.testReferences().size).isEqualTo(1)
+        assertThat(record.actions.isSuccessful()).isTrue()
+        // Metrics typically correspond to the number of tests
+        assertThat(record.metrics.size()).isEqualTo(2)
+        assertThat(summaries.isNotEmpty()).isTrue()
+        val metrics = Metrics.buildMetrics(record, summaries)
+        val json = Models.gsonBuilder()
+            .setPrettyPrinting()
+            .create()
+            .toJson(metrics)
+        println()
+        println(json)
+        println()
+        assertThat(json).isNotEmpty()
+    }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/README.md b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/README.md
new file mode 100644
index 0000000..6610e6b
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/README.md
@@ -0,0 +1,18 @@
+# Test Data
+
+## [sample-xcode.xcresult](./sample-xcode.xcresult)
+
+This is an example benchmark `xcresult` directory that is the result of running an `xcodebuild`.
+
+An example invocation might look something like:
+
+```bash
+xcodebuild test -project $SRCROOT/benchmark-darwin-samples-xcode.xcodeproj \
+    -scheme testapp-ios \
+    -destination id=7F61C467-4E4A-437C-B6EF-026FEEF3904C \
+    -resultBundlePath $SRCROOT/benchmark-darwin-samples-xcode.xcresult
+```
+
+The `xcresult` output directory stores results in a nested `plist` format. Entities in the top-level
+`plist` file point to other entities stored in other`plist` files (inside the `Data` directory),
+using a unique filename identifier.
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw==
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw==
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~809cv4Ku3dnBcPFN0EL1A1xx_h4MH0THT41Rlz6CEankhzddTnjMa6CWhjt9F69jFsquD4x8Offmms1sFw6EKA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~809cv4Ku3dnBcPFN0EL1A1xx_h4MH0THT41Rlz6CEankhzddTnjMa6CWhjt9F69jFsquD4x8Offmms1sFw6EKA==
new file mode 100644
index 0000000..bf96d3c
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~809cv4Ku3dnBcPFN0EL1A1xx_h4MH0THT41Rlz6CEankhzddTnjMa6CWhjt9F69jFsquD4x8Offmms1sFw6EKA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~BVkwcGZLlAQkY6Iyxs4jrfCad9IriiNRwFgkj-q5CYa3nlIwjU66k7hfhLcnmUHGVk4q8lK8eljDDjEI2_UwrA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~BVkwcGZLlAQkY6Iyxs4jrfCad9IriiNRwFgkj-q5CYa3nlIwjU66k7hfhLcnmUHGVk4q8lK8eljDDjEI2_UwrA==
new file mode 100644
index 0000000..07b9130
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~BVkwcGZLlAQkY6Iyxs4jrfCad9IriiNRwFgkj-q5CYa3nlIwjU66k7hfhLcnmUHGVk4q8lK8eljDDjEI2_UwrA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~NcxF_MrjcLDoh1KWbeGq453YSibqfUcZRlcfxDlh1HVpo6_BqONP2ZB1AiJYY9lYfIGL1g5h3VsbOKnwGLjywQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~NcxF_MrjcLDoh1KWbeGq453YSibqfUcZRlcfxDlh1HVpo6_BqONP2ZB1AiJYY9lYfIGL1g5h3VsbOKnwGLjywQ==
new file mode 100644
index 0000000..13d30be
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~NcxF_MrjcLDoh1KWbeGq453YSibqfUcZRlcfxDlh1HVpo6_BqONP2ZB1AiJYY9lYfIGL1g5h3VsbOKnwGLjywQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~PBlxYYh3AAyWwRJ85oIfJdGUcOuzcjExLhwrde-sNFno45FanCv9KkZmQ_eAJL9BVGKBh78r4OF_Fw2MeiGPYg== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~PBlxYYh3AAyWwRJ85oIfJdGUcOuzcjExLhwrde-sNFno45FanCv9KkZmQ_eAJL9BVGKBh78r4OF_Fw2MeiGPYg==
new file mode 100644
index 0000000..3032bd0
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~PBlxYYh3AAyWwRJ85oIfJdGUcOuzcjExLhwrde-sNFno45FanCv9KkZmQ_eAJL9BVGKBh78r4OF_Fw2MeiGPYg==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~TV3vc3QcCkBeyTI4OZibWTUhnLOa2pj_khWYMDIsFQfzU1CC6gAO1HxXclYQhodcuQjZSw7GGBlpuUT8X0uctA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~TV3vc3QcCkBeyTI4OZibWTUhnLOa2pj_khWYMDIsFQfzU1CC6gAO1HxXclYQhodcuQjZSw7GGBlpuUT8X0uctA==
new file mode 100644
index 0000000..305a73b
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~TV3vc3QcCkBeyTI4OZibWTUhnLOa2pj_khWYMDIsFQfzU1CC6gAO1HxXclYQhodcuQjZSw7GGBlpuUT8X0uctA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~YK5eNAHGX7bQW65jgs2rLhWNoTj7vgF1CBLZ2k12n3svTuVa_whSNkDWJY9V9-QVC_GCh2-DzDDLoIpXvYOToA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~YK5eNAHGX7bQW65jgs2rLhWNoTj7vgF1CBLZ2k12n3svTuVa_whSNkDWJY9V9-QVC_GCh2-DzDDLoIpXvYOToA==
new file mode 100644
index 0000000..9a0adba
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~YK5eNAHGX7bQW65jgs2rLhWNoTj7vgF1CBLZ2k12n3svTuVa_whSNkDWJY9V9-QVC_GCh2-DzDDLoIpXvYOToA==
@@ -0,0 +1 @@
+[{"name":"testmanagerd.log","type":1}]
\ No newline at end of file
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~bO5MkLRUHsU78utGaMM3HVQkRM7RbYps9mAfFre5AyZnGEAQmWmCQMb2RChRtxj7aiaDPHDpQxhAswnGBW3rAA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~bO5MkLRUHsU78utGaMM3HVQkRM7RbYps9mAfFre5AyZnGEAQmWmCQMb2RChRtxj7aiaDPHDpQxhAswnGBW3rAA==
new file mode 100644
index 0000000..52a8bc8
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~bO5MkLRUHsU78utGaMM3HVQkRM7RbYps9mAfFre5AyZnGEAQmWmCQMb2RChRtxj7aiaDPHDpQxhAswnGBW3rAA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~eMJEUpMqij97v52rtaj8rSl-UZJhPmN04NCDD0JZ0cWyFbAHRCbjw5PJqWyqalQuFswnMkB6LvsT3uFLJVGVqw== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~eMJEUpMqij97v52rtaj8rSl-UZJhPmN04NCDD0JZ0cWyFbAHRCbjw5PJqWyqalQuFswnMkB6LvsT3uFLJVGVqw==
new file mode 100644
index 0000000..b385fa3
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~eMJEUpMqij97v52rtaj8rSl-UZJhPmN04NCDD0JZ0cWyFbAHRCbjw5PJqWyqalQuFswnMkB6LvsT3uFLJVGVqw==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~hncU2qbL3W7Jwz-E6zuM7ThWI10rl9EEkPM5cV70-odmasyId-QIR9ZRmIMwYF8DCechev-bgJgPAiYsyImmnQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~hncU2qbL3W7Jwz-E6zuM7ThWI10rl9EEkPM5cV70-odmasyId-QIR9ZRmIMwYF8DCechev-bgJgPAiYsyImmnQ==
new file mode 100644
index 0000000..195b954
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~hncU2qbL3W7Jwz-E6zuM7ThWI10rl9EEkPM5cV70-odmasyId-QIR9ZRmIMwYF8DCechev-bgJgPAiYsyImmnQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~l90yex7j_mtyE5LhZFKn-M7ovFj_tArzqr984SmaEo2RQ3JhValxuXW7UXxzNRGaNamX9MMhtWVxU-wT9DA6Zg== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~l90yex7j_mtyE5LhZFKn-M7ovFj_tArzqr984SmaEo2RQ3JhValxuXW7UXxzNRGaNamX9MMhtWVxU-wT9DA6Zg==
new file mode 100644
index 0000000..88dc22a
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~l90yex7j_mtyE5LhZFKn-M7ovFj_tArzqr984SmaEo2RQ3JhValxuXW7UXxzNRGaNamX9MMhtWVxU-wT9DA6Zg==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~no8uvpmY2pkdGvoMP79k6Q4ikuedatoSuF6vBG0HR8h0GsBvPND_wHS-M8mBpxtX6NzpHV4bKIhwFkxUsd7RXA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~no8uvpmY2pkdGvoMP79k6Q4ikuedatoSuF6vBG0HR8h0GsBvPND_wHS-M8mBpxtX6NzpHV4bKIhwFkxUsd7RXA==
new file mode 100644
index 0000000..c4b8635
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~no8uvpmY2pkdGvoMP79k6Q4ikuedatoSuF6vBG0HR8h0GsBvPND_wHS-M8mBpxtX6NzpHV4bKIhwFkxUsd7RXA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw==
new file mode 100644
index 0000000..47daeba
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~wV6va8rQlQLAcZ8rb5UAf1mKxaJdF9ZDlsizSwQ7FqQynxq_02ZsiipQ63-2YJ3jEODImbyk3FvKgJ2KaOPOPQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~wV6va8rQlQLAcZ8rb5UAf1mKxaJdF9ZDlsizSwQ7FqQynxq_02ZsiipQ63-2YJ3jEODImbyk3FvKgJ2KaOPOPQ==
new file mode 100644
index 0000000..ff4a430
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~wV6va8rQlQLAcZ8rb5UAf1mKxaJdF9ZDlsizSwQ7FqQynxq_02ZsiipQ63-2YJ3jEODImbyk3FvKgJ2KaOPOPQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~wpEwfbWjFv9AR7VY2bJ5g_ndbKMYwUEFGZ1w3MdarhPZE605CCX5XHRoL5cjlzYTdIpHF8aXtcLt6kz2ELrAsQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~wpEwfbWjFv9AR7VY2bJ5g_ndbKMYwUEFGZ1w3MdarhPZE605CCX5XHRoL5cjlzYTdIpHF8aXtcLt6kz2ELrAsQ==
new file mode 100644
index 0000000..70db723
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~wpEwfbWjFv9AR7VY2bJ5g_ndbKMYwUEFGZ1w3MdarhPZE605CCX5XHRoL5cjlzYTdIpHF8aXtcLt6kz2ELrAsQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~z_lmQvK-mq_MbksvJ2oEALgVyhIwk-VirNem09JWgC47IaBCD7JaSPfQeouKa8iSKUchkiKh993EavN5hgc0Mg== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~z_lmQvK-mq_MbksvJ2oEALgVyhIwk-VirNem09JWgC47IaBCD7JaSPfQeouKa8iSKUchkiKh993EavN5hgc0Mg==
new file mode 100644
index 0000000..0d78c58
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/data.0~z_lmQvK-mq_MbksvJ2oEALgVyhIwk-VirNem09JWgC47IaBCD7JaSPfQeouKa8iSKUchkiKh993EavN5hgc0Mg==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~809cv4Ku3dnBcPFN0EL1A1xx_h4MH0THT41Rlz6CEankhzddTnjMa6CWhjt9F69jFsquD4x8Offmms1sFw6EKA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~809cv4Ku3dnBcPFN0EL1A1xx_h4MH0THT41Rlz6CEankhzddTnjMa6CWhjt9F69jFsquD4x8Offmms1sFw6EKA==
new file mode 100644
index 0000000..0ec3518
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~809cv4Ku3dnBcPFN0EL1A1xx_h4MH0THT41Rlz6CEankhzddTnjMa6CWhjt9F69jFsquD4x8Offmms1sFw6EKA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~BVkwcGZLlAQkY6Iyxs4jrfCad9IriiNRwFgkj-q5CYa3nlIwjU66k7hfhLcnmUHGVk4q8lK8eljDDjEI2_UwrA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~BVkwcGZLlAQkY6Iyxs4jrfCad9IriiNRwFgkj-q5CYa3nlIwjU66k7hfhLcnmUHGVk4q8lK8eljDDjEI2_UwrA==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~BVkwcGZLlAQkY6Iyxs4jrfCad9IriiNRwFgkj-q5CYa3nlIwjU66k7hfhLcnmUHGVk4q8lK8eljDDjEI2_UwrA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~NcxF_MrjcLDoh1KWbeGq453YSibqfUcZRlcfxDlh1HVpo6_BqONP2ZB1AiJYY9lYfIGL1g5h3VsbOKnwGLjywQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~NcxF_MrjcLDoh1KWbeGq453YSibqfUcZRlcfxDlh1HVpo6_BqONP2ZB1AiJYY9lYfIGL1g5h3VsbOKnwGLjywQ==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~NcxF_MrjcLDoh1KWbeGq453YSibqfUcZRlcfxDlh1HVpo6_BqONP2ZB1AiJYY9lYfIGL1g5h3VsbOKnwGLjywQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~PBlxYYh3AAyWwRJ85oIfJdGUcOuzcjExLhwrde-sNFno45FanCv9KkZmQ_eAJL9BVGKBh78r4OF_Fw2MeiGPYg== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~PBlxYYh3AAyWwRJ85oIfJdGUcOuzcjExLhwrde-sNFno45FanCv9KkZmQ_eAJL9BVGKBh78r4OF_Fw2MeiGPYg==
new file mode 100644
index 0000000..6fde618
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~PBlxYYh3AAyWwRJ85oIfJdGUcOuzcjExLhwrde-sNFno45FanCv9KkZmQ_eAJL9BVGKBh78r4OF_Fw2MeiGPYg==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~TV3vc3QcCkBeyTI4OZibWTUhnLOa2pj_khWYMDIsFQfzU1CC6gAO1HxXclYQhodcuQjZSw7GGBlpuUT8X0uctA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~TV3vc3QcCkBeyTI4OZibWTUhnLOa2pj_khWYMDIsFQfzU1CC6gAO1HxXclYQhodcuQjZSw7GGBlpuUT8X0uctA==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~TV3vc3QcCkBeyTI4OZibWTUhnLOa2pj_khWYMDIsFQfzU1CC6gAO1HxXclYQhodcuQjZSw7GGBlpuUT8X0uctA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~YK5eNAHGX7bQW65jgs2rLhWNoTj7vgF1CBLZ2k12n3svTuVa_whSNkDWJY9V9-QVC_GCh2-DzDDLoIpXvYOToA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~YK5eNAHGX7bQW65jgs2rLhWNoTj7vgF1CBLZ2k12n3svTuVa_whSNkDWJY9V9-QVC_GCh2-DzDDLoIpXvYOToA==
new file mode 100644
index 0000000..7bc9ef9c
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~YK5eNAHGX7bQW65jgs2rLhWNoTj7vgF1CBLZ2k12n3svTuVa_whSNkDWJY9V9-QVC_GCh2-DzDDLoIpXvYOToA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~bO5MkLRUHsU78utGaMM3HVQkRM7RbYps9mAfFre5AyZnGEAQmWmCQMb2RChRtxj7aiaDPHDpQxhAswnGBW3rAA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~bO5MkLRUHsU78utGaMM3HVQkRM7RbYps9mAfFre5AyZnGEAQmWmCQMb2RChRtxj7aiaDPHDpQxhAswnGBW3rAA==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~bO5MkLRUHsU78utGaMM3HVQkRM7RbYps9mAfFre5AyZnGEAQmWmCQMb2RChRtxj7aiaDPHDpQxhAswnGBW3rAA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~eMJEUpMqij97v52rtaj8rSl-UZJhPmN04NCDD0JZ0cWyFbAHRCbjw5PJqWyqalQuFswnMkB6LvsT3uFLJVGVqw== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~eMJEUpMqij97v52rtaj8rSl-UZJhPmN04NCDD0JZ0cWyFbAHRCbjw5PJqWyqalQuFswnMkB6LvsT3uFLJVGVqw==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~eMJEUpMqij97v52rtaj8rSl-UZJhPmN04NCDD0JZ0cWyFbAHRCbjw5PJqWyqalQuFswnMkB6LvsT3uFLJVGVqw==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~hncU2qbL3W7Jwz-E6zuM7ThWI10rl9EEkPM5cV70-odmasyId-QIR9ZRmIMwYF8DCechev-bgJgPAiYsyImmnQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~hncU2qbL3W7Jwz-E6zuM7ThWI10rl9EEkPM5cV70-odmasyId-QIR9ZRmIMwYF8DCechev-bgJgPAiYsyImmnQ==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~hncU2qbL3W7Jwz-E6zuM7ThWI10rl9EEkPM5cV70-odmasyId-QIR9ZRmIMwYF8DCechev-bgJgPAiYsyImmnQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~l90yex7j_mtyE5LhZFKn-M7ovFj_tArzqr984SmaEo2RQ3JhValxuXW7UXxzNRGaNamX9MMhtWVxU-wT9DA6Zg== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~l90yex7j_mtyE5LhZFKn-M7ovFj_tArzqr984SmaEo2RQ3JhValxuXW7UXxzNRGaNamX9MMhtWVxU-wT9DA6Zg==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~l90yex7j_mtyE5LhZFKn-M7ovFj_tArzqr984SmaEo2RQ3JhValxuXW7UXxzNRGaNamX9MMhtWVxU-wT9DA6Zg==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~no8uvpmY2pkdGvoMP79k6Q4ikuedatoSuF6vBG0HR8h0GsBvPND_wHS-M8mBpxtX6NzpHV4bKIhwFkxUsd7RXA== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~no8uvpmY2pkdGvoMP79k6Q4ikuedatoSuF6vBG0HR8h0GsBvPND_wHS-M8mBpxtX6NzpHV4bKIhwFkxUsd7RXA==
new file mode 100644
index 0000000..e12afe0
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~no8uvpmY2pkdGvoMP79k6Q4ikuedatoSuF6vBG0HR8h0GsBvPND_wHS-M8mBpxtX6NzpHV4bKIhwFkxUsd7RXA==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw==
new file mode 100644
index 0000000..bd95f97
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~wV6va8rQlQLAcZ8rb5UAf1mKxaJdF9ZDlsizSwQ7FqQynxq_02ZsiipQ63-2YJ3jEODImbyk3FvKgJ2KaOPOPQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~wV6va8rQlQLAcZ8rb5UAf1mKxaJdF9ZDlsizSwQ7FqQynxq_02ZsiipQ63-2YJ3jEODImbyk3FvKgJ2KaOPOPQ==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~wV6va8rQlQLAcZ8rb5UAf1mKxaJdF9ZDlsizSwQ7FqQynxq_02ZsiipQ63-2YJ3jEODImbyk3FvKgJ2KaOPOPQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~wpEwfbWjFv9AR7VY2bJ5g_ndbKMYwUEFGZ1w3MdarhPZE605CCX5XHRoL5cjlzYTdIpHF8aXtcLt6kz2ELrAsQ== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~wpEwfbWjFv9AR7VY2bJ5g_ndbKMYwUEFGZ1w3MdarhPZE605CCX5XHRoL5cjlzYTdIpHF8aXtcLt6kz2ELrAsQ==
new file mode 100644
index 0000000..a5fa9ca
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~wpEwfbWjFv9AR7VY2bJ5g_ndbKMYwUEFGZ1w3MdarhPZE605CCX5XHRoL5cjlzYTdIpHF8aXtcLt6kz2ELrAsQ==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~z_lmQvK-mq_MbksvJ2oEALgVyhIwk-VirNem09JWgC47IaBCD7JaSPfQeouKa8iSKUchkiKh993EavN5hgc0Mg== b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~z_lmQvK-mq_MbksvJ2oEALgVyhIwk-VirNem09JWgC47IaBCD7JaSPfQeouKa8iSKUchkiKh993EavN5hgc0Mg==
new file mode 100644
index 0000000..f76dd23
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Data/refs.0~z_lmQvK-mq_MbksvJ2oEALgVyhIwk-VirNem09JWgC47IaBCD7JaSPfQeouKa8iSKUchkiKh993EavN5hgc0Mg==
Binary files differ
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Info.plist b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Info.plist
new file mode 100644
index 0000000..8e63641
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/sample-xcode.xcresult/Info.plist
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>dateCreated</key>
+	<date>2022-09-23T17:38:01Z</date>
+	<key>externalLocations</key>
+	<array/>
+	<key>rootId</key>
+	<dict>
+		<key>hash</key>
+		<string>0~vcNmCXushFQo2Asf-Ddow5-J6PKttgTE0SMgzoRxYZv26906WiKBSKSrE4LJnPfLcKZXgqb8b9B9dS0xNC6phw==</string>
+	</dict>
+	<key>storage</key>
+	<dict>
+		<key>backend</key>
+		<string>fileBacked2</string>
+		<key>compression</key>
+		<string>standard</string>
+	</dict>
+	<key>version</key>
+	<dict>
+		<key>major</key>
+		<integer>3</integer>
+		<key>minor</key>
+		<integer>34</integer>
+	</dict>
+</dict>
+</plist>
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/test_output.json b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/test_output.json
new file mode 100644
index 0000000..a8287ad
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/test_output.json
@@ -0,0 +1,624 @@
+{
+  "_type" : {
+    "_name" : "ActionTestSummary",
+    "_supertype" : {
+      "_name" : "ActionTestSummaryIdentifiableObject",
+      "_supertype" : {
+        "_name" : "ActionAbstractTestSummary"
+      }
+    }
+  },
+  "activitySummaries" : {
+    "_type" : {
+      "_name" : "Array"
+    },
+    "_values" : [
+      {
+        "_type" : {
+          "_name" : "ActionTestActivitySummary"
+        },
+        "activityType" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.xctest.activity-type.userCreated"
+        },
+        "finish" : {
+          "_type" : {
+            "_name" : "Date"
+          },
+          "_value" : "2022-09-20T15:39:30.433-0700"
+        },
+        "start" : {
+          "_type" : {
+            "_name" : "Date"
+          },
+          "_value" : "2022-09-20T15:39:30.366-0700"
+        },
+        "title" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Allocate an ArrayList of size 1000"
+        },
+        "uuid" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "660362D3-AA8A-4630-B098-780ED90922E2"
+        }
+      }
+    ]
+  },
+  "duration" : {
+    "_type" : {
+      "_name" : "Double"
+    },
+    "_value" : "0.0741419792175293"
+  },
+  "identifier" : {
+    "_type" : {
+      "_name" : "String"
+    },
+    "_value" : "BenchmarkTest\/runBenchmark()"
+  },
+  "name" : {
+    "_type" : {
+      "_name" : "String"
+    },
+    "_value" : "runBenchmark()"
+  },
+  "performanceMetrics" : {
+    "_type" : {
+      "_name" : "Array"
+    },
+    "_values" : [
+      {
+        "_type" : {
+          "_name" : "ActionTestPerformanceMetricSummary"
+        },
+        "displayName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Memory Physical"
+        },
+        "identifier" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.XCTMetric_Memory.physical"
+        },
+        "maxPercentRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxPercentRelativeStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "maxStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "measurements" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8208.448"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8192.0"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8192.0"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8208.384"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8257.536"
+            }
+          ]
+        },
+        "polarity" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "prefers smaller"
+        },
+        "unitOfMeasurement" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "kB"
+        }
+      },
+      {
+        "_type" : {
+          "_name" : "ActionTestPerformanceMetricSummary"
+        },
+        "displayName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Clock Monotonic Time"
+        },
+        "identifier" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.XCTMetric_Clock.time.monotonic"
+        },
+        "maxPercentRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxPercentRelativeStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "maxStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "measurements" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.003374259"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0011105260000000001"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0009685430000000001"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.001031847"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.006419944"
+            }
+          ]
+        },
+        "polarity" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "prefers smaller"
+        },
+        "unitOfMeasurement" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "s"
+        }
+      },
+      {
+        "_type" : {
+          "_name" : "ActionTestPerformanceMetricSummary"
+        },
+        "displayName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "CPU Cycles"
+        },
+        "identifier" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.XCTMetric_CPU.cycles"
+        },
+        "maxPercentRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxPercentRelativeStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "maxStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "measurements" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8568.973"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "5173.538"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "4663.702"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "4684.408"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "21681.682"
+            }
+          ]
+        },
+        "polarity" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "prefers smaller"
+        },
+        "unitOfMeasurement" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "kC"
+        }
+      },
+      {
+        "_type" : {
+          "_name" : "ActionTestPerformanceMetricSummary"
+        },
+        "displayName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "CPU Time"
+        },
+        "identifier" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.XCTMetric_CPU.time"
+        },
+        "maxPercentRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxPercentRelativeStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "maxStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "measurements" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0027404810000000003"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0016844030000000001"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0014999850000000001"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0015120390000000002"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.007035559"
+            }
+          ]
+        },
+        "polarity" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "prefers smaller"
+        },
+        "unitOfMeasurement" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "s"
+        }
+      },
+      {
+        "_type" : {
+          "_name" : "ActionTestPerformanceMetricSummary"
+        },
+        "displayName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "CPU Instructions Retired"
+        },
+        "identifier" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.XCTMetric_CPU.instructions_retired"
+        },
+        "maxPercentRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxPercentRelativeStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "maxStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "measurements" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "16386.777"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8917.514"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8777.292"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "8398.467"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "40257.553"
+            }
+          ]
+        },
+        "polarity" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "prefers smaller"
+        },
+        "unitOfMeasurement" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "kI"
+        }
+      },
+      {
+        "_type" : {
+          "_name" : "ActionTestPerformanceMetricSummary"
+        },
+        "displayName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Memory Peak Physical"
+        },
+        "identifier" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "com.apple.dt.XCTMetric_Memory.physical_peak"
+        },
+        "maxPercentRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxPercentRelativeStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "10.0"
+        },
+        "maxRegression" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "maxStandardDeviation" : {
+          "_type" : {
+            "_name" : "Double"
+          },
+          "_value" : "0.0"
+        },
+        "measurements" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0"
+            },
+            {
+              "_type" : {
+                "_name" : "Double"
+              },
+              "_value" : "0.0"
+            }
+          ]
+        },
+        "polarity" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "prefers smaller"
+        },
+        "unitOfMeasurement" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "kB"
+        }
+      }
+    ]
+  },
+  "testStatus" : {
+    "_type" : {
+      "_name" : "String"
+    },
+    "_value" : "Success"
+  }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/tests_reference_output.json b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/tests_reference_output.json
new file mode 100644
index 0000000..9b3286a
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/tests_reference_output.json
@@ -0,0 +1,277 @@
+{
+  "_type" : {
+    "_name" : "ActionTestPlanRunSummaries"
+  },
+  "summaries" : {
+    "_type" : {
+      "_name" : "Array"
+    },
+    "_values" : [
+      {
+        "_type" : {
+          "_name" : "ActionTestPlanRunSummary",
+          "_supertype" : {
+            "_name" : "ActionAbstractTestSummary"
+          }
+        },
+        "name" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Test Scheme Action"
+        },
+        "testableSummaries" : {
+          "_type" : {
+            "_name" : "Array"
+          },
+          "_values" : [
+            {
+              "_type" : {
+                "_name" : "ActionTestableSummary",
+                "_supertype" : {
+                  "_name" : "ActionAbstractTestSummary"
+                }
+              },
+              "diagnosticsDirectoryName" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "testapp-ios-benchmarks-D5CA694B-66BF-479C-ABE4-0D3BDF591768-Configuration-Test Scheme Action-Iteration-1"
+              },
+              "name" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "testapp-ios-benchmarks"
+              },
+              "projectRelativePath" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "benchmark-darwin-sample-xcode.xcodeproj"
+              },
+              "targetName" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "testapp-ios-benchmarks"
+              },
+              "testKind" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "xctest-tool hosted"
+              },
+              "testLanguage" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : ""
+              },
+              "testRegion" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : ""
+              },
+              "tests" : {
+                "_type" : {
+                  "_name" : "Array"
+                },
+                "_values" : [
+                  {
+                    "_type" : {
+                      "_name" : "ActionTestSummaryGroup",
+                      "_supertype" : {
+                        "_name" : "ActionTestSummaryIdentifiableObject",
+                        "_supertype" : {
+                          "_name" : "ActionAbstractTestSummary"
+                        }
+                      }
+                    },
+                    "duration" : {
+                      "_type" : {
+                        "_name" : "Double"
+                      },
+                      "_value" : "0.07568299770355225"
+                    },
+                    "identifier" : {
+                      "_type" : {
+                        "_name" : "String"
+                      },
+                      "_value" : "All tests"
+                    },
+                    "name" : {
+                      "_type" : {
+                        "_name" : "String"
+                      },
+                      "_value" : "All tests"
+                    },
+                    "subtests" : {
+                      "_type" : {
+                        "_name" : "Array"
+                      },
+                      "_values" : [
+                        {
+                          "_type" : {
+                            "_name" : "ActionTestSummaryGroup",
+                            "_supertype" : {
+                              "_name" : "ActionTestSummaryIdentifiableObject",
+                              "_supertype" : {
+                                "_name" : "ActionAbstractTestSummary"
+                              }
+                            }
+                          },
+                          "duration" : {
+                            "_type" : {
+                              "_name" : "Double"
+                            },
+                            "_value" : "0.07503998279571533"
+                          },
+                          "identifier" : {
+                            "_type" : {
+                              "_name" : "String"
+                            },
+                            "_value" : "testapp-ios-benchmarks.xctest"
+                          },
+                          "name" : {
+                            "_type" : {
+                              "_name" : "String"
+                            },
+                            "_value" : "testapp-ios-benchmarks.xctest"
+                          },
+                          "subtests" : {
+                            "_type" : {
+                              "_name" : "Array"
+                            },
+                            "_values" : [
+                              {
+                                "_type" : {
+                                  "_name" : "ActionTestSummaryGroup",
+                                  "_supertype" : {
+                                    "_name" : "ActionTestSummaryIdentifiableObject",
+                                    "_supertype" : {
+                                      "_name" : "ActionAbstractTestSummary"
+                                    }
+                                  }
+                                },
+                                "duration" : {
+                                  "_type" : {
+                                    "_name" : "Double"
+                                  },
+                                  "_value" : "0.07448196411132812"
+                                },
+                                "identifier" : {
+                                  "_type" : {
+                                    "_name" : "String"
+                                  },
+                                  "_value" : "BenchmarkTest"
+                                },
+                                "name" : {
+                                  "_type" : {
+                                    "_name" : "String"
+                                  },
+                                  "_value" : "BenchmarkTest"
+                                },
+                                "subtests" : {
+                                  "_type" : {
+                                    "_name" : "Array"
+                                  },
+                                  "_values" : [
+                                    {
+                                      "_type" : {
+                                        "_name" : "ActionTestMetadata",
+                                        "_supertype" : {
+                                          "_name" : "ActionTestSummaryIdentifiableObject",
+                                          "_supertype" : {
+                                            "_name" : "ActionAbstractTestSummary"
+                                          }
+                                        }
+                                      },
+                                      "duration" : {
+                                        "_type" : {
+                                          "_name" : "Double"
+                                        },
+                                        "_value" : "0.0741419792175293"
+                                      },
+                                      "identifier" : {
+                                        "_type" : {
+                                          "_name" : "String"
+                                        },
+                                        "_value" : "BenchmarkTest\/runBenchmark()"
+                                      },
+                                      "name" : {
+                                        "_type" : {
+                                          "_name" : "String"
+                                        },
+                                        "_value" : "runBenchmark()"
+                                      },
+                                      "summaryRef" : {
+                                        "_type" : {
+                                          "_name" : "Reference"
+                                        },
+                                        "id" : {
+                                          "_type" : {
+                                            "_name" : "String"
+                                          },
+                                          "_value" : "0~9QZnTT_BgRyY356cOax6Xlj_lclcNKu46aQPHsHicDo5aDaBNGg0f1y5hrlGl30pfE8RtG0-7IY1sZEQSgv-yA=="
+                                        },
+                                        "targetType" : {
+                                          "_type" : {
+                                            "_name" : "TypeDefinition"
+                                          },
+                                          "name" : {
+                                            "_type" : {
+                                              "_name" : "String"
+                                            },
+                                            "_value" : "ActionTestSummary"
+                                          },
+                                          "supertype" : {
+                                            "_type" : {
+                                              "_name" : "TypeDefinition"
+                                            },
+                                            "name" : {
+                                              "_type" : {
+                                                "_name" : "String"
+                                              },
+                                              "_value" : "ActionTestSummaryIdentifiableObject"
+                                            },
+                                            "supertype" : {
+                                              "_type" : {
+                                                "_name" : "TypeDefinition"
+                                              },
+                                              "name" : {
+                                                "_type" : {
+                                                  "_name" : "String"
+                                                },
+                                                "_value" : "ActionAbstractTestSummary"
+                                              }
+                                            }
+                                          }
+                                        }
+                                      },
+                                      "testStatus" : {
+                                        "_type" : {
+                                          "_name" : "String"
+                                        },
+                                        "_value" : "Success"
+                                      }
+                                    }
+                                  ]
+                                }
+                              }
+                            ]
+                          }
+                        }
+                      ]
+                    }
+                  }
+                ]
+              }
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
diff --git a/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/xcresult_output.json b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/xcresult_output.json
new file mode 100644
index 0000000..bedddde
--- /dev/null
+++ b/benchmark/benchmark-darwin-gradle-plugin/src/test/test-data/xcresult_output.json
@@ -0,0 +1,502 @@
+{
+  "_type" : {
+    "_name" : "ActionsInvocationRecord"
+  },
+  "actions" : {
+    "_type" : {
+      "_name" : "Array"
+    },
+    "_values" : [
+      {
+        "_type" : {
+          "_name" : "ActionRecord"
+        },
+        "actionResult" : {
+          "_type" : {
+            "_name" : "ActionResult"
+          },
+          "coverage" : {
+            "_type" : {
+              "_name" : "CodeCoverageInfo"
+            }
+          },
+          "diagnosticsRef" : {
+            "_type" : {
+              "_name" : "Reference"
+            },
+            "id" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "0~jAQXUmgyNkmcnoWDboB4OeaM0-whlsynWt0yZ3wo4KvsnpcYa713pAK1NtXSCTE8Uc0lFtZHoOCqewug4plRDQ=="
+            }
+          },
+          "issues" : {
+            "_type" : {
+              "_name" : "ResultIssueSummaries"
+            }
+          },
+          "logRef" : {
+            "_type" : {
+              "_name" : "Reference"
+            },
+            "id" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "0~Qjtn8Ykt-p4Y2zYr6DbsVuygoZGAblK7Eph8ZFqa-w0iUTl09NihLHxd7DNfeeR1B0dwrjrN5Fvx_yZPpHvUjQ=="
+            },
+            "targetType" : {
+              "_type" : {
+                "_name" : "TypeDefinition"
+              },
+              "name" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "ActivityLogSection"
+              }
+            }
+          },
+          "metrics" : {
+            "_type" : {
+              "_name" : "ResultMetrics"
+            },
+            "testsCount" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "1"
+            }
+          },
+          "resultName" : {
+            "_type" : {
+              "_name" : "String"
+            },
+            "_value" : "action"
+          },
+          "status" : {
+            "_type" : {
+              "_name" : "String"
+            },
+            "_value" : "succeeded"
+          },
+          "testsRef" : {
+            "_type" : {
+              "_name" : "Reference"
+            },
+            "id" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "0~CRExaYFNITauPqprkYOVFcX44BiMR7Y5SK7vYQTvwKNnaF1--St6QQlAOz693pVVLLQkXitHdwytuCOA_J3AmA=="
+            },
+            "targetType" : {
+              "_type" : {
+                "_name" : "TypeDefinition"
+              },
+              "name" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "ActionTestPlanRunSummaries"
+              }
+            }
+          }
+        },
+        "buildResult" : {
+          "_type" : {
+            "_name" : "ActionResult"
+          },
+          "coverage" : {
+            "_type" : {
+              "_name" : "CodeCoverageInfo"
+            }
+          },
+          "issues" : {
+            "_type" : {
+              "_name" : "ResultIssueSummaries"
+            }
+          },
+          "logRef" : {
+            "_type" : {
+              "_name" : "Reference"
+            },
+            "id" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "0~o__Z27AtErlXE6_lRzQF5cFdk50BUQF_aPv-O8Qsdvi5HMz3kXluFX3_xOAAfqzm0-YWnbEyrw1wXekD7fMHEw=="
+            },
+            "targetType" : {
+              "_type" : {
+                "_name" : "TypeDefinition"
+              },
+              "name" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "ActivityLogSection"
+              }
+            }
+          },
+          "metrics" : {
+            "_type" : {
+              "_name" : "ResultMetrics"
+            }
+          },
+          "resultName" : {
+            "_type" : {
+              "_name" : "String"
+            },
+            "_value" : "build"
+          },
+          "status" : {
+            "_type" : {
+              "_name" : "String"
+            },
+            "_value" : "succeeded"
+          }
+        },
+        "endedTime" : {
+          "_type" : {
+            "_name" : "Date"
+          },
+          "_value" : "2022-09-20T15:39:30.709-0700"
+        },
+        "runDestination" : {
+          "_type" : {
+            "_name" : "ActionRunDestinationRecord"
+          },
+          "displayName" : {
+            "_type" : {
+              "_name" : "String"
+            },
+            "_value" : "iPhone 13"
+          },
+          "localComputerRecord" : {
+            "_type" : {
+              "_name" : "ActionDeviceRecord"
+            },
+            "busSpeedInMHz" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "cpuCount" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "1"
+            },
+            "cpuKind" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "Apple M1 Pro"
+            },
+            "cpuSpeedInMHz" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "identifier" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "00006000-000239D03CE2801E"
+            },
+            "isConcreteDevice" : {
+              "_type" : {
+                "_name" : "Bool"
+              },
+              "_value" : "true"
+            },
+            "logicalCPUCoresPerPackage" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "10"
+            },
+            "modelCode" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "MacBookPro18,1"
+            },
+            "modelName" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "MacBook Pro"
+            },
+            "modelUTI" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "com.apple.macbookpro-16-2021"
+            },
+            "name" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "My Mac"
+            },
+            "nativeArchitecture" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "arm64e"
+            },
+            "operatingSystemVersion" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "12.6"
+            },
+            "operatingSystemVersionWithBuildNumber" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "12.6 (21G115)"
+            },
+            "physicalCPUCoresPerPackage" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "10"
+            },
+            "platformRecord" : {
+              "_type" : {
+                "_name" : "ActionPlatformRecord"
+              },
+              "identifier" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "com.apple.platform.macosx"
+              },
+              "userDescription" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "macOS"
+              }
+            },
+            "ramSizeInMegabytes" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "16384"
+            }
+          },
+          "targetArchitecture" : {
+            "_type" : {
+              "_name" : "String"
+            },
+            "_value" : "arm64"
+          },
+          "targetDeviceRecord" : {
+            "_type" : {
+              "_name" : "ActionDeviceRecord"
+            },
+            "busSpeedInMHz" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "cpuCount" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "cpuSpeedInMHz" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "identifier" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "7F61C467-4E4A-437C-B6EF-026FEEF3904C"
+            },
+            "isConcreteDevice" : {
+              "_type" : {
+                "_name" : "Bool"
+              },
+              "_value" : "true"
+            },
+            "logicalCPUCoresPerPackage" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "modelCode" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "iPhone14,5"
+            },
+            "modelName" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "iPhone 13"
+            },
+            "modelUTI" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "com.apple.iphone-13-1"
+            },
+            "name" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "iPhone 13"
+            },
+            "nativeArchitecture" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "arm64"
+            },
+            "operatingSystemVersion" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "15.2"
+            },
+            "operatingSystemVersionWithBuildNumber" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "15.2 (19C51)"
+            },
+            "physicalCPUCoresPerPackage" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            },
+            "platformRecord" : {
+              "_type" : {
+                "_name" : "ActionPlatformRecord"
+              },
+              "identifier" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "com.apple.platform.iphonesimulator"
+              },
+              "userDescription" : {
+                "_type" : {
+                  "_name" : "String"
+                },
+                "_value" : "iOS Simulator"
+              }
+            },
+            "ramSizeInMegabytes" : {
+              "_type" : {
+                "_name" : "Int"
+              },
+              "_value" : "0"
+            }
+          },
+          "targetSDKRecord" : {
+            "_type" : {
+              "_name" : "ActionSDKRecord"
+            },
+            "identifier" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "iphonesimulator15.2"
+            },
+            "name" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "Simulator - iOS 15.2"
+            },
+            "operatingSystemVersion" : {
+              "_type" : {
+                "_name" : "String"
+              },
+              "_value" : "15.2"
+            }
+          }
+        },
+        "schemeCommandName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Test"
+        },
+        "schemeTaskName" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "BuildAndAction"
+        },
+        "startedTime" : {
+          "_type" : {
+            "_name" : "Date"
+          },
+          "_value" : "2022-09-20T15:38:15.930-0700"
+        },
+        "title" : {
+          "_type" : {
+            "_name" : "String"
+          },
+          "_value" : "Testing project benchmark-darwin-sample-xcode with scheme testapp-ios"
+        }
+      }
+    ]
+  },
+  "issues" : {
+    "_type" : {
+      "_name" : "ResultIssueSummaries"
+    }
+  },
+  "metadataRef" : {
+    "_type" : {
+      "_name" : "Reference"
+    },
+    "id" : {
+      "_type" : {
+        "_name" : "String"
+      },
+      "_value" : "0~9QqgSKuPd_WJ00r0MtQ53EH7hHP4lpwwOlDne6b7j-OjJ6jPH5Vm31modXkV3kE_g1-SD2HbuvM_J1hZSGH-mw=="
+    },
+    "targetType" : {
+      "_type" : {
+        "_name" : "TypeDefinition"
+      },
+      "name" : {
+        "_type" : {
+          "_name" : "String"
+        },
+        "_value" : "ActionsInvocationMetadata"
+      }
+    }
+  },
+  "metrics" : {
+    "_type" : {
+      "_name" : "ResultMetrics"
+    },
+    "testsCount" : {
+      "_type" : {
+        "_name" : "Int"
+      },
+      "_value" : "1"
+    }
+  }
+}
diff --git a/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml b/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml
index bece70f..b0e054a 100644
--- a/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml
+++ b/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml
@@ -1,6 +1,6 @@
 # XCodeGen for the :benchmark:benchmark-darwin-samples module..
 
-name: benchmark-darwin-sample-xcode
+name: benchmark-darwin-samples-xcode
 targets:
 
   testapp-ios:
@@ -13,19 +13,9 @@
     scheme:
       testTargets:
         - testapp-ios-benchmarks
-      preActions:
-        - name: build AndroidXDarwinSampleBenchmarks.xcframework
-          basedOnDependencyAnalysis: false
-          settingsTarget: testapp-ios
-          script: |
-            cd ${PROJECT_DIR}/../..
-            ANDROIDX_PROJECTS=KMP ./gradlew :benchmark:benchmark-darwin-samples:assembleAndroidXDarwinSampleBenchmarksReleaseXCFramework \
-                --no-configuration-cache < /dev/null
-          outputFiles:
-            - "${PROJECT_DIR}/../../../../out/androidx/benchmark/benchmark-darwin-samples/build/XCFrameworks/release/AndroidXDarwinSampleBenchmarks.xcframework"
       gatherCoverageData: false
     dependencies:
-      - framework: "${PROJECT_DIR}/../../../../out/androidx/benchmark/benchmark-darwin-samples/build/XCFrameworks/release/AndroidXDarwinSampleBenchmarks.xcframework"
+      - framework: "${PROJECT_DIR}/../../../../androidx/benchmark/benchmark-darwin-samples/build/XCFrameworks/release/AndroidXDarwinSampleBenchmarks.xcframework"
     settings:
       PRODUCT_NAME: testapp-ios
 
diff --git a/benchmark/benchmark-darwin-samples/build.gradle b/benchmark/benchmark-darwin-samples/build.gradle
index f31dd41..80282e3 100644
--- a/benchmark/benchmark-darwin-samples/build.gradle
+++ b/benchmark/benchmark-darwin-samples/build.gradle
@@ -3,6 +3,7 @@
 
 plugins {
     id("AndroidXPlugin")
+    id("androidx.benchmark.darwin")
 }
 
 androidXMultiplatform {
@@ -46,6 +47,17 @@
     }
 }
 
+darwinBenchmark {
+    xcodeGenConfigFile = project.rootProject.file(
+            "benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml"
+    )
+    xcodeProjectName = "benchmark-darwin-samples-xcode"
+    scheme = "testapp-ios"
+    // ios 13, 15.2
+    destination = "id=7F61C467-4E4A-437C-B6EF-026FEEF3904C"
+    xcFrameworkConfig = "AndroidXDarwinSampleBenchmarks"
+}
+
 androidx {
     name = "AndroidX Benchmarks - Darwin Samples"
     mavenGroup = LibraryGroups.BENCHMARK
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index fe156f1..5db44a4 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -28,6 +28,7 @@
 import androidx.benchmark.DeviceInfo
 import androidx.benchmark.InstrumentationResults
 import androidx.benchmark.ResultWriter
+import androidx.benchmark.Shell
 import androidx.benchmark.UserspaceTracing
 import androidx.benchmark.checkAndGetSuppressionState
 import androidx.benchmark.conditionalError
@@ -65,6 +66,7 @@
         false
     }
 
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
     val errors = DeviceInfo.errors +
         // TODO: Merge this debuggable check / definition with Errors.kt in benchmark-common
         listOfNotNull(
@@ -97,6 +99,38 @@
                     <!--suppress AndroidElementNotAllowed -->
                     <profileable android:shell="true"/>
                 """.trimIndent()
+            ),
+            conditionalError(
+                hasError = instrumentation.targetContext.packageName !=
+                    instrumentation.context.packageName,
+                id = "NOT-SELF-INSTRUMENTING",
+                summary = "Benchmark manifest is instrumenting separate process",
+                message = """
+                    Macrobenchmark instrumentation target in manifest
+                    ${instrumentation.targetContext.packageName} does not match macrobenchmark
+                    package ${instrumentation.context.packageName}. While macrobenchmarks 'target' a
+                    separate app they measure, they can not declare it as their instrumentation
+                    targetPackage in their manifest. Doing so would cause the macrobenchmark test
+                    app to be loaded into the target application process, which would prevent
+                    macrobenchmark from killing, compiling, or launching the target process.
+
+                    Ensure your macrobenchmark test apk's manifest matches the manifest package, and
+                    instrumentation target package, also called 'self-instrumenting':
+
+                    <manifest
+                        package="com.mymacrobenchpackage" ...>
+                        <instrumentation
+                            android:name="androidx.benchmark.junit4.AndroidBenchmarkRunner"
+                            android:targetPackage="mymacrobenchpackage"/>
+
+                    In gradle library modules, this is the default behavior. In gradle test modules,
+                    specify the experimental self-instrumenting property:
+                    android {
+                        targetProjectPath = ":app"
+                        // Enable the benchmark to run separately from the app process
+                        experimentalProperties["android.experimental.self-instrumenting"] = true
+                    }
+                """.trimIndent()
             )
         ).sortedBy { it.id }
 
@@ -129,12 +163,12 @@
         "Empty list of metrics passed to metrics param, must pass at least one Metric"
     }
 
-    // skip benchmark if not supported by vm settings
-    compilationMode.assumeSupportedWithVmSettings()
-
     val suppressionState = checkErrors(packageName)
     var warningMessage = suppressionState?.warningMessage ?: ""
 
+    // skip benchmark if not supported by vm settings
+    compilationMode.assumeSupportedWithVmSettings()
+
     val startTime = System.nanoTime()
     val scope = MacrobenchmarkScope(packageName, launchWithClearTask)
 
@@ -340,31 +374,37 @@
         userspaceTracingPackage = userspaceTracingPackage,
         setupBlock = {
             if (startupMode == StartupMode.COLD) {
+                // Run setup before killing process
+                setupBlock(this)
+
+                // Kill - code below must not wake process!
                 killProcess()
+
                 // Shader caches are stored in the code cache directory. Make sure that
                 // they are cleared every iteration.
                 dropShaderCache()
-                // drop app pages from page cache to ensure it is loaded from disk, from scratch
 
-                // resetAndCompile uses ProfileInstallReceiver to write a skip file.
-                // This is done to reduce the interference from ProfileInstaller,
-                // so long-running benchmarks don't get optimized due to a background dexopt.
-
-                // To restore the state of the process we need to drop app pages so its
-                // loaded from disk, from scratch.
+                // Ensure app's pages are not cached in memory for a true _cold_ start.
                 dropKernelPageCache()
-            } else if (iteration == 0 && startupMode != null) {
-                try {
-                    iteration = null // override to null for warmup, before starting measurements
 
-                    // warmup process by running the measure block once unmeasured
-                    setupBlock(this)
-                    measureBlock()
-                } finally {
-                    iteration = 0
+                // validate process is not running just before returning
+                check(!Shell.isPackageAlive(packageName)) {
+                    "Package $packageName must not be running prior to cold start!"
                 }
+            } else {
+                if (iteration == 0 && startupMode != null) {
+                    try {
+                        iteration = null // override to null for warmup
+
+                        // warmup process by running the measure block once unmeasured
+                        setupBlock(this)
+                        measureBlock()
+                    } finally {
+                        iteration = 0 // resume counting
+                    }
+                }
+                setupBlock(this)
             }
-            setupBlock(this)
         },
         // Don't reuse activities by default in COLD / WARM
         launchWithClearTask = startupMode == StartupMode.COLD || startupMode == StartupMode.WARM,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 9524df3..1accf72 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -36,7 +36,6 @@
 import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.api.tasks.ClasspathNormalizer
-import org.gradle.api.tasks.bundling.Zip
 import org.gradle.kotlin.dsl.apply
 import org.gradle.kotlin.dsl.create
 import org.gradle.kotlin.dsl.findByType
@@ -54,8 +53,8 @@
     "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination"
 const val composeReportsOption =
     "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination"
-const val zipComposeReportsTaskName = "zipComposeCompilerReports"
-const val zipComposeMetricsTaskName = "zipComposeCompilerMetrics"
+const val enableMetricsArg = "androidx.enableComposeCompilerMetrics"
+const val enableReportsArg = "androidx.enableComposeCompilerReports"
 
 /**
  * Plugin to apply common configuration for Compose projects.
@@ -359,63 +358,46 @@
             }
         }.files
 
-        val enableMetrics = project.enableComposeCompilerMetrics()
-        val enableReports = project.enableComposeCompilerReports()
+        val enableMetricsProvider = project.providers.gradleProperty(enableMetricsArg)
+        val enableReportsProvider = project.providers.gradleProperty(enableReportsArg)
 
-        val compileTasks = project.tasks.withType(KotlinCompile::class.java)
-
-        compileTasks.configureEach { compile ->
+        val libraryMetricsDirectory = project.rootProject.getLibraryMetricsDirectory()
+        val libraryReportsDirectory = project.rootProject.getLibraryReportsDirectory()
+        project.tasks.withType(KotlinCompile::class.java).configureEach { compile ->
             // Append inputs to KotlinCompile so tasks get invalidated if any of these values change
             compile.inputs.files({ kotlinPlugin })
                 .withPropertyName("composeCompilerExtension")
                 .withNormalizer(ClasspathNormalizer::class.java)
-            compile.inputs.property("composeMetricsEnabled", enableMetrics)
-            compile.inputs.property("composeReportsEnabled", enableReports)
+            compile.inputs.property("composeMetricsEnabled", enableMetricsProvider).optional(true)
+            compile.inputs.property("composeReportsEnabled", enableReportsProvider).optional(true)
 
             // Gradle hack ahead, we use of absolute paths, but is OK here because we do it in
             // doFirst which happens after Gradle task input snapshotting. AGP does the same.
             compile.doFirst {
                 compile.kotlinOptions.freeCompilerArgs += "-Xplugin=${kotlinPlugin.first()}"
 
+                if (enableMetricsProvider.orNull == "true") {
+                    val metricsDest = File(libraryMetricsDirectory, "compose")
+                    compile.kotlinOptions.freeCompilerArgs +=
+                        listOf(
+                            "-P",
+                            "$composeMetricsOption=${metricsDest.absolutePath}"
+                        )
+                }
+                if ((enableReportsProvider.orNull == "true")) {
+                    val reportsDest = File(libraryReportsDirectory, "compose")
+                    compile.kotlinOptions.freeCompilerArgs +=
+                        listOf(
+                            "-P",
+                            "$composeReportsOption=${reportsDest.absolutePath}"
+                        )
+                }
                 if (shouldPublish) {
                     compile.kotlinOptions.freeCompilerArgs +=
                         listOf("-P", composeSourceOption)
                 }
             }
         }
-
-        if (enableMetrics) {
-            project.rootProject.tasks.named(zipComposeMetricsTaskName).configure({ zipTask ->
-                zipTask.dependsOn(compileTasks)
-            })
-
-            val metricsIntermediateDir = project.compilerMetricsIntermediatesDir()
-            compileTasks.configureEach { compile ->
-                compile.doFirst {
-                    compile.kotlinOptions.freeCompilerArgs +=
-                        listOf(
-                            "-P",
-                            "$composeMetricsOption=$metricsIntermediateDir"
-                        )
-                }
-            }
-        }
-        if (enableReports) {
-            project.rootProject.tasks.named(zipComposeReportsTaskName).configure({ zipTask ->
-                zipTask.dependsOn(compileTasks)
-            })
-
-            val reportsIntermediateDir = project.compilerReportsIntermediatesDir()
-            compileTasks.configureEach { compile ->
-                compile.doFirst {
-                    compile.kotlinOptions.freeCompilerArgs +=
-                        listOf(
-                            "-P",
-                            "$composeReportsOption=$reportsIntermediateDir"
-                        )
-                }
-            }
-        }
     }
 }
 
@@ -524,39 +506,3 @@
     return if (KotlinVersion.CURRENT.isAtLeast(1, 4)) ArrayDeque(initialSize)
     else ArrayList(initialSize)
 }
-
-public fun Project.zipComposeCompilerMetrics() {
-    if (project.enableComposeCompilerMetrics()) {
-        val reportsIntermediateDir = project.compilerMetricsIntermediatesDir()
-        val libraryMetricsDirectory = project.rootProject.getLibraryMetricsDirectory()
-        val zipComposeMetrics = project.tasks.register(zipComposeMetricsTaskName, Zip::class.java) {
-            zipTask ->
-            zipTask.from(reportsIntermediateDir)
-            zipTask.destinationDirectory.set(File(libraryMetricsDirectory, "compose"))
-            zipTask.archiveBaseName.set("composemetrics")
-        }
-        project.addToBuildOnServer(zipComposeMetrics)
-    }
-}
-
-public fun Project.zipComposeCompilerReports() {
-    if (project.enableComposeCompilerReports()) {
-        val metricsIntermediateDir = project.compilerReportsIntermediatesDir()
-        val libraryReportsDirectory = project.rootProject.getLibraryReportsDirectory()
-        val zipComposeReports = project.tasks.register(zipComposeReportsTaskName, Zip::class.java) {
-            zipTask ->
-            zipTask.from(metricsIntermediateDir)
-            zipTask.destinationDirectory.set(File(libraryReportsDirectory, "compose"))
-            zipTask.archiveBaseName.set("composereports")
-        }
-        project.addToBuildOnServer(zipComposeReports)
-    }
-}
-
-fun Project.compilerMetricsIntermediatesDir(): File {
-    return project.rootProject.file("${project.rootProject.buildDir}/libraryreports/composemetrics")
-}
-
-fun Project.compilerReportsIntermediatesDir(): File {
-    return project.rootProject.file("${project.rootProject.buildDir}/libraryreports/composereports")
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 457e325..4e1d108 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -243,16 +243,4 @@
 fun Project.allowMissingLintProject() =
     findBooleanProperty(ALLOW_MISSING_LINT_CHECKS_PROJECT) ?: false
 
-/**
- * Returns whether we export compose compiler metrics
- */
-fun Project.enableComposeCompilerMetrics() =
-    findBooleanProperty(ENABLE_COMPOSE_COMPILER_METRICS) ?: false
-
-/**
- * Returns whether we export compose compiler reports
- */
-fun Project.enableComposeCompilerReports() =
-    findBooleanProperty(ENABLE_COMPOSE_COMPILER_REPORTS) ?: false
-
-fun Project.findBooleanProperty(propName: String) = (findProperty(propName) as? String)?.toBoolean()
+fun Project.findBooleanProperty(propName: String) = (findProperty(propName) as? String)?.toBoolean()
\ No newline at end of file
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 6d02d10..672f99b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -230,9 +230,6 @@
             task.setOutput(File(project.getDistributionDirectory(), "task_outputs.txt"))
             task.removePrefix(project.getCheckoutRoot().path)
         }
-
-        project.zipComposeCompilerMetrics()
-        project.zipComposeCompilerReports()
     }
 
     // If our output message validation configuration changes, invalidate all tasks to make sure
diff --git a/buildSrc/public/build.gradle b/buildSrc/public/build.gradle
index f98bc4c..e37e156 100644
--- a/buildSrc/public/build.gradle
+++ b/buildSrc/public/build.gradle
@@ -4,6 +4,9 @@
     main.java.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/kotlin"
     main.resources.srcDirs += "${supportRootFolder}/benchmark/gradle-plugin/src/main/resources"
 
+    main.java.srcDirs += "${supportRootFolder}/benchmark/benchmark-darwin-gradle-plugin/src/main/kotlin"
+    main.resources.srcDirs += "${supportRootFolder}/benchmark/benchmark-darwin-gradle-plugin/src/main/resources"
+
     main.java.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main/kotlin"
     main.resources.srcDirs += "${supportRootFolder}/inspection/inspection-gradle-plugin/src/main" +
             "/resources"
@@ -15,6 +18,11 @@
             "src/main/kotlin"
 }
 
+dependencies {
+    // This is for androidx.benchmark.darwin
+    implementation(libs.apacheCommonsMath)
+}
+
 gradlePlugin {
     plugins {
         benchmark {
@@ -25,5 +33,9 @@
             id = "androidx.inspection"
             implementationClass = "androidx.inspection.gradle.InspectionPlugin"
         }
+        darwinBenchmark {
+            id = "androidx.benchmark.darwin"
+            implementationClass = "androidx.benchmark.darwin.gradle.DarwinBenchmarkPlugin"
+        }
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 0d4049b..fd71cc8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -365,6 +365,9 @@
     @SuppressWarnings("WeakerAccess")
     final Executor mSequentialIoExecutor;
 
+    @Nullable
+    private CameraEffect mCameraEffect;
+
     /**
      * Creates a new image capture use case from the given configuration.
      *
@@ -1937,8 +1940,7 @@
         Log.d(TAG, String.format("createPipelineWithNode(cameraId: %s, resolution: %s)",
                 cameraId, resolution));
         checkState(mImagePipeline == null);
-        // TODO: set CameraEffect
-        mImagePipeline = new ImagePipeline(config, resolution, null);
+        mImagePipeline = new ImagePipeline(config, resolution, mCameraEffect);
         checkState(mTakePictureManager == null);
         mTakePictureManager = new TakePictureManager(mImageCaptureControl, mImagePipeline);
 
@@ -2059,6 +2061,27 @@
         return mImagePipeline != null && mTakePictureManager != null;
     }
 
+    /**
+     * @hide
+     */
+    @MainThread
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public void setEffect(@Nullable CameraEffect cameraEffect) {
+        checkMainThread();
+        mCameraEffect = cameraEffect;
+    }
+
+    /**
+     * @hide
+     */
+    @MainThread
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @Nullable
+    public CameraEffect getEffect() {
+        checkMainThread();
+        return mCameraEffect;
+    }
+
     // ===== New architecture end =====
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index c0f7ec02..fc8f5d3 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -459,6 +459,9 @@
                 preview.setProcessor(new SurfaceProcessorWithExecutor(
                         requireNonNull(effect.getSurfaceProcessor()),
                         effect.getExecutor()));
+            } else if (useCase instanceof ImageCapture) {
+                ImageCapture imageCapture = ((ImageCapture) useCase);
+                imageCapture.setEffect(effectsByTargets.get(CameraEffect.IMAGE_CAPTURE));
             }
         }
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index 48dab10..82353c9 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -43,6 +43,7 @@
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.camera.testing.fakes.FakeUseCaseConfig
 import androidx.camera.testing.fakes.FakeUseCaseConfigFactory
+import androidx.camera.testing.fakes.GrayscaleImageEffect
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
@@ -77,6 +78,7 @@
     private lateinit var fakeCamera: FakeCamera
     private lateinit var useCaseConfigFactory: UseCaseConfigFactory
     private val fakeCameraSet = LinkedHashSet<CameraInternal>()
+    private val imageEffect = GrayscaleImageEffect()
 
     @Before
     fun setUp() {
@@ -86,7 +88,7 @@
         fakeCameraSet.add(fakeCamera)
         surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor())
         executor = Executors.newSingleThreadExecutor()
-        effects = listOf(FakePreviewEffect(executor, surfaceProcessor))
+        effects = listOf(FakePreviewEffect(executor, surfaceProcessor), imageEffect)
     }
 
     @After
@@ -619,17 +621,24 @@
     @Test
     fun updateEffects_effectsAddedAndRemoved() {
         // Arrange.
-        val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+        val preview = Preview.Builder().build()
+        val imageCapture = ImageCapture.Builder().build()
+        val useCases = listOf(preview, imageCapture)
+
         // Act: update use cases with effects.
-        CameraUseCaseAdapter.updateEffects(effects, listOf(preview))
+        CameraUseCaseAdapter.updateEffects(effects, useCases)
         // Assert: preview has processor wrapped with the right executor.
         val previewProcessor = preview.processor as SurfaceProcessorWithExecutor
         assertThat(previewProcessor.processor).isEqualTo(surfaceProcessor)
         assertThat(previewProcessor.executor).isEqualTo(executor)
+        // Assert: imageCapture has the effect set.
+        assertThat(imageCapture.effect).isEqualTo(imageEffect)
+
         // Act: update again with no effects.
-        CameraUseCaseAdapter.updateEffects(listOf(), listOf(preview))
-        // Assert: preview no longer has processors.
+        CameraUseCaseAdapter.updateEffects(listOf(), useCases)
+        // Assert: use cases no longer has effects.
         assertThat(preview.processor).isNull()
+        assertThat(imageCapture.effect).isNull()
     }
 
     private fun createCoexistingRequiredRuleCameraConfig(): CameraConfig {
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 1c8e0e8..09b1c7e 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2019 The Android Open Source Project
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -14,35 +13,52 @@
 limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-
-    <uses-feature android:name="android.hardware.camera" />
-    <uses-feature android:glEsVersion="0x00020000" />
 
     <application
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
         <activity
             android:name=".CameraXActivity"
+            android:exported="true"
             android:label="Camera Core Test App"
-            android:exported="true">
+            android:taskAffinity="androidx.camera.integration.core.CameraXActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".CameraPipeActivity"
+            android:exported="true"
+            android:label="CameraPipe Core Test App"
+            android:taskAffinity="androidx.camera.integration.core.CameraPipeActivity"
+            android:theme="@android:style/Theme.Dialog">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity
             android:name=".OpenGLActivity"
+            android:exported="true"
             android:label="Camera Core Test App (OpenGL Activity)"
-            android:theme="@style/NoTitleTheme"
-            android:exported="true">
+            android:taskAffinity="androidx.camera.integration.core.OpenGLActivity"
+            android:theme="@style/NoTitleTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
     </application>
+    <uses-feature android:glEsVersion="0x00020000" />
+    <uses-feature android:name="android.hardware.camera" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
 </manifest>
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraPipeActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraPipeActivity.java
new file mode 100644
index 0000000..36307f0
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraPipeActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.camera.integration.core;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ * An activity to launch "Camera Core Test App" with Camera Pipe configuration
+ *
+ * <p>Launches CameraXActivity with Camera Pipe configuration.
+ */
+public class CameraPipeActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = new Intent(CameraPipeActivity.this, CameraXActivity.class);
+        Bundle b = new Bundle();
+        b.putString(CameraXActivity.INTENT_EXTRA_CAMERA_IMPLEMENTATION,
+                CameraXViewModel.CAMERA_PIPE_IMPLEMENTATION_OPTION);
+        intent.putExtras(b);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index b7d26e4..24257c5 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -157,6 +157,7 @@
 public class CameraXActivity extends AppCompatActivity {
     private static final String TAG = "CameraXActivity";
     private static final String[] REQUIRED_PERMISSIONS;
+
     static {
         // From Android T, skips the permission check of WRITE_EXTERNAL_STORAGE since it won't be
         // granted any more.
@@ -174,6 +175,9 @@
         }
     }
 
+    //Use this activity title when Camera Pipe configuration is used by core test app
+    private static final String APP_TITLE_FOR_CAMERA_PIPE = "CameraPipe Core Test App";
+
     // Possible values for this intent key: "backward" or "forward".
     private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
     // Possible values for this intent key: "switch_test_case", "preview_test_case" or
@@ -647,7 +651,7 @@
                     } else if (outputOptions instanceof FileOutputOptions) {
                         videoFilePath = ((FileOutputOptions) outputOptions).getFile().getPath();
                         MediaScannerConnection.scanFile(this,
-                                new String[] { videoFilePath }, null,
+                                new String[]{videoFilePath}, null,
                                 (path, uri1) -> {
                                     Log.i(TAG, "Scanned " + path + " -> uri= " + uri1);
                                     updateVideoSavedSessionData(uri1);
@@ -1072,6 +1076,11 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        //if different Camera Provider (CameraPipe vs Camera2 was initialized in previous session,
+        //then close this application.
+        closeAppIfCameraProviderMismatch(this.getIntent());
+
         setContentView(R.layout.activity_camera_xmain);
         mImageCaptureExecutorService = Executors.newSingleThreadExecutor();
         OpenGLRenderer previewRenderer = mPreviewRenderer = new OpenGLRenderer();
@@ -1183,7 +1192,12 @@
                 newIntent.removeExtra(INTENT_EXTRA_CAMERA_IMPLEMENTATION_NO_HISTORY);
                 setIntent(newIntent);
             }
+
             if (cameraImplementation != null) {
+                if (cameraImplementation.equalsIgnoreCase(
+                        CameraXViewModel.CAMERA_PIPE_IMPLEMENTATION_OPTION)) {
+                    setTitle(APP_TITLE_FOR_CAMERA_PIPE);
+                }
                 CameraXViewModel.configureCameraProvider(
                         cameraImplementation, cameraImplementationNoHistory);
             }
@@ -1215,6 +1229,34 @@
         setupPermissions();
     }
 
+    /**
+     * Close current app if CameraProvider from intent of current activity doesn't match with
+     * CameraProvider stored in the CameraXViewModel, because CameraProvider can't be changed
+     * between Camera2 and Camera Pipe while app is running.
+     */
+    private void closeAppIfCameraProviderMismatch(Intent mIntent) {
+        String cameraImplementation = null;
+        Boolean cameraImplementationNoHistory = false;
+        Bundle bundle = mIntent.getExtras();
+        if (bundle != null) {
+            cameraImplementation = bundle.getString(INTENT_EXTRA_CAMERA_IMPLEMENTATION);
+            cameraImplementationNoHistory =
+                    bundle.getBoolean(INTENT_EXTRA_CAMERA_IMPLEMENTATION_NO_HISTORY, false);
+        }
+
+        if (!cameraImplementationNoHistory) {
+            if (!CameraXViewModel.isCameraProviderUnInitializedOrSameAsParameter(
+                    cameraImplementation)) {
+                Toast.makeText(CameraXActivity.this, "Please relaunch "
+                                + "the app to apply new CameraX configuration.",
+                        Toast.LENGTH_LONG).show();
+                finish();
+                System.exit(0);
+            }
+        }
+    }
+
+
     @Override
     public void onDestroy() {
         super.onDestroy();
@@ -1410,8 +1452,8 @@
                                 for (String permission : REQUIRED_PERMISSIONS) {
                                     if (!Objects.requireNonNull(result.get(permission))) {
                                         Toast.makeText(getApplicationContext(),
-                                                "Camera permission denied.",
-                                                Toast.LENGTH_SHORT)
+                                                        "Camera permission denied.",
+                                                        Toast.LENGTH_SHORT)
                                                 .show();
                                         finish();
                                         return;
@@ -1596,10 +1638,10 @@
         cameraInfo.getZoomState().removeObservers(this);
         cameraInfo.getZoomState().observe(this,
                 state -> {
-                String str = String.format("%.2fx", state.getZoomRatio());
-                mZoomRatioLabel.setText(str);
-                mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
-            });
+                    String str = String.format("%.2fx", state.getZoomRatio());
+                    mZoomRatioLabel.setText(str);
+                    mZoomSeekBar.setProgress((int) (MAX_SEEKBAR_VALUE * state.getLinearZoom()));
+                });
     }
 
     private boolean is2XZoomSupported() {
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
index 60d7ed6..61a1f09 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
@@ -106,6 +106,41 @@
 
     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
     @MainThread
+    public static boolean isCameraProviderUnInitializedOrSameAsParameter(
+            @NonNull String cameraImplementation) {
+
+        if (sConfiguredCameraXCameraImplementation == null) {
+            return true;
+        }
+        String currentCameraProvider = getCameraProviderName(
+                sConfiguredCameraXCameraImplementation);
+        cameraImplementation = getCameraProviderName(cameraImplementation);
+
+        if (currentCameraProvider.equals(cameraImplementation)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * convert null and IMPLICIT_IMPLEMENTATION_OPTION Camera Provider name to
+     * CAMERA2_IMPLEMENTATION_OPTION
+     */
+    @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
+    @MainThread
+    private static String getCameraProviderName(String mCameraProvider) {
+        if (mCameraProvider == null) {
+            mCameraProvider = CAMERA2_IMPLEMENTATION_OPTION;
+        }
+        if (mCameraProvider.equals(IMPLICIT_IMPLEMENTATION_OPTION)) {
+            mCameraProvider = CAMERA2_IMPLEMENTATION_OPTION;
+        }
+        return mCameraProvider;
+    }
+
+    @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
+    @MainThread
     static void configureCameraProvider(@NonNull String cameraImplementation) {
         configureCameraProvider(cameraImplementation, false);
     }
diff --git a/collection/settings.gradle b/collection/settings.gradle
index f2f97ca..e70b8e0 100644
--- a/collection/settings.gradle
+++ b/collection/settings.gradle
@@ -28,6 +28,8 @@
     setupPlayground("..")
     selectProjectsFromAndroidX({ name ->
         if (name.startsWith(":collection")) return true
+        if (name == ":benchmark:benchmark-darwin") return true
+        if (name == ":benchmark:benchmark-darwin-core") return true
         if (name == ":internal-testutils-truth") return true
         return false
     })
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt
index afa7499..b763795 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenSignatureTest.kt
@@ -246,7 +246,7 @@
             instanceClass ?: error("Could not find class $className in loaded classes")
         }
 
-        val instanceOfClass = instanceClass.newInstance()
+        val instanceOfClass = instanceClass.getDeclaredConstructor().newInstance()
         val testMethod = instanceClass.getMethod("test", Context::class.java)
 
         val controller = Robolectric.buildActivity(TestActivity::class.java)
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
index 1ca1cb8..9fc894a 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractCodegenTest.kt
@@ -143,7 +143,7 @@
         val loader = createClassLoader()
         if (dumpClasses) dumpClasses(loader)
         val loadedClass = loader.loadClass("Test")
-        val instance = loadedClass.newInstance()
+        val instance = loadedClass.getDeclaredConstructor().newInstance()
         val instanceClass = instance::class.java
         val testMethod = instanceClass.getMethod("test")
         testMethod.invoke(instance)
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt
index 3120221..11a509a 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/AbstractLoweringTests.kt
@@ -120,7 +120,7 @@
             instanceClass ?: error("Could not find class $className in loaded classes")
         }
 
-        val instanceOfClass = instanceClass.newInstance()
+        val instanceOfClass = instanceClass.getDeclaredConstructor().newInstance()
         val testMethod = instanceClass.getMethod(
             "test",
             *parameterTypes,
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index ac4674a..fc62647 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.compiler.plugins.kotlin
 
+import org.intellij.lang.annotations.Language
 import org.junit.Test
 
 class ControlFlowTransformTests : AbstractControlFlowTransformTests() {
@@ -219,6 +220,727 @@
         """
     )
 
+    private fun verifyInlineReturn(
+        @Language("kotlin")
+        source: String,
+        expectedTransformed: String,
+    ) = verifyComposeIrTransform(
+        """
+            import androidx.compose.runtime.Composable
+            $source
+        """,
+        expectedTransformed,
+        """
+            import androidx.compose.runtime.Composable
+
+            @Composable
+            fun A() { }
+
+            @Composable
+            fun Text(text: String) { }
+
+            @Composable
+            inline fun Wrapper(content: @Composable () -> Unit) = content()
+
+            @Composable
+            inline fun M1(content: @Composable () -> Unit) = Wrapper {
+                content()
+            }
+
+            @Composable
+            inline fun M2(content: @Composable () -> Unit) = Wrapper {
+                Wrapper {
+                    content()
+                }
+            }
+
+            @Composable
+            inline fun M3(content: @Composable () -> Unit) = Wrapper {
+                Wrapper {
+                    Wrapper {
+                        content()
+                    }
+                }
+            }
+        """.trimIndent()
+    )
+
+    @Test
+    fun testInline_CM3_RFun() = verifyInlineReturn(
+        """
+            @Composable
+            fun Test(condition: Boolean) {
+                A()
+                M3 {
+                    A()
+                    if (condition) {
+                        return
+                    }
+                    A()
+                }
+                A()
+            }
+
+        """,
+        """
+            @Composable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>,<M3>,<A()>:Test.kt")
+              val tmp1_marker = %composer.currentMarker
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    if (condition) {
+                      %composer.endToMarker(tmp1_marker)
+                      if (isTraceInProgress()) {
+                        traceEventEnd()
+                      }
+                      %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                        Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+                      }
+                      return
+                    }
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testInline_CM3_RFun_CM3_RFun() = verifyInlineReturn(
+        """
+            @Composable
+            fun Test(a: Boolean, b: Boolean) {
+                A()
+                M3 {
+                    A()
+                    if (a) {
+                        return
+                    }
+                    A()
+                }
+                M3 {
+                    A()
+                    if (b) {
+                        return
+                    }
+                    A()
+                }
+                A()
+            }
+
+        """,
+        """
+            @Composable
+            fun Test(a: Boolean, b: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>,<M3>,<M3>,<A()>:Test.kt")
+              val tmp1_marker = %composer.currentMarker
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(a)) 0b0100 else 0b0010
+              }
+              if (%changed and 0b01110000 === 0) {
+                %dirty = %dirty or if (%composer.changed(b)) 0b00100000 else 0b00010000
+              }
+              if (%dirty and 0b01011011 !== 0b00010010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    if (a) {
+                      %composer.endToMarker(tmp1_marker)
+                      if (isTraceInProgress()) {
+                        traceEventEnd()
+                      }
+                      %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                        Test(a, b, %composer, updateChangedFlags(%changed or 0b0001))
+                      }
+                      return
+                    }
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    if (b) {
+                      %composer.endToMarker(tmp1_marker)
+                      if (isTraceInProgress()) {
+                        traceEventEnd()
+                      }
+                      %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                        Test(a, b, %composer, updateChangedFlags(%changed or 0b0001))
+                      }
+                      return
+                    }
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(a, b, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testInline_CM3_RM3() = verifyInlineReturn(
+        """
+            @Composable
+            fun Test(condition: Boolean) {
+                A()
+                M3 {
+                    A()
+                    if (condition) {
+                        return@M3
+                    }
+                    A()
+                }
+                A()
+            }
+
+        """,
+        """
+            @Composable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test)<A()>,<M3>,<A()>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                val tmp0_marker = %composer.currentMarker
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    if (condition) {
+                      %composer.endToMarker(tmp0_marker)
+                    }
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testInline_Lambda() = verifyComposeIrTransform(
+        """
+            fun Test(condition: Boolean) {
+                T {
+                    compose {
+                        M1 {
+                            if (condition) return@compose
+                        }
+                    }
+                }
+            }
+        """,
+        """
+            fun Test(condition: Boolean) {
+              T {
+                %this%T.compose(composableLambdaInstance(<>, true) { %composer: Composer?, %changed: Int ->
+                  sourceInformation(%composer, "C<M1>:Test.kt")
+                  val tmp0_marker = %composer.currentMarker
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    if (isTraceInProgress()) {
+                      traceEventStart(<>, %changed, -1, <>)
+                    }
+                    M1({ %composer: Composer?, %changed: Int ->
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "C:Test.kt")
+                      if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                        if (condition) {
+                          %composer.endToMarker(tmp0_marker)
+                          if (isTraceInProgress()) {
+                            traceEventEnd()
+                          }
+                        }
+                      } else {
+                        %composer.skipToGroupEnd()
+                      }
+                      %composer.endReplaceableGroup()
+                    }, %composer, 0)
+                    if (isTraceInProgress()) {
+                      traceEventEnd()
+                    }
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                }
+                )
+              }
+            }
+        """,
+        """
+            import androidx.compose.runtime.*
+
+            class Scope {
+                fun compose(block: @Composable () -> Unit) { }
+            }
+
+            fun T(block: suspend Scope.() -> Unit) { }
+
+            @Composable
+            inline fun M1(block: @Composable () -> Unit) { block() }
+
+            @Composable
+            fun Text(text: String) { }
+        """
+    )
+
+    @Test
+    fun testInline_M3_M1_Return_M1() = verifyInlineReturn(
+        """
+            @Composable
+            fun Test_M3_M1_Return_M1(condition: Boolean) {
+                A()
+                M3 {
+                    A()
+                    M1 {
+                        if (condition) {
+                            return@M1
+                        }
+                    }
+                    A()
+                }
+                A()
+            }
+        """,
+        """
+            @Composable
+            fun Test_M3_M1_Return_M1(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test_M3_M1_Return_M1)<A()>,<M3>,<A()>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    val tmp0_marker = %composer.currentMarker
+                    M1({ %composer: Composer?, %changed: Int ->
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "C:Test.kt")
+                      if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                        if (condition) {
+                          %composer.endToMarker(tmp0_marker)
+                        }
+                      } else {
+                        %composer.skipToGroupEnd()
+                      }
+                      %composer.endReplaceableGroup()
+                    }, %composer, 0)
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test_M3_M1_Return_M1(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testInline_M3_M1_Return_M3() = verifyInlineReturn(
+        """
+            @Composable
+            fun Test_M3_M1_Return_M3(condition: Boolean) {
+                A()
+                M3 {
+                    A()
+                    M1 {
+                        if (condition) {
+                            return@M3
+                        }
+                    }
+                    A()
+                }
+                A()
+            }
+        """,
+        """
+            @Composable
+            fun Test_M3_M1_Return_M3(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(Test_M3_M1_Return_M3)<A()>,<M3>,<A()>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                val tmp0_marker = %composer.currentMarker
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    M1({ %composer: Composer?, %changed: Int ->
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "C:Test.kt")
+                      if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                        if (condition) {
+                          %composer.endToMarker(tmp0_marker)
+                        }
+                      } else {
+                        %composer.skipToGroupEnd()
+                      }
+                      %composer.endReplaceableGroup()
+                    }, %composer, 0)
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                Test_M3_M1_Return_M3(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testInline_M1_W_Return_Func() = verifyInlineReturn(
+        """
+            @Composable
+            fun testInline_M1_W_Return_Func(condition: Boolean) {
+                A()
+                M1 {
+                    A()
+                    while(true) {
+                        A()
+                        if (condition) {
+                            return
+                        }
+                        A()
+                    }
+                    A()
+                }
+                A()
+            }
+
+        """,
+        """
+            @Composable
+            fun testInline_M1_W_Return_Func(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(testInline_M1_W_Return_Func)<A()>,<M1>,<A()>:Test.kt")
+              val tmp1_marker = %composer.currentMarker
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                M1({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "*<A()>,<A()>")
+                    while (true) {
+                      A(%composer, 0)
+                      if (condition) {
+                        %composer.endToMarker(tmp1_marker)
+                        if (isTraceInProgress()) {
+                          traceEventEnd()
+                        }
+                        %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                          testInline_M1_W_Return_Func(condition, %composer, updateChangedFlags(%changed or 0b0001))
+                        }
+                        return
+                      }
+                      A(%composer, 0)
+                    }
+                    %composer.endReplaceableGroup()
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                testInline_M1_W_Return_Func(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun testInline_CM3_Return_M3_CM3_Return_M3() = verifyInlineReturn(
+        """
+            @Composable
+            fun testInline_M1_W_Return_Func(condition: Boolean) {
+                A()
+                M3 {
+                    A()
+                    if (condition) {
+                        return@M3
+                    }
+                    A()
+                }
+                M3 {
+                    A()
+                    if (condition) {
+                        return@M3
+                    }
+                    A()
+                }
+                A()
+            }
+
+        """,
+        """
+            @Composable
+            fun testInline_M1_W_Return_Func(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(testInline_M1_W_Return_Func)<A()>,<M3>,<M3>,<A()>:Test.kt")
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                A(%composer, 0)
+                val tmp0_marker = %composer.currentMarker
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    if (condition) {
+                      %composer.endToMarker(tmp0_marker)
+                    }
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                val tmp1_marker = %composer.currentMarker
+                M3({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    A(%composer, 0)
+                    if (condition) {
+                      %composer.endToMarker(tmp1_marker)
+                    }
+                    A(%composer, 0)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                A(%composer, 0)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                testInline_M1_W_Return_Func(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
+    @Test
+    fun test_CM1_CCM1_RetFun(): Unit = verifyInlineReturn(
+        """
+            @Composable
+            fun test_CM1_CCM1_RetFun(condition: Boolean) {
+                Text("Root - before")
+                M1 {
+                    Text("M1 - begin")
+                    if (condition) {
+                        Text("if - begin")
+                        M1 {
+                            Text("In CCM1")
+                            return@test_CM1_CCM1_RetFun
+                        }
+                    }
+                    Text("M1 - end")
+                }
+                Text("Root - end")
+            }
+
+        """,
+        """
+            @Composable
+            fun test_CM1_CCM1_RetFun(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer = %composer.startRestartGroup(<>)
+              sourceInformation(%composer, "C(test_CM1_CCM1_RetFun)<Text("...>,<M1>,<Text("...>:Test.kt")
+              val tmp1_marker = %composer.currentMarker
+              val %dirty = %changed
+              if (%changed and 0b1110 === 0) {
+                %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
+              }
+              if (%dirty and 0b1011 !== 0b0010 || !%composer.skipping) {
+                if (isTraceInProgress()) {
+                  traceEventStart(<>, %changed, -1, <>)
+                }
+                Text("Root - before", %composer, 0b0110)
+                M1({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
+                  if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                    Text("M1 - begin", %composer, 0b0110)
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "<Text("...>,<M1>")
+                    if (condition) {
+                      Text("if - begin", %composer, 0b0110)
+                      M1({ %composer: Composer?, %changed: Int ->
+                        %composer.startReplaceableGroup(<>)
+                        sourceInformation(%composer, "C<Text("...>:Test.kt")
+                        if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+                          Text("In CCM1", %composer, 0b0110)
+                          %composer.endToMarker(tmp1_marker)
+                          if (isTraceInProgress()) {
+                            traceEventEnd()
+                          }
+                          %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                            test_CM1_CCM1_RetFun(condition, %composer, updateChangedFlags(%changed or 0b0001))
+                          }
+                          return
+                        } else {
+                          %composer.skipToGroupEnd()
+                        }
+                        %composer.endReplaceableGroup()
+                      }, %composer, 0)
+                    }
+                    %composer.endReplaceableGroup()
+                    Text("M1 - end", %composer, 0b0110)
+                  } else {
+                    %composer.skipToGroupEnd()
+                  }
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                Text("Root - end", %composer, 0b0110)
+                if (isTraceInProgress()) {
+                  traceEventEnd()
+                }
+              } else {
+                %composer.skipToGroupEnd()
+              }
+              %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+                test_CM1_CCM1_RetFun(condition, %composer, updateChangedFlags(%changed or 0b0001))
+              }
+            }
+        """
+    )
+
     @Test
     fun testInlineReturnLabel(): Unit = controlFlow(
         """
@@ -246,11 +968,13 @@
               if (isTraceInProgress()) {
                 traceEventStart(<>, %changed, -1, <>)
               }
+              val tmp0_marker = %composer.currentMarker
               FakeBox({ %composer: Composer?, %changed: Int ->
                 %composer.startReplaceableGroup(<>)
                 sourceInformation(%composer, "C<A()>:Test.kt")
                 if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                   if (condition) {
+                    %composer.endToMarker(tmp0_marker)
                   }
                   A(%composer, 0)
                 } else {
@@ -2660,6 +3384,7 @@
             fun WithReturn(%composer: Composer?, %changed: Int) {
               %composer.startReplaceableGroup(<>)
               sourceInformation(%composer, "C(WithReturn)<A()>:Test.kt")
+              val tmp0_marker = %composer.currentMarker
               if (isTraceInProgress()) {
                 traceEventStart(<>, %changed, -1, <>)
               }
@@ -2667,7 +3392,7 @@
               sourceInformation(%composer, "*<A()>")
               run {
                 A(%composer, 0)
-                %composer.endReplaceableGroup()
+                %composer.endToMarker(tmp0_marker)
                 if (isTraceInProgress()) {
                   traceEventEnd()
                 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt
index aaa16df..07f487d 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/KtxCrossModuleTests.kt
@@ -1080,7 +1080,7 @@
             instanceClass ?: error("Could not find class $mainClassName in loaded classes")
         }
 
-        val instanceOfClass = instanceClass.newInstance()
+        val instanceOfClass = instanceClass.getDeclaredConstructor().newInstance()
         val advanceMethod = instanceClass.getMethod("advance")
         val composeMethod = instanceClass.getMethod(
             "compose",
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RunComposableTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RunComposableTests.kt
index 37041d7..5390e8c 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RunComposableTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RunComposableTests.kt
@@ -142,7 +142,7 @@
             instanceClass ?: error("Could not find class $className in loaded classes")
         }
 
-        val instanceOfClass = instanceClass.newInstance()
+        val instanceOfClass = instanceClass.getDeclaredConstructor().newInstance()
         val testMethod = instanceClass.getMethod(
             "test",
             *emptyArray(),
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
index 5eded68..4159214 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
@@ -118,6 +118,7 @@
             fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
               sourceInformation(%composer, "C(Test)<A()>,<Wrappe...>,<A()>:Test.kt")
+              val tmp1_marker = %composer.currentMarker
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(condition)) 0b0100 else 0b0010
@@ -133,6 +134,7 @@
                   if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
                     A(%composer, 0)
                     if (!condition) {
+                      %composer.endToMarker(tmp1_marker)
                       if (isTraceInProgress()) {
                         traceEventEnd()
                       }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
index 35cbb05..bd3e9b5 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/KtxNameConventions.kt
@@ -20,4 +20,6 @@
     val TRACE_EVENT_END = "traceEventEnd"
     val SOURCEINFORMATIONMARKEREND = "sourceInformationMarkerEnd"
     val UPDATE_CHANGED_FLAGS = "updateChangedFlags"
+    val CURRENTMARKER = Name.identifier("currentMarker")
+    val ENDTOMARKER = Name.identifier("endToMarker")
 }
\ No newline at end of file
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 4067649..96d4611 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -601,6 +601,24 @@
             }
     }
 
+    private val currentMarkerProperty: IrProperty? by guardedLazy {
+        composerIrClass.properties
+            .firstOrNull {
+                it.name == KtxNameConventions.CURRENTMARKER
+            }
+    }
+
+    private val endToMarkerFunction: IrSimpleFunction? by guardedLazy {
+        composerIrClass
+            .functions
+            .firstOrNull {
+                it.name == KtxNameConventions.ENDTOMARKER && it.valueParameters.size == 1
+            }
+    }
+
+    private val rollbackGroupMarkerEnabled get() =
+        currentMarkerProperty != null && endToMarkerFunction != null
+
     private val endRestartGroupFunction by guardedLazy {
         composerIrClass
             .functions
@@ -926,6 +944,7 @@
                         )
                     else -> null
                 },
+                *scope.markerPreamble.statements.toTypedArray(),
                 *bodyPreamble.statements.toTypedArray(),
                 *transformed.statements.toTypedArray(),
                 when {
@@ -1008,7 +1027,7 @@
 
         scope.dirty = dirty
 
-        val (nonReturningBody, returnVar) = body.asBodyAndResultVar()
+        val (nonReturningBody, returnVar) = body.asBodyAndResultVar(declaration)
 
         val emitTraceMarkers = traceEventMarkersEnabled && !scope.isInlinedLambda
 
@@ -1076,6 +1095,7 @@
                         irStartReplaceableGroup(body, scope, irFunctionSourceKey())
                     else null,
                     *sourceInformationPreamble.statements.toTypedArray(),
+                    *scope.markerPreamble.statements.toTypedArray(),
                     *skipPreamble.statements.toTypedArray(),
                     *bodyPreamble.statements.toTypedArray(),
                     transformedBody,
@@ -1091,6 +1111,7 @@
                 body.endOffset,
                 listOfNotNull(
                     *sourceInformationPreamble.statements.toTypedArray(),
+                    *scope.markerPreamble.statements.toTypedArray(),
                     *skipPreamble.statements.toTypedArray(),
                     *bodyPreamble.statements.toTypedArray(),
                     transformed,
@@ -1265,6 +1286,7 @@
                     scope,
                     irFunctionSourceKey()
                 ),
+                *scope.markerPreamble.statements.toTypedArray(),
                 *skipPreamble.statements.toTypedArray(),
                 transformedBody,
                 if (returnVar == null) end() else null,
@@ -1827,6 +1849,9 @@
         )
     }
 
+    fun irCurrentMarker() =
+        irMethodCall(irCurrentComposer(), currentMarkerProperty!!.getter!!)
+
     private fun irIsSkipping() =
         irMethodCall(irCurrentComposer(), isSkippingFunction.getter!!)
     private fun irDefaultsInvalid() =
@@ -1869,7 +1894,9 @@
         }
     }
 
-    private fun IrBody.asBodyAndResultVar(): Pair<IrContainerExpression, IrVariable?> {
+    private fun IrBody.asBodyAndResultVar(
+        expectedTarget: IrFunction? = null
+    ): Pair<IrContainerExpression, IrVariable?> {
         val original = IrCompositeImpl(
             startOffset,
             endOffset,
@@ -1880,7 +1907,10 @@
         var block: IrStatementContainer? = original
         var expr: IrStatement? = block?.statements?.lastOrNull()
         while (expr != null && block != null) {
-            if (expr is IrReturn) {
+            if (
+                expr is IrReturn &&
+                (expectedTarget == null || expectedTarget == expr.returnTargetSymbol.owner)
+            ) {
                 block.statements.pop()
                 return if (expr.value.type.isUnitOrNullableUnit() ||
                     expr.value.type.isNothing() ||
@@ -2238,6 +2268,12 @@
         return irMethodCall(irCurrentComposer(), endMovableFunction)
     }
 
+    private fun irEndToMarker(marker: IrExpression): IrExpression {
+        return irMethodCall(irCurrentComposer(), endToMarkerFunction!!).apply {
+            putValueArgument(0, marker)
+        }
+    }
+
     private fun irJoinKeyChain(keyExprs: List<IrExpression>): IrExpression {
         return keyExprs.reduce { accumulator, value ->
             irMethodCall(irCurrentComposer(), joinKeyFunction).apply {
@@ -2299,7 +2335,7 @@
         }
     }
 
-    private fun irTemporary(
+    fun irTemporary(
         value: IrExpression,
         nameHint: String? = null,
         irType: IrType = value.type,
@@ -2397,6 +2433,15 @@
         }
     }
 
+    private fun IrExpression.variablePrefix(variable: IrVariable) =
+        IrBlockImpl(
+            startOffset,
+            endOffset,
+            type,
+            null,
+            listOf(variable, this)
+        )
+
     private fun IrExpression.wrap(
         before: List<IrExpression> = emptyList(),
         after: List<IrExpression> = emptyList()
@@ -2452,15 +2497,7 @@
         )
     }
 
-    private fun mutableStatementContainer(): IrContainerExpression {
-        // NOTE(lmr): It's important to use IrComposite here so that we don't introduce any new
-        // scopes
-        return IrCompositeImpl(
-            UNDEFINED_OFFSET,
-            UNDEFINED_OFFSET,
-            context.irBuiltIns.unitType
-        )
-    }
+    private fun mutableStatementContainer() = mutableStatementContainer(context)
 
     private fun encounteredComposableCall(withGroups: Boolean, isCached: Boolean) {
         var scope: Scope? = currentScope
@@ -2546,6 +2583,9 @@
                     scope.markCoalescableGroup(coalescableScope, realizeGroup, makeEnd)
                     break@loop
                 }
+                is Scope.CallScope -> {
+                    // Ignore
+                }
                 else -> error("Unexpected scope type")
             }
             scope = scope.parent
@@ -2557,16 +2597,43 @@
         extraEndLocation: (IrExpression) -> Unit
     ) {
         var scope: Scope? = currentScope
+        val blockScopeMarks = mutableListOf<Scope.BlockScope>()
+        var leavingInlinedLambda = false
         loop@ while (scope != null) {
             when (scope) {
                 is Scope.FunctionScope -> {
                     if (scope.function == symbol.owner) {
-                        scope.markReturn(extraEndLocation)
+                        if (
+                            !(
+                                leavingInlinedLambda ||
+                                (scope.isInlinedLambda && scope.inComposableCall)
+                            ) ||
+                            !rollbackGroupMarkerEnabled
+                        ) {
+                            blockScopeMarks.forEach {
+                                it.markReturn(extraEndLocation)
+                            }
+                            scope.markReturn(extraEndLocation)
+                        } else {
+                            val functionScope = scope
+                            if (functionScope.isInlinedLambda) {
+                                val marker = irGet(functionScope.allocateMarker())
+                                extraEndLocation(irEndToMarker(marker))
+                            } else {
+                                functionScope.markReturn {
+                                    val marker = irGet(functionScope.allocateMarker())
+                                    extraEndLocation(irEndToMarker(marker))
+                                    extraEndLocation(it)
+                                }
+                            }
+                        }
+
                         break@loop
                     }
+                    if (scope.isInlinedLambda) leavingInlinedLambda = true
                 }
                 is Scope.BlockScope -> {
-                    scope.markReturn(extraEndLocation)
+                    blockScopeMarks.add(scope)
                 }
                 else -> {
                     /* Do nothing, continue traversing */
@@ -2816,9 +2883,14 @@
             withGroups = !expression.symbol.owner.isReadonly(),
             isCached = false
         )
+
+        val callScope = Scope.CallScope(expression, this)
+
         // it's important that we transform all of the parameters here since this will cause the
         // IrGetValue's of remapped default parameters to point to the right variable.
-        expression.transformChildrenVoid()
+        inScope(callScope) {
+            expression.transformChildrenVoid()
+        }
 
         val ownerFn = expression.symbol.owner
         val numValueParams = ownerFn.valueParameters.size
@@ -2940,7 +3012,9 @@
         )
         recordCallInSource(call = expression)
 
-        return expression
+        return callScope.marker?.let {
+            expression.variablePrefix(it)
+        } ?: expression
     }
 
     private fun canElideRememberGroup(): Boolean {
@@ -3655,6 +3729,13 @@
             val isInlinedLambda: Boolean
                 get() = transformer.inlineLambdaInfo.isInlineLambda(function)
 
+            val inComposableCall: Boolean
+                get() = (parent as? Scope.CallScope)?.expression?.let { call ->
+                    with(transformer) {
+                        call.isTransformedComposableCall() || call.isSyntheticComposableCall()
+                    }
+                } == true
+
             val metrics: FunctionMetrics = transformer.metricsFor(function)
 
             private var lastTemporaryIndex: Int = 0
@@ -3688,6 +3769,21 @@
 
             var dirty: IrChangedBitMaskValue? = null
 
+            val markerPreamble = mutableStatementContainer(transformer.context)
+            private var marker: IrVariable? = null
+
+            fun allocateMarker(): IrVariable = marker ?: run {
+                val parent = parent
+                return (if (isInlinedLambda && parent is Scope.CallScope) {
+                    parent.allocateMarker()
+                } else transformer.irTemporary(
+                    transformer.irCurrentMarker(),
+                    getNameForTemporary("marker")
+                ).also { markerPreamble.statements.add(it) }).also {
+                    marker = it
+                }
+            }
+
             // Parameter information is an index from the sorted order of the parameters to the
             // actual order. This is used to reorder the fields of the lambda class generated for
             // restart lambdas into parameter order. If all the parameters are in sorted order
@@ -4124,6 +4220,25 @@
                 }
         }
         class ParametersScope : BlockScope("parameters")
+
+        class CallScope(
+            val expression: IrCall,
+            private val transformer: ComposableFunctionBodyTransformer
+        ) : Scope("call") {
+            var marker: IrVariable? = null
+                private set
+
+            fun allocateMarker(): IrVariable = marker
+                ?: transformer.irTemporary(
+                    transformer.irCurrentMarker(),
+                    getNameForTemporary("marker")
+                ).also { marker = it }
+
+            private fun getNameForTemporary(nameHint: String?) =
+                functionScope?.getNameForTemporary(nameHint)
+                    ?: error("Expected to be in a function")
+        }
+
         class ComposableLambdaScope : BlockScope("composableLambda") {
             override fun calculateHasSourceInformation(sourceInformationEnabled: Boolean): Boolean {
                 return sourceInformationEnabled
@@ -4484,3 +4599,13 @@
     value(property.name)
 
 private fun <T> guardedLazy(initializer: () -> T) = GuardedLazy<T>(initializer)
+
+private fun mutableStatementContainer(context: IrPluginContext): IrContainerExpression {
+    // NOTE(lmr): It's important to use IrComposite here so that we don't introduce any new
+    // scopes
+    return IrCompositeImpl(
+        UNDEFINED_OFFSET,
+        UNDEFINED_OFFSET,
+        context.irBuiltIns.unitType
+    )
+}
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
new file mode 100644
index 0000000..801ddf6
--- /dev/null
+++ b/compose/runtime/runtime/api/current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.runtime.Composer#endToMarker(int):
+    Added method androidx.compose.runtime.Composer.endToMarker(int)
+AddedAbstractMethod: androidx.compose.runtime.Composer#getCurrentMarker():
+    Added method androidx.compose.runtime.Composer.getCurrentMarker()
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index f4bfd21..4210434 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -120,11 +120,13 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endToMarker(int marker);
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
     method public androidx.compose.runtime.tooling.CompositionData getCompositionData();
     method public int getCompoundKeyHash();
+    method public int getCurrentMarker();
     method public boolean getDefaultsInvalid();
     method public boolean getInserting();
     method public androidx.compose.runtime.RecomposeScope? getRecomposeScope();
@@ -151,6 +153,7 @@
     property @org.jetbrains.annotations.TestOnly public abstract androidx.compose.runtime.ControlledComposition composition;
     property public abstract androidx.compose.runtime.tooling.CompositionData compositionData;
     property public abstract int compoundKeyHash;
+    property public abstract int currentMarker;
     property public abstract boolean defaultsInvalid;
     property public abstract boolean inserting;
     property public abstract androidx.compose.runtime.RecomposeScope? recomposeScope;
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 6dca9cb..ce05da6 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -128,11 +128,13 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endToMarker(int marker);
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
     method public androidx.compose.runtime.tooling.CompositionData getCompositionData();
     method public int getCompoundKeyHash();
+    method public int getCurrentMarker();
     method public boolean getDefaultsInvalid();
     method public boolean getInserting();
     method public androidx.compose.runtime.RecomposeScope? getRecomposeScope();
@@ -164,6 +166,7 @@
     property @org.jetbrains.annotations.TestOnly public abstract androidx.compose.runtime.ControlledComposition composition;
     property public abstract androidx.compose.runtime.tooling.CompositionData compositionData;
     property public abstract int compoundKeyHash;
+    property public abstract int currentMarker;
     property public abstract boolean defaultsInvalid;
     property public abstract boolean inserting;
     property public abstract androidx.compose.runtime.RecomposeScope? recomposeScope;
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
new file mode 100644
index 0000000..801ddf6
--- /dev/null
+++ b/compose/runtime/runtime/api/restricted_current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.runtime.Composer#endToMarker(int):
+    Added method androidx.compose.runtime.Composer.endToMarker(int)
+AddedAbstractMethod: androidx.compose.runtime.Composer#getCurrentMarker():
+    Added method androidx.compose.runtime.Composer.getCurrentMarker()
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 69872de..6fcdd3c 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -122,11 +122,13 @@
     method @androidx.compose.runtime.ComposeCompilerApi public void endReplaceableGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public androidx.compose.runtime.ScopeUpdateScope? endRestartGroup();
     method @androidx.compose.runtime.ComposeCompilerApi public void endReusableGroup();
+    method @androidx.compose.runtime.ComposeCompilerApi public void endToMarker(int marker);
     method public androidx.compose.runtime.Applier<?> getApplier();
     method @org.jetbrains.annotations.TestOnly public kotlin.coroutines.CoroutineContext getApplyCoroutineContext();
     method @org.jetbrains.annotations.TestOnly public androidx.compose.runtime.ControlledComposition getComposition();
     method public androidx.compose.runtime.tooling.CompositionData getCompositionData();
     method public int getCompoundKeyHash();
+    method public int getCurrentMarker();
     method public boolean getDefaultsInvalid();
     method public boolean getInserting();
     method public androidx.compose.runtime.RecomposeScope? getRecomposeScope();
@@ -153,6 +155,7 @@
     property @org.jetbrains.annotations.TestOnly public abstract androidx.compose.runtime.ControlledComposition composition;
     property public abstract androidx.compose.runtime.tooling.CompositionData compositionData;
     property public abstract int compoundKeyHash;
+    property public abstract int currentMarker;
     property public abstract boolean defaultsInvalid;
     property public abstract boolean inserting;
     property public abstract androidx.compose.runtime.RecomposeScope? recomposeScope;
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 318a06f..dcbc8dc 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -724,6 +724,25 @@
     /**
      * A Compose compiler plugin API. DO NOT call directly.
      *
+     * Return a marker for the current group that can be used in a call to [endToMarker].
+     */
+    @ComposeCompilerApi
+    val currentMarker: Int
+
+    /**
+     *  Compose compiler plugin API. DO NOT call directly.
+     *
+     *  Ends all the groups up to but not including the group that is the parent group when
+     *  [currentMarker] was called to produce [marker]. All groups ended must have been started with
+     *  either [startReplaceableGroup] or [startMovableGroup]. Ending other groups can cause the
+     *  state of the composer to become inconsistent.
+     */
+    @ComposeCompilerApi
+    fun endToMarker(marker: Int)
+
+    /**
+     * A Compose compiler plugin API. DO NOT call directly.
+     *
      * Schedule [block] to called with [value]. This is intended to update the node generated by
      * [createNode] to changes discovered by composition.
      *
@@ -1627,6 +1646,18 @@
         reusing = reusingGroup >= 0
     }
 
+    override val currentMarker: Int
+        get() = if (inserting) -writer.parent else reader.parent
+
+    override fun endToMarker(marker: Int) {
+        if (marker < 0) {
+            val writerLocation = -marker
+            while (writer.parent > writerLocation) end(false)
+        } else {
+            while (reader.parent > marker) end(false)
+        }
+    }
+
     /**
      * Schedule a change to be applied to a node's property. This change will be applied to the
      * node that is the current node in the tree which was either created by [createNode].
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 82ba839..285838a 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -3232,6 +3232,130 @@
         advance()
         assertEquals(state, functionInstance())
     }
+
+    @Test
+    fun testNonLocalReturn_CM1_RetFunc_FalseTrue() = compositionTest {
+        var condition by mutableStateOf(false)
+
+        compose {
+            test_CM1_RetFun(condition)
+        }
+
+        validate {
+            this.test_CM1_RetFun(condition)
+        }
+
+        condition = true
+
+        expectChanges()
+
+        revalidate()
+    }
+
+    @Test
+    fun testNonLocalReturn_CM1_RetFunc_TrueFalse() = compositionTest {
+        var condition by mutableStateOf(true)
+
+        compose {
+            test_CM1_RetFun(condition)
+        }
+
+        validate {
+            this.test_CM1_RetFun(condition)
+        }
+
+        condition = false
+
+        expectChanges()
+
+        revalidate()
+    }
+
+    @Test
+    fun test_CM1_CCM1_RetFun_FalseTrue() = compositionTest {
+        var condition by mutableStateOf(false)
+
+        compose {
+            test_CM1_CCM1_RetFun(condition)
+        }
+
+        validate {
+            this.test_CM1_CCM1_RetFun(condition)
+        }
+
+        condition = true
+
+        expectChanges()
+
+        revalidate()
+    }
+
+    @Test
+    fun test_CM1_CCM1_RetFun_TrueFalse() = compositionTest {
+        var condition by mutableStateOf(true)
+
+        compose {
+            test_CM1_CCM1_RetFun(condition)
+        }
+
+        validate {
+            this.test_CM1_CCM1_RetFun(condition)
+        }
+
+        condition = false
+
+        expectChanges()
+
+        revalidate()
+    }
+}
+
+@Composable
+fun test_CM1_RetFun(condition: Boolean) {
+    Text("Root - before")
+    M1 {
+        Text("M1 - before")
+        if (condition) return
+        Text("M1 - after")
+    }
+    Text("Root - after")
+}
+
+fun MockViewValidator.test_CM1_RetFun(condition: Boolean) {
+    Text("Root - before")
+    Text("M1 - before")
+    if (condition) return
+    Text("M1 - after")
+    Text("Root - after")
+}
+
+@Composable
+fun test_CM1_CCM1_RetFun(condition: Boolean) {
+    Text("Root - before")
+    M1 {
+        Text("M1 - begin")
+        if (condition) {
+            Text("if - begin")
+            M1 {
+                Text("In CCM1")
+                return@test_CM1_CCM1_RetFun
+            }
+        }
+        Text("M1 - end")
+    }
+    Text("Root - end")
+}
+
+fun MockViewValidator.test_CM1_CCM1_RetFun(condition: Boolean) {
+    Text("Root - before")
+    Text("M1 - begin")
+    if (condition) {
+        Text("if - begin")
+        Text("In CCM1")
+        return
+    }
+    Text("M1 - end")
+    Text("Root - end")
 }
 
 var functionInstance: () -> Int = { 0 }
@@ -3391,4 +3515,16 @@
     require(aIndex >= 0 && bIndex >= 0)
     set(aIndex, b)
     set(bIndex, a)
-}
\ No newline at end of file
+}
+
+@Composable
+private inline fun InlineWrapper(content: @Composable () -> Unit) = content()
+
+@Composable
+private inline fun M1(content: @Composable () -> Unit) = InlineWrapper { content() }
+
+@Composable
+private inline fun M2(content: @Composable () -> Unit) = M1 { content() }
+
+@Composable
+private inline fun M3(content: @Composable () -> Unit) = M2 { content() }
diff --git a/core/core/src/main/java/androidx/core/accessibilityservice/OWNERS b/core/core/src/main/java/androidx/core/accessibilityservice/OWNERS
index 0a785d6..d29d58d 100644
--- a/core/core/src/main/java/androidx/core/accessibilityservice/OWNERS
+++ b/core/core/src/main/java/androidx/core/accessibilityservice/OWNERS
@@ -1,2 +1 @@
[email protected]
[email protected]
\ No newline at end of file
+include ../view/accessibility/OWNERS
\ No newline at end of file
diff --git a/core/core/src/main/java/androidx/core/view/OWNERS b/core/core/src/main/java/androidx/core/view/OWNERS
index db3e50a..abaadb7 100644
--- a/core/core/src/main/java/androidx/core/view/OWNERS
+++ b/core/core/src/main/java/androidx/core/view/OWNERS
@@ -1 +1 @@
-per-file AccessibilityDelegateCompat.java = [email protected], [email protected]
\ No newline at end of file
+per-file AccessibilityDelegateCompat.java = file:accessibility/OWNERS
\ No newline at end of file
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java
index 74a0ae7..7b38d7d 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityEventCompat.java
@@ -93,6 +93,28 @@
 
     /**
      * Represents the event of an application making an announcement.
+     *
+     * <p>
+     * Note: This event carries no semantic meaning and is appropriate only in exceptional
+     * situations. Apps can generally achieve correct behavior for accessibility by
+     * accurately supplying the semantics of their UI. They should not need to specify what
+     * exactly is announced to users.
+     *
+     * <p>
+     * In general, only announce transitions that can't be handled by the following preferred
+     * alternatives:
+     * <ul>
+     *     <li>Label your controls concisely and precisely. Don’t generate a confirmation message
+     *     for simple actions like a button press.</li>
+     *     <li>For significant UI changes like window changes, use
+     *     {@link android.app.Activity#setTitle(CharSequence)} and
+     *     {@link androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)} )}.
+     *     </li>
+     *     <li>Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
+     *     to inform the user of changes to critical views within the user interface. These
+     *     should still be used sparingly as they may generate announcements every time a View is
+     *     updated.</li>
+     * </ul>
      */
     public static final int TYPE_ANNOUNCEMENT = 0x00004000;
 
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
index 162df8f..5db4c9d 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -811,6 +811,10 @@
      * A vertical tree is a hierarchical collection with one column and
      * as many rows as the first level children.
      * </p>
+     * <p>
+     * To be a valid list, a collection has 1 row and any number of columns or 1 column and any
+     * number of rows.
+     * </p>
      */
     public static class CollectionInfoCompat {
         /** Selection mode where items are not selectable. */
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/OWNERS b/core/core/src/main/java/androidx/core/view/accessibility/OWNERS
index 0a785d6..1c92d03 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/OWNERS
+++ b/core/core/src/main/java/androidx/core/view/accessibility/OWNERS
@@ -1,2 +1,9 @@
+# Bug component: 1110922
+
[email protected]
 [email protected]
[email protected]
\ No newline at end of file
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
\ No newline at end of file
diff --git a/datastore/datastore-multiprocess/src/main/java/androidx/datastore/multiprocess/MultiProcessDataStore.kt b/datastore/datastore-multiprocess/src/main/java/androidx/datastore/multiprocess/MultiProcessDataStore.kt
index 1769116..3dc1b02 100644
--- a/datastore/datastore-multiprocess/src/main/java/androidx/datastore/multiprocess/MultiProcessDataStore.kt
+++ b/datastore/datastore-multiprocess/src/main/java/androidx/datastore/multiprocess/MultiProcessDataStore.kt
@@ -185,7 +185,7 @@
             // It will be triggered by same-process-write as well. Shared memory version check will
             // prevent it from reading again. parameter `path` is relative to the observed directory
             override fun onEvent(event: Int, path: String?) {
-                if ((downstreamFlow.value !is Final) && (path!! == file.name)) {
+                if ((downstreamFlow.value !is Final) && (file.name == path)) {
                     readActor.offer(Message.Read(downstreamFlow.value))
                 }
             }
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 92aa50e..a9e5ab6 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -230,129 +230,6 @@
 WARN: Missing @param tag for parameter `text` of function androidx\.compose\.ui\.text/AnnotatedString\.Builder/append/\#androidx\.compose\.ui\.text\.AnnotatedString\#kotlin\.Int\#kotlin\.Int/PointingToDeclaration/
 WARNING: link to @throws type Renderer\.GlesException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name,  e\.g\.`@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root=CustomDocTag\(children=\[P\(children=\[Text\(body=If any GL calls fail during initialization\., children=\[\], params=\{\}\)\], params=\{\}\)\], params=\{\}, name=MARKDOWN_FILE\), name=Renderer\.GlesException, exceptionAddress=null\)\.`
 WARNING: link to @throws type ServiceStartFailureException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name,  e\.g\.`@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root=CustomDocTag\(children=\[P\(children=\[Text\(body=if the watchface dies during startup\., children=\[\], params=\{\}\)\], params=\{\}\)\], params=\{\}, name=MARKDOWN_FILE\), name=ServiceStartFailureException, exceptionAddress=null\)\.`
-# Wire proto generation, task :generateDebugProtos
-Writing .* to \$OUT_DIR/.*/build/generated/source/wire
-# > Task :compose:ui:ui-tooling:processDebugAndroidTestManifest
-\$SUPPORT/compose/ui/ui\-tooling/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-dagger\.lint\.DaggerIssueRegistry in .*/lint\.jar does not specify a vendor; see IssueRegistry#vendor
-# > Task :compose:foundation:foundation:androidReleaseSourcesJar
-Encountered duplicate path "android[a-zA-Z]*/.+" during copy operation configured with DuplicatesStrategy\.WARN
-# ./gradlew tasks warns as we have warnings present
-You can use \'\-\-warning\-mode all\' to show the individual deprecation warnings and determine if they come from your own scripts or plugins\.
-# > Task :emoji2:emoji2-bundled:processDebugAndroidTestManifest
-\$SUPPORT/emoji[0-9]+/emoji[0-9]+\-bundled/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-meta\-data\#androidx\.emoji[0-9]+\.text\.EmojiCompatInitializer was tagged at AndroidManifest\.xml:[0-9]+ to remove other declarations but no other declaration present
-# > Task :emoji2:emoji2-bundled:processDebugManifest
-\$SUPPORT/emoji[0-9]+/emoji[0-9]+\-bundled/src/main/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# b/195025261
-To honour the JVM settings for this build a single\-use Daemon process will be forked.*
-# > Tasks configureCMakeRelWithDebInfo or configureCMakeDebug
-C/C\+\+: Building ver\.\: [0-9]+\.[0-9]+\.[0-9]+
-C/C\+\+: Packaging for\: (amd\-[0-9]+|armhf\-[0-9]+|x86\-[0-9]+)
-C/C\+\+: Compiling for ARM
-w: \[ksp\] Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\. \- androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDb\.jvmDao\(\)
-w: \[ksp\] \$SUPPORT/room/integration\-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.kt:[0-9]+: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
-# > Task :room:integration-tests:room-testapp-kotlin:kaptWithKaptDebugAndroidTestKotlin
-\$OUT_DIR/androidx/room/integration\-tests/room\-testapp\-kotlin/build/tmp/kapt[0-9]+/stubs/withKaptDebugAndroidTest/androidx/room/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.java:[0-9]+: warning: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
-public abstract void jvmDelete\(T t\);
-public abstract void jvmInsert\(@org\.jetbrains\.annotations\.NotNull.*
-public abstract java\.util\.List<androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameEntity> jvmQuery\(\);
-public abstract androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDao jvmDao\(\);
-\^
-# Gradle will log if you are not authenticated to upload scans
-A build scan was not published as you have not authenticated with server 'ge\.androidx\.dev'\.
-# Room unresolved type error messages
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromAsset\(kotlin\.String\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromAsset\(kotlin\.String\,\ androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromFile\(java\.io\.File\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromFile\(java\.io\.File\,\ androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromInputStream\(java\.util\.concurrent\.Callable\(\(java\.io\.InputStream\)\)\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromInputStream\(java\.util\.concurrent\.Callable\(\(java\.io\.InputStream\)\), androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$openHelperFactory\(androidx\.sqlite\.db\.SupportSQLiteOpenHelper\.Factory\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addMigrations\(kotlin\.Array\(\(androidx\.room\.migration\.Migration\)\)\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addAutoMigrationSpec\(androidx\.room\.migration\.AutoMigrationSpec\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$allowMainThreadQueries\(\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setJournalMode\(androidx\.room\.RoomDatabase\.JournalMode\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setQueryExecutor\(java\.util\.concurrent\.Executor\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setTransactionExecutor\(java\.util\.concurrent\.Executor\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$enableMultiInstanceInvalidation\(\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setMultiInstanceInvalidationServiceIntent\(android\.content\.Intent\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$fallbackToDestructiveMigration\(\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$fallbackToDestructiveMigrationFrom\(kotlin\.IntArray\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$fallbackToDestructiveMigrationOnDowngrade\(\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addCallback\(androidx\.room\.RoomDatabase\.Callback\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setQueryCallback\(androidx\.room\.RoomDatabase\.QueryCallback, java\.util\.concurrent\.Executor\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addTypeConverter\(kotlin\.Any\) \(RoomDatabase\.kt:[0-9]+\)
-Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setAutoCloseTimeout\(kotlin\.Long, java\.util\.concurrent\.TimeUnit\) \(RoomDatabase\.kt:[0-9]+\)
-# > Task :compose:ui:ui:compileReleaseKotlin
-w: \$SUPPORT/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat\.android\.kt: \([0-9]+, [0-9]+\): Unnecessary non\-null assertion \(!!\) on a non\-null receiver of type LayoutNode
-# When konan is downloading a dependency from another file, don't warn about it.
-\(KonanProperies\) Downloading dependency: file:\.\./\.\./.*
-Please wait while Kotlin/Native compiler .* is being installed\.
-Unpack Kotlin/Native compiler to .*
-Download file:\.\./\.\./.*
-Downloading native dependencies \(LLVM, sysroot etc\)\. This is a one\-time action performed only on the first run of the compiler\.
-Extracting dependency: .*\.konan/cache.*
-# New memory model does not work with compiler caches yet:
-# https://github.com/JetBrains/kotlin/blob/master/kotlin-native/NEW_MM.md#known-bugs
-w: Cached libraries will not be used with experimental memory model
-# > Task :commonizeNativeDistribution
-# Linux builds cannot build mac, hence we get this warning.
-# see: https://github.com/JetBrains/kotlin/blob/master/native/commonizer/README.md
-# This warning is printed from: https://github.com/JetBrains/kotlin/blob/bc853e45e8982eff74e3263b0197c1af6086615d/native/commonizer/src/org/jetbrains/kotlin/commonizer/konan/LibraryCommonizer.kt#L41
-Warning\: No libraries found for target (macos|ios|ios_simulator)_(arm|x)[0-9]+\. This target will be excluded from commonization\.
-void androidx.tv.foundation.lazy.list.LazyListKt.LazyList(androidx.compose.ui.Modifier, androidx.tv.foundation.lazy.list.TvLazyListState, androidx.compose.foundation.layout.PaddingValues, boolean, boolean, boolean, androidx.tv.foundation.PivotOffsets, androidx.compose.ui.Alignment$Horizontal, androidx.compose.foundation.layout.Arrangement$Vertical, androidx.compose.ui.Alignment$Vertical, androidx.compose.foundation.layout.Arrangement$Horizontal, kotlin.jvm.functions.Function1, androidx.compose.runtime.Composer, int, int, int)
-void androidx.tv.foundation.lazy.grid.LazyGridKt.LazyGrid(androidx.compose.ui.Modifier, androidx.tv.foundation.lazy.grid.TvLazyGridState, kotlin.jvm.functions.Function2, androidx.compose.foundation.layout.PaddingValues, boolean, boolean, boolean, androidx.compose.foundation.layout.Arrangement$Vertical, androidx.compose.foundation.layout.Arrangement$Horizontal, androidx.tv.foundation.PivotOffsets, kotlin.jvm.functions.Function1, androidx.compose.runtime.Composer, int, int, int)
-# > Task :room:integration-tests:room-testapp:mergeDexWithExpandProjectionDebugAndroidTest
-WARNING:D[0-9]+: Application does not contain `androidx\.tracing\.Trace` as referenced in main\-dex\-list\.
-# > Task :hilt:hilt-compiler:kaptTestKotlin
-Annotation processors discovery from compile classpath is deprecated\.
-Set 'kapt\.include\.compile\.classpath=false' to disable discovery\.
-Run the build with '\-\-info' for more details\.
-# > Task :health:health-connect-client:testDebugUnitTest
-WARNING: An illegal reflective access operation has occurred
-WARNING: Illegal reflective access by org\.robolectric\.util\.ReflectionHelpers\$[0-9]+ \(file:\$CHECKOUT/prebuilts/androidx/external/org/robolectric/shadowapi/.*/shadowapi\-.*\.jar\) to field java\.io\.FileDescriptor\.fd
-WARNING: Illegal reflective access by org\.robolectric\.util\.ReflectionHelpers \(file:\$CHECKOUT/prebuilts/androidx/external/org/robolectric/shadowapi/.*/shadowapi\-.*\.jar\) to method java\.lang\.Class\.getDeclaredFields0\(boolean\)
-WARNING: Please consider reporting this to the maintainers of org\.robolectric\.util\.ReflectionHelpers.*
-WARNING: Illegal reflective access by org\.jetbrains\.kotlin\.kapt[0-9]+\.base\.javac\.KaptJavaFileManager \(file:\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-annotation\-processing\-embeddable/[0-9]+\.[0-9]+\.[0-9]+\-RC/kotlin\-annotation\-processing\-embeddable\-[0-9]+\.[0-9]+\.[0-9]+\-RC\.jar\) to method com\.sun\.tools\.javac\.file\.BaseFileManager\.handleOption\(com\.sun\.tools\.javac\.main\.Option,java\.lang\.String\)
-WARNING\: Illegal reflective access by org\.jetbrains\.kotlin\.kapt[0-9]+\.base\.javac\.KaptJavaFileManager \(file\:\$CHECKOUT\/prebuilts\/androidx\/external\/org\/jetbrains\/kotlin\/kotlin\-annotation\-processing\-embeddable\/[0-9]+\.[0-9]+\.[0-9]+\-Beta\/kotlin\-annotation\-processing\-embeddable\-[0-9]+\.[0-9]+\.[0-9]+\-Beta\.jar\) to method com\.sun\.tools\.javac\.file\.BaseFileManager\.handleOption\(com\.sun\.tools\.javac\.main\.Option\,java\.lang\.String\)
-WARNING: Illegal reflective access by org\.robolectric\.util\.ReflectionHelpers\$[0-9]+ \(file:\$CHECKOUT/prebuilts/androidx/external/org/robolectric/shadowapi/[0-9]+\.[0-9]+\.[0-9]+/shadowapi\-[0-9]+\.[0-9]+\.[0-9]+\.jar\) to field java\.io\.FileDescriptor\.fd
-WARNING: Use \-\-illegal\-access=warn to enable warnings of further illegal reflective access operations
-WARNING: All illegal access operations will be denied in a future release
-# > Task :room:room-compiler-processing-testing:test
-WARNING: Illegal reflective access by androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion \(file:\$OUT_DIR/androidx/room/room\-compiler\-processing/build/(classes/kotlin/main/|libs/room\-compiler\-processing\-[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta|rc)[0-9]+)?\.jar)\) to field com\.sun\.tools\.javac\.code\.Symbol\.owner
-WARNING: Please consider reporting this to the maintainers of androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion
-# > Task :room:room-compiler:test
-WARNING: Illegal reflective access by androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion \(file:\$OUT_DIR/androidx/room/room\-compiler\-processing/build/(classes/kotlin/main/|libs/room\-compiler\-processing\-[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta|rc)[0-9]+)?\.jar)\) to field com\.sun\.tools\.javac\.code\.Symbol\$ClassSymbol\.classfile
-# > Task :room:room-compiler-processing:test
-WARNING: Illegal reflective access by org\.jetbrains\.kotlin\.kapt[0-9]+\.base\.javac\.KaptJavaFileManager \(file:\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-annotation\-processing\-embeddable/[0-9]+\.[0-9]+\.[0-9]+/kotlin\-annotation\-processing\-embeddable\-[0-9]+\.[0-9]+\.[0-9]+\.jar\) to method com\.sun\.tools\.javac\.file\.BaseFileManager\.handleOption\(com\.sun\.tools\.javac\.main\.Option[,\.]java\.lang\.String\)
-WARNING: Please consider reporting this to the maintainers of org\.jetbrains\.kotlin\.kapt[0-9]+\.base\.javac\.KaptJavaFileManager
-# > Task :car:app:app-testing:testDebugUnitTest
-WARNING: Illegal reflective access by org\.robolectric\.interceptors\.AndroidInterceptors\$FileDescriptorInterceptor to field java\.io\.FileDescriptor\.fd
-WARNING: Please consider reporting this to the maintainers of org\.robolectric\.interceptors\.AndroidInterceptors\$FileDescriptorInterceptor
-# > Task :compose:compiler:compiler-hosted:integration-tests:testDebugUnitTest b/238915109
-WARNING: Illegal reflective access by androidx\.compose\.compiler\.plugins\.kotlin\.ComposeCodegenTestUtilsKt .* to method java\.lang\.ClassLoader\.defineClass.*
-WARNING: Please consider reporting this to the maintainers of androidx\.compose\.compiler\.plugins\.kotlin\.ComposeCodegenTestUtilsKt
-# > Task :buildSrc-tests:test
-WARNING: Illegal reflective access using Lookup on org\.gradle\.internal\.classloader\.ClassLoaderUtils\$AbstractClassLoaderLookuper .* to class java\.lang\.ClassLoader
-WARNING: Please consider reporting this to the maintainers of org\.gradle\.internal\.classloader\.ClassLoaderUtils\$AbstractClassLoaderLookuper
-# AGP warning about API usage we have no control over
-Values of variant API AnnotationProcessorOptions\.arguments are queried and may return non final values, this is unsupported
-# > Task :compose:ui:ui:testDebugUnitTest
-OpenJDK 64\-Bit Server VM warning:
-.*Sharing is only supported for boot loader classes because bootstrap classpath has been appended
-# > Task :concurrent:concurrent-futures:compileTestJava b/242311027
-\$SUPPORT/concurrent/concurrent\-futures/src/test/java/androidx/concurrent/futures/AbstractResolvableFutureTest\.java:[0-9]+: warning: \[removal\] (resume|suspend)\(\) in Thread has been deprecated and marked for removal
-thread\.(resume|suspend)\(\);
-2 warnings
-# AGP warning that will go away soon
-WARNING:Software Components will not be created automatically for Maven publishing from Android Gradle Plugin 8\.0\. To opt\-in to the future behavior, set the Gradle property android\.disableAutomaticComponentCreation=true in the `gradle\.properties` file or use the new publishing DSL\.
-# > Task :graphics:graphics-path:compileDebugKotlin
-w\: \$SUPPORT\/graphics\/graphics\-path\/src\/main\/java\/androidx\/graphics\/path\/Paths\.kt\: \([0-9]+\, [0-9]+\)\: Extension is shadowed by a member\: public open fun iterator\(\)\: PathIterator
-# > Task :core:core-splashscreen:core-splashscreen-samples:lintReportDebug
-Warning: Lint will treat :annotation:annotation as an external dependency and not analyze it\.
-Did you make a typo\? Are you trying to refer to something not visible to users\?
-\* Recommended Action: Apply the 'com\.android\.lint' plugin to java library project :annotation:annotation\. to enable lint to analyze those sources\.
 WARN: Sources for .+ is empty
 WARN: Missing @param tag for parameter `iterations` of function androidx\.benchmark\.macro\.junit[0-9]+/BaselineProfileRule/collectBaselineProfile/\#kotlin\.String\#kotlin\.Int\#kotlin\.collections\.List\[kotlin\.String\]\#kotlin\.Function[0-9]+\[androidx\.benchmark\.macro\.MacrobenchmarkScope,kotlin\.Unit\]/PointingToDeclaration/
 WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.camera[0-9]+\.interop/CaptureRequestOptions/retrieveOptionWithPriority/\#androidx\.camera\.core\.impl\.Config\.Option<ValueT>\#androidx\.camera\.core\.impl\.Config\.OptionPriority/PointingToDeclaration/
@@ -812,5 +689,101 @@
 WARN: Missing @param tag for parameter `clipOp` of function androidx\.compose\.ui\.graphics/Canvas/clipRect/\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#androidx\.compose\.ui\.graphics\.ClipOp/PointingToDeclaration/
 WARN: Missing @param tag for parameter `paint` of function androidx\.compose\.ui\.graphics/Canvas/drawArc/\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Boolean\#androidx\.compose\.ui\.graphics\.Paint/PointingToDeclaration/
 WARN: Missing @param tag for parameter `operation` of function androidx\.compose\.ui\.graphics/AndroidPath/op/\#androidx\.compose\.ui\.graphics\.Path\#androidx\.compose\.ui\.graphics\.Path\#androidx\.compose\.ui\.graphics\.PathOperation/PointingToDeclaration/
+# Wire proto generation, task :generateDebugProtos
+Writing .* to \$OUT_DIR/.*/build/generated/source/wire
+# > Task :compose:ui:ui-tooling:processDebugAndroidTestManifest
+\$SUPPORT/compose/ui/ui\-tooling/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+dagger\.lint\.DaggerIssueRegistry in .*/lint\.jar does not specify a vendor; see IssueRegistry#vendor
+# > Task :compose:foundation:foundation:androidReleaseSourcesJar
+Encountered duplicate path "android[a-zA-Z]*/.+" during copy operation configured with DuplicatesStrategy\.WARN
+# ./gradlew tasks warns as we have warnings present
+You can use \'\-\-warning\-mode all\' to show the individual deprecation warnings and determine if they come from your own scripts or plugins\.
+# > Task :emoji2:emoji2-bundled:processDebugAndroidTestManifest
+\$SUPPORT/emoji[0-9]+/emoji[0-9]+\-bundled/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+meta\-data\#androidx\.emoji[0-9]+\.text\.EmojiCompatInitializer was tagged at AndroidManifest\.xml:[0-9]+ to remove other declarations but no other declaration present
+# > Task :emoji2:emoji2-bundled:processDebugManifest
+\$SUPPORT/emoji[0-9]+/emoji[0-9]+\-bundled/src/main/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+# b/195025261
+To honour the JVM settings for this build a single\-use Daemon process will be forked.*
+# > Tasks configureCMakeRelWithDebInfo or configureCMakeDebug
+C/C\+\+: Building ver\.\: [0-9]+\.[0-9]+\.[0-9]+
+C/C\+\+: Packaging for\: (amd\-[0-9]+|armhf\-[0-9]+|x86\-[0-9]+)
+C/C\+\+: Compiling for ARM
+w: \[ksp\] Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\. \- androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDb\.jvmDao\(\)
+w: \[ksp\] \$SUPPORT/room/integration\-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.kt:[0-9]+: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
+# > Task :room:integration-tests:room-testapp-kotlin:kaptWithKaptDebugAndroidTestKotlin
+\$OUT_DIR/androidx/room/integration\-tests/room\-testapp\-kotlin/build/tmp/kapt[0-9]+/stubs/withKaptDebugAndroidTest/androidx/room/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.java:[0-9]+: warning: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
+public abstract void jvmDelete\(T t\);
+public abstract void jvmInsert\(@org\.jetbrains\.annotations\.NotNull.*
+public abstract java\.util\.List<androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameEntity> jvmQuery\(\);
+public abstract androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDao jvmDao\(\);
+\^
+# Gradle will log if you are not authenticated to upload scans
+A build scan was not published as you have not authenticated with server 'ge\.androidx\.dev'\.
+# Room unresolved type error messages
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromAsset\(kotlin\.String\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromAsset\(kotlin\.String\,\ androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromFile\(java\.io\.File\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromFile\(java\.io\.File\,\ androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromInputStream\(java\.util\.concurrent\.Callable\(\(java\.io\.InputStream\)\)\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$createFromInputStream\(java\.util\.concurrent\.Callable\(\(java\.io\.InputStream\)\), androidx\.room\.RoomDatabase\.PrepackagedDatabaseCallback\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$openHelperFactory\(androidx\.sqlite\.db\.SupportSQLiteOpenHelper\.Factory\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addMigrations\(kotlin\.Array\(\(androidx\.room\.migration\.Migration\)\)\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addAutoMigrationSpec\(androidx\.room\.migration\.AutoMigrationSpec\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$allowMainThreadQueries\(\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setJournalMode\(androidx\.room\.RoomDatabase\.JournalMode\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setQueryExecutor\(java\.util\.concurrent\.Executor\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setTransactionExecutor\(java\.util\.concurrent\.Executor\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$enableMultiInstanceInvalidation\(\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setMultiInstanceInvalidationServiceIntent\(android\.content\.Intent\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$fallbackToDestructiveMigration\(\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$fallbackToDestructiveMigrationFrom\(kotlin\.IntArray\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$fallbackToDestructiveMigrationOnDowngrade\(\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addCallback\(androidx\.room\.RoomDatabase\.Callback\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setQueryCallback\(androidx\.room\.RoomDatabase\.QueryCallback, java\.util\.concurrent\.Executor\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$addTypeConverter\(kotlin\.Any\) \(RoomDatabase\.kt:[0-9]+\)
+Found an unresolved type in androidx\.room\.RoomDatabase\.Builder\$setAutoCloseTimeout\(kotlin\.Long, java\.util\.concurrent\.TimeUnit\) \(RoomDatabase\.kt:[0-9]+\)
+# > Task :compose:ui:ui:compileReleaseKotlin
+w: \$SUPPORT/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat\.android\.kt: \([0-9]+, [0-9]+\): Unnecessary non\-null assertion \(!!\) on a non\-null receiver of type LayoutNode
+# When konan is downloading a dependency from another file, don't warn about it.
+\(KonanProperies\) Downloading dependency: file:\.\./\.\./.*
+Please wait while Kotlin/Native compiler .* is being installed\.
+Unpack Kotlin/Native compiler to .*
+Download file:\.\./\.\./.*
+Downloading native dependencies \(LLVM, sysroot etc\)\. This is a one\-time action performed only on the first run of the compiler\.
+Extracting dependency: .*\.konan/cache.*
+# New memory model does not work with compiler caches yet:
+# https://github.com/JetBrains/kotlin/blob/master/kotlin-native/NEW_MM.md#known-bugs
+w: Cached libraries will not be used with experimental memory model
+# > Task :commonizeNativeDistribution
+# Linux builds cannot build mac, hence we get this warning.
+# see: https://github.com/JetBrains/kotlin/blob/master/native/commonizer/README.md
+# This warning is printed from: https://github.com/JetBrains/kotlin/blob/bc853e45e8982eff74e3263b0197c1af6086615d/native/commonizer/src/org/jetbrains/kotlin/commonizer/konan/LibraryCommonizer.kt#L41
+Warning\: No libraries found for target (macos|ios|ios_simulator)_(arm|x)[0-9]+\. This target will be excluded from commonization\.
+void androidx.tv.foundation.lazy.list.LazyListKt.LazyList(androidx.compose.ui.Modifier, androidx.tv.foundation.lazy.list.TvLazyListState, androidx.compose.foundation.layout.PaddingValues, boolean, boolean, boolean, androidx.tv.foundation.PivotOffsets, androidx.compose.ui.Alignment$Horizontal, androidx.compose.foundation.layout.Arrangement$Vertical, androidx.compose.ui.Alignment$Vertical, androidx.compose.foundation.layout.Arrangement$Horizontal, kotlin.jvm.functions.Function1, androidx.compose.runtime.Composer, int, int, int)
+void androidx.tv.foundation.lazy.grid.LazyGridKt.LazyGrid(androidx.compose.ui.Modifier, androidx.tv.foundation.lazy.grid.TvLazyGridState, kotlin.jvm.functions.Function2, androidx.compose.foundation.layout.PaddingValues, boolean, boolean, boolean, androidx.compose.foundation.layout.Arrangement$Vertical, androidx.compose.foundation.layout.Arrangement$Horizontal, androidx.tv.foundation.PivotOffsets, kotlin.jvm.functions.Function1, androidx.compose.runtime.Composer, int, int, int)
+# > Task :room:integration-tests:room-testapp:mergeDexWithExpandProjectionDebugAndroidTest
+WARNING:D[0-9]+: Application does not contain `androidx\.tracing\.Trace` as referenced in main\-dex\-list\.
+# > Task :hilt:hilt-compiler:kaptTestKotlin
+Annotation processors discovery from compile classpath is deprecated\.
+Set 'kapt\.include\.compile\.classpath=false' to disable discovery\.
+Run the build with '\-\-info' for more details\.
+# AGP warning about API usage we have no control over
+Values of variant API AnnotationProcessorOptions\.arguments are queried and may return non final values, this is unsupported
+# > Task :compose:ui:ui:testDebugUnitTest
+OpenJDK 64\-Bit Server VM warning:
+.*Sharing is only supported for boot loader classes because bootstrap classpath has been appended
+# > Task :concurrent:concurrent-futures:compileTestJava b/242311027
+\$SUPPORT/concurrent/concurrent\-futures/src/test/java/androidx/concurrent/futures/AbstractResolvableFutureTest\.java:[0-9]+: warning: \[removal\] (resume|suspend)\(\) in Thread has been deprecated and marked for removal
+thread\.(resume|suspend)\(\);
+2 warnings
+# AGP warning that will go away soon
+WARNING:Software Components will not be created automatically for Maven publishing from Android Gradle Plugin 8\.0\. To opt\-in to the future behavior, set the Gradle property android\.disableAutomaticComponentCreation=true in the `gradle\.properties` file or use the new publishing DSL\.
+# > Task :graphics:graphics-path:compileDebugKotlin
+w\: \$SUPPORT\/graphics\/graphics\-path\/src\/main\/java\/androidx\/graphics\/path\/Paths\.kt\: \([0-9]+\, [0-9]+\)\: Extension is shadowed by a member\: public open fun iterator\(\)\: PathIterator
+# > Task :core:core-splashscreen:core-splashscreen-samples:lintReportDebug
+Warning: Lint will treat :annotation:annotation as an external dependency and not analyze it\.
+Did you make a typo\? Are you trying to refer to something not visible to users\?
+\* Recommended Action: Apply the 'com\.android\.lint' plugin to java library project :annotation:annotation\. to enable lint to analyze those sources\.
 # > Task :linkDebugTestIosX64 b/253041601
 w: Cached libraries will not be used with std allocator
diff --git a/emoji2/emoji2-emojipicker/build.gradle b/emoji2/emoji2-emojipicker/build.gradle
index 008a65a..53a6ddb 100644
--- a/emoji2/emoji2-emojipicker/build.gradle
+++ b/emoji2/emoji2-emojipicker/build.gradle
@@ -26,6 +26,8 @@
     api(libs.kotlinStdlib)
     implementation("androidx.core:core-ktx:1.8.0")
     implementation project(path: ':emoji2:emoji2')
+    implementation project(path: ':core:core')
+
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testCore)
 }
diff --git a/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/BundledEmojiListLoaderTest.kt b/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/BundledEmojiListLoaderTest.kt
index 00f13b4..cc5b603 100644
--- a/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/BundledEmojiListLoaderTest.kt
+++ b/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/BundledEmojiListLoaderTest.kt
@@ -17,6 +17,7 @@
 package androidx.emoji2.emojipicker
 
 import android.content.Context
+import androidx.emoji2.emojipicker.utils.FileCache
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -30,15 +31,52 @@
     private val emojiCompatMetadata = EmojiPickerView.EmojiCompatMetadata(null, false)
 
     @Test
-    fun testGetCategorizedEmojiData_loaded() {
+    fun testGetCategorizedEmojiData_loaded_writeToCache() {
+        // delete cache dir first
+        val fileCache = FileCache.getInstance(context)
+        fileCache.emojiPickerCacheDir.deleteRecursively()
+        assertFalse(fileCache.emojiPickerCacheDir.exists())
+
         BundledEmojiListLoader.load(context, emojiCompatMetadata)
         assertTrue(BundledEmojiListLoader.categorizedEmojiData.isNotEmpty())
+
+        // emoji_picker/osVersion|appVersion/ folder should be created
+        val propertyFolder = fileCache.emojiPickerCacheDir.listFiles()!![0]
+        assertTrue(propertyFolder!!.isDirectory)
+
+        // Number of cache files should match the size of categorizedEmojiData
+        val cacheFiles = propertyFolder.listFiles()
+        assertTrue(
+            cacheFiles!!.size == BundledEmojiListLoader.categorizedEmojiData.size
+        )
+    }
+
+    @Test
+    fun testGetCategorizedEmojiData_loaded_readFromCache() {
+        // delete cache and load again
+        val fileCache = FileCache.getInstance(context)
+        fileCache.emojiPickerCacheDir.deleteRecursively()
+        BundledEmojiListLoader.load(context, emojiCompatMetadata)
+
+        val cacheFileName = fileCache.emojiPickerCacheDir.listFiles()!![0].listFiles()!![0].name
+        val emptyDefaultValue = listOf<BundledEmojiListLoader.EmojiData>()
+        // Read from cache instead of using default value
+        var output = fileCache.getOrPut(cacheFileName) { emptyDefaultValue }
+        assertTrue(output.isNotEmpty())
+
+        // Remove cache, write default value to cache
+        fileCache.emojiPickerCacheDir.deleteRecursively()
+        output = fileCache.getOrPut(cacheFileName) { emptyDefaultValue }
+        assertTrue(output.isEmpty())
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 21)
     fun testGetEmojiVariantsLookup_loaded() {
+        // delete cache and load again
+        FileCache.getInstance(context).emojiPickerCacheDir.deleteRecursively()
         BundledEmojiListLoader.load(context, emojiCompatMetadata)
+
         // 👃 has variants (👃,👃,👃🏻,👃🏼,👃🏽,👃🏾,👃🏿)
         assertTrue(
             BundledEmojiListLoader
diff --git a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/BundledEmojiListLoader.kt b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/BundledEmojiListLoader.kt
index 9d1b492..8795edf 100644
--- a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/BundledEmojiListLoader.kt
+++ b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/BundledEmojiListLoader.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import androidx.core.content.res.use
+import androidx.emoji2.emojipicker.utils.FileCache
 import androidx.emoji2.emojipicker.utils.UnicodeRenderableManager
 
 /**
@@ -30,26 +31,25 @@
  * emoji. This allows faster variants lookup.
  */
 internal object BundledEmojiListLoader {
-
     private var _categorizedEmojiData: List<EmojiDataCategory>? = null
     private var _emojiVariantsLookup: Map<String, List<String>>? = null
 
     internal fun load(context: Context, emojiCompatMetadata: EmojiPickerView.EmojiCompatMetadata) {
-        // TODO(chelseahao): load from cache.
         val categoryNames = context.resources.getStringArray(R.array.category_names)
 
         _categorizedEmojiData = context.resources
             .obtainTypedArray(R.array.emoji_by_category_raw_resources)
             .use { ta ->
+                val emojiFileCache = FileCache.getInstance(context)
                 (0 until ta.length()).map {
-                    EmojiDataCategory(
-                        categoryNames[it],
+                    val cacheFileName = getCacheFileName(it, emojiCompatMetadata)
+                    emojiFileCache.getOrPut(cacheFileName) {
                         loadSingleCategory(
                             context,
                             emojiCompatMetadata,
                             ta.getResourceId(it, 0)
                         )
-                    )
+                    }.let { data -> EmojiDataCategory(categoryNames[it], data) }
                 }.toList()
             }
 
@@ -82,6 +82,18 @@
             .filter { it.isNotEmpty() }
             .map { EmojiData(it.first(), it.drop(1)) }
 
+    private fun getCacheFileName(
+        categoryIndex: Int,
+        emojiCompatMetadata: EmojiPickerView.EmojiCompatMetadata
+    ) = StringBuilder().append("emoji.v1.")
+        .append(emojiCompatMetadata.hashCode())
+        .append(".")
+        .append(categoryIndex)
+        .append(".")
+        .append(
+            if (UnicodeRenderableManager.isEmoji12Supported(emojiCompatMetadata)) 1 else 0
+        ).toString()
+
     /**
      * To eliminate 'Tofu' (the fallback glyph when an emoji is not renderable), check the
      * renderability of emojis and keep only when they are renderable on the current device.
diff --git a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/FileCache.kt b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/FileCache.kt
new file mode 100644
index 0000000..6c1d57d
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/FileCache.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.emoji2.emojipicker.utils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
+import androidx.emoji2.emojipicker.BundledEmojiListLoader
+import java.io.File
+
+/**
+ * A class that manages cache files for the emoji picker. All cache files are stored in DE
+ * (Device Encryption) storage (N+), and will be invalidated if device OS version or App version is
+ * updated.
+ *
+ * Currently this class is only used by [BundledEmojiListLoader]. All renderable emojis will be
+ * cached by categories under
+ * /app.package.name/cache/emoji_picker/<osVersion|appVersion>
+ * /emoji.<emojiPickerVersion>.<emojiCompatMetadataHashCode>.<categoryIndex>.<ifEmoji12Supported>
+ */
+internal class FileCache(context: Context) {
+
+    @VisibleForTesting
+    internal val emojiPickerCacheDir: File
+    private val currentProperty: String
+
+    init {
+        val osVersion = "${Build.VERSION.SDK_INT}_${Build.TIME}"
+        val appVersion = getVersionCode(context)
+        currentProperty = "$osVersion|$appVersion"
+        emojiPickerCacheDir =
+            File(getDeviceProtectedStorageContext(context).cacheDir, EMOJI_PICKER_FOLDER)
+    }
+
+    /** Get cache for a given file name, or write to a new file using the [defaultValue] factory. */
+    internal fun getOrPut(
+        key: String,
+        defaultValue: () -> List<BundledEmojiListLoader.EmojiData>
+    ): List<BundledEmojiListLoader.EmojiData> {
+        val targetDir = File(emojiPickerCacheDir, currentProperty)
+        // No matching cache folder for current property, clear stale cache directory if any
+        if (!targetDir.exists()) {
+            emojiPickerCacheDir.listFiles()?.forEach { it.deleteRecursively() }
+            targetDir.mkdirs()
+        }
+
+        val targetFile = File(targetDir, key)
+        return readFrom(targetFile) ?: writeTo(targetFile, defaultValue)
+    }
+
+    private fun readFrom(targetFile: File): List<BundledEmojiListLoader.EmojiData>? {
+        if (!targetFile.isFile)
+            return null
+        return targetFile.bufferedReader()
+            .useLines { it.toList() }
+            .map { it.split(",") }
+            .map { BundledEmojiListLoader.EmojiData(it.first(), it.drop(1)) }
+    }
+
+    private fun writeTo(
+        targetFile: File,
+        defaultValue: () -> List<BundledEmojiListLoader.EmojiData>
+    ): List<BundledEmojiListLoader.EmojiData> {
+        val data = defaultValue.invoke()
+        targetFile.bufferedWriter()
+            .use { out ->
+                for (emoji in data) {
+                    out.write(emoji.primary)
+                    emoji.variants.forEach { out.write(",$it") }
+                    out.newLine()
+                }
+            }
+        return data
+    }
+
+    /** Returns a new [context] for accessing device protected storage.  */
+    private fun getDeviceProtectedStorageContext(context: Context) =
+        context.takeIf {
+            ContextCompat.isDeviceProtectedStorage(it)
+        } ?: run { ContextCompat.createDeviceProtectedStorageContext(context) } ?: context
+
+    /** Gets the version code for a package. */
+    @Suppress("DEPRECATION")
+    private fun getVersionCode(context: Context): Long = try {
+        if (Build.VERSION.SDK_INT >= 33)
+            Api33Impl.getAppVersionCode(context)
+        else if (Build.VERSION.SDK_INT >= 28)
+            Api28Impl.getAppVersionCode(context)
+        else context.packageManager.getPackageInfo(context.packageName, 0).versionCode.toLong()
+    } catch (e: PackageManager.NameNotFoundException) {
+        // Default version to 1
+        1
+    }
+
+    companion object {
+        @Volatile
+        private var instance: FileCache? = null
+
+        internal fun getInstance(context: Context): FileCache =
+            instance ?: synchronized(this) {
+                instance ?: FileCache(context).also { instance = it }
+            }
+
+        private const val EMOJI_PICKER_FOLDER = "emoji_picker"
+    }
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    internal object Api33Impl {
+        fun getAppVersionCode(context: Context) =
+            context.packageManager.getPackageInfo(
+                context.packageName,
+                PackageManager.PackageInfoFlags.of(0)
+            ).longVersionCode
+    }
+
+    @Suppress("DEPRECATION")
+    @RequiresApi(Build.VERSION_CODES.P)
+    internal object Api28Impl {
+        fun getAppVersionCode(context: Context) =
+            context.packageManager.getPackageInfo(
+                context.packageName,
+                /* flags= */ 0
+            ).longVersionCode
+    }
+}
\ No newline at end of file
diff --git a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/UnicodeRenderableManager.kt b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/UnicodeRenderableManager.kt
index 5f6386a..b8bd327 100644
--- a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/UnicodeRenderableManager.kt
+++ b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/utils/UnicodeRenderableManager.kt
@@ -30,6 +30,8 @@
 
     private const val VARIATION_SELECTOR = "\uFE0F"
 
+    private const val YAWNING_FACE_EMOJI = "\uD83E\uDD71"
+
     private val paint = TextPaint()
 
     /**
@@ -64,6 +66,13 @@
         EmojiCompat.get().getEmojiMatch(emoji, this) > 0
     } ?: (getClosestRenderable(emoji) != null)
 
+    internal fun isEmoji12Supported(
+        emojiCompatMetaData: EmojiPickerView.EmojiCompatMetadata
+    ) =
+        // Yawning face is added in emoji 12 which is the first version starts to support gender
+        // inclusive emojis.
+        isEmojiRenderable(YAWNING_FACE_EMOJI, emojiCompatMetaData)
+
     @VisibleForTesting
     fun getClosestRenderable(emoji: String): String? {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index 18f7836..bf3e575 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -25,9 +25,9 @@
 
   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 public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(long checkedColor, long uncheckedColor);
-    method public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(optional androidx.glance.unit.ColorProvider? checkedColor, optional androidx.glance.unit.ColorProvider? uncheckedColor);
-    method public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(@ColorRes int checkBoxColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors(long checkedColor, long uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors();
   }
 
   public final class CircularProgressIndicatorKt {
@@ -124,9 +124,9 @@
 
   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 public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(androidx.glance.unit.ColorProvider? checkedColor, androidx.glance.unit.ColorProvider? uncheckedColor);
-    method public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(optional @ColorRes int color);
-    method public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(long checkedColor, long uncheckedColor);
+    method public static androidx.glance.appwidget.RadioButtonColors radioButtonColors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method public static androidx.glance.appwidget.RadioButtonColors radioButtonColors(long checkedColor, long uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.RadioButtonColors radioButtonColors();
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -153,16 +153,10 @@
   public abstract sealed class SwitchColors {
   }
 
-  public final class SwitchDefaults {
-    method public androidx.glance.appwidget.SwitchColors getColors();
-    property public final androidx.glance.appwidget.SwitchColors colors;
-    field public static final androidx.glance.appwidget.SwitchDefaults INSTANCE;
-  }
-
   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 public static androidx.glance.appwidget.SwitchColors SwitchColors(optional androidx.glance.unit.ColorProvider? checkedThumbColor, optional androidx.glance.unit.ColorProvider? uncheckedThumbColor, optional androidx.glance.unit.ColorProvider? checkedTrackColor, optional androidx.glance.unit.ColorProvider? uncheckedTrackColor);
-    method public static androidx.glance.appwidget.SwitchColors SwitchColors(@ColorRes int thumbColor, optional @ColorRes int trackColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.SwitchColors switchColors(androidx.glance.unit.ColorProvider checkedThumbColor, androidx.glance.unit.ColorProvider uncheckedThumbColor, androidx.glance.unit.ColorProvider checkedTrackColor, androidx.glance.unit.ColorProvider uncheckedTrackColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.SwitchColors switchColors();
   }
 
   public final class UtilsKt {
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index 1591d4d..6934f35 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -25,9 +25,9 @@
 
   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 public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(long checkedColor, long uncheckedColor);
-    method public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(optional androidx.glance.unit.ColorProvider? checkedColor, optional androidx.glance.unit.ColorProvider? uncheckedColor);
-    method public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(@ColorRes int checkBoxColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors(long checkedColor, long uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors();
   }
 
   public final class CircularProgressIndicatorKt {
@@ -132,9 +132,9 @@
 
   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 public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(androidx.glance.unit.ColorProvider? checkedColor, androidx.glance.unit.ColorProvider? uncheckedColor);
-    method public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(optional @ColorRes int color);
-    method public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(long checkedColor, long uncheckedColor);
+    method public static androidx.glance.appwidget.RadioButtonColors radioButtonColors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method public static androidx.glance.appwidget.RadioButtonColors radioButtonColors(long checkedColor, long uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.RadioButtonColors radioButtonColors();
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -167,16 +167,10 @@
   public abstract sealed class SwitchColors {
   }
 
-  public final class SwitchDefaults {
-    method public androidx.glance.appwidget.SwitchColors getColors();
-    property public final androidx.glance.appwidget.SwitchColors colors;
-    field public static final androidx.glance.appwidget.SwitchDefaults INSTANCE;
-  }
-
   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 public static androidx.glance.appwidget.SwitchColors SwitchColors(optional androidx.glance.unit.ColorProvider? checkedThumbColor, optional androidx.glance.unit.ColorProvider? uncheckedThumbColor, optional androidx.glance.unit.ColorProvider? checkedTrackColor, optional androidx.glance.unit.ColorProvider? uncheckedTrackColor);
-    method public static androidx.glance.appwidget.SwitchColors SwitchColors(@ColorRes int thumbColor, optional @ColorRes int trackColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.SwitchColors switchColors(androidx.glance.unit.ColorProvider checkedThumbColor, androidx.glance.unit.ColorProvider uncheckedThumbColor, androidx.glance.unit.ColorProvider checkedTrackColor, androidx.glance.unit.ColorProvider uncheckedTrackColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.SwitchColors switchColors();
   }
 
   public final class UtilsKt {
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index 18f7836..bf3e575 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -25,9 +25,9 @@
 
   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 public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(long checkedColor, long uncheckedColor);
-    method public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(optional androidx.glance.unit.ColorProvider? checkedColor, optional androidx.glance.unit.ColorProvider? uncheckedColor);
-    method public static androidx.glance.appwidget.CheckBoxColors CheckBoxColors(@ColorRes int checkBoxColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors(long checkedColor, long uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.CheckBoxColors checkBoxColors();
   }
 
   public final class CircularProgressIndicatorKt {
@@ -124,9 +124,9 @@
 
   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 public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(androidx.glance.unit.ColorProvider? checkedColor, androidx.glance.unit.ColorProvider? uncheckedColor);
-    method public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(optional @ColorRes int color);
-    method public static androidx.glance.appwidget.RadioButtonColors RadioButtonColors(long checkedColor, long uncheckedColor);
+    method public static androidx.glance.appwidget.RadioButtonColors radioButtonColors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method public static androidx.glance.appwidget.RadioButtonColors radioButtonColors(long checkedColor, long uncheckedColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.RadioButtonColors radioButtonColors();
     method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
   }
 
@@ -153,16 +153,10 @@
   public abstract sealed class SwitchColors {
   }
 
-  public final class SwitchDefaults {
-    method public androidx.glance.appwidget.SwitchColors getColors();
-    property public final androidx.glance.appwidget.SwitchColors colors;
-    field public static final androidx.glance.appwidget.SwitchDefaults INSTANCE;
-  }
-
   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 public static androidx.glance.appwidget.SwitchColors SwitchColors(optional androidx.glance.unit.ColorProvider? checkedThumbColor, optional androidx.glance.unit.ColorProvider? uncheckedThumbColor, optional androidx.glance.unit.ColorProvider? checkedTrackColor, optional androidx.glance.unit.ColorProvider? uncheckedTrackColor);
-    method public static androidx.glance.appwidget.SwitchColors SwitchColors(@ColorRes int thumbColor, optional @ColorRes int trackColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.SwitchColors switchColors(androidx.glance.unit.ColorProvider checkedThumbColor, androidx.glance.unit.ColorProvider uncheckedThumbColor, androidx.glance.unit.ColorProvider checkedTrackColor, androidx.glance.unit.ColorProvider uncheckedTrackColor);
+    method @androidx.compose.runtime.Composable public static androidx.glance.appwidget.SwitchColors switchColors();
   }
 
   public final class UtilsKt {
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
index e8739cb..eb0d0c2 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
@@ -28,14 +28,14 @@
 import androidx.glance.action.ActionParameters
 import androidx.glance.action.actionParametersOf
 import androidx.glance.appwidget.CheckBox
-import androidx.glance.appwidget.CheckBoxColors
+import androidx.glance.appwidget.checkBoxColors
 import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.GlanceAppWidgetReceiver
 import androidx.glance.appwidget.RadioButton
-import androidx.glance.appwidget.RadioButtonColors
+import androidx.glance.appwidget.radioButtonColors
 import androidx.glance.appwidget.SizeMode
 import androidx.glance.appwidget.Switch
-import androidx.glance.appwidget.SwitchColors
+import androidx.glance.appwidget.switchColors
 import androidx.glance.appwidget.selectableGroup
 import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.appwidget.action.ToggleableStateKey
@@ -114,7 +114,7 @@
                 text = "Checkbox 2",
                 style = textStyle,
                 modifier = fillModifier,
-                colors = CheckBoxColors(
+                colors = checkBoxColors(
                     checkedColor = ColorProvider(day = Color.Red, night = Color.Cyan),
                     uncheckedColor = ColorProvider(day = Color.Green, night = Color.Magenta)
                 )
@@ -125,7 +125,6 @@
                     actionParametersOf(EventTargetKey to Buttons.CHECK_3.name)
                 ),
                 text = "Checkbox 3",
-                colors = CheckBoxColors(R.color.my_checkbox_colors)
             )
             Switch(
                 checked = switch1Checked,
@@ -133,7 +132,7 @@
                     actionParametersOf(EventTargetKey to Buttons.SWITCH_1.name)
                 ),
                 text = "Switch 1",
-                colors = SwitchColors(
+                colors = switchColors(
                     checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan),
                     uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta),
                     checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow),
@@ -156,7 +155,7 @@
                         actionParametersOf(EventTargetKey to Radios.RADIO_1.name)
                     ),
                     text = "Radio 1",
-                    colors = RadioButtonColors(
+                    colors = radioButtonColors(
                         checkedColor = ColorProvider(day = Color.Red, night = Color.Cyan),
                         uncheckedColor = ColorProvider(day = Color.Green, night = Color.Magenta)
                     ),
@@ -167,7 +166,7 @@
                         actionParametersOf(EventTargetKey to Radios.RADIO_2.name)
                     ),
                     text = "Radio 2",
-                    colors = RadioButtonColors(
+                    colors = radioButtonColors(
                         checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow),
                         uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue)
                     ),
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/GlanceAppWidgetDemoActivity.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/GlanceAppWidgetDemoActivity.kt
index fb88455..801f260 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/GlanceAppWidgetDemoActivity.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/GlanceAppWidgetDemoActivity.kt
@@ -80,7 +80,8 @@
                 if (!GlanceAppWidgetReceiver::class.java.isAssignableFrom(receiverClass)) {
                     return@mapNotNull null
                 }
-                val receiver = receiverClass.newInstance() as GlanceAppWidgetReceiver
+                val receiver = receiverClass.getDeclaredConstructor()
+                    .newInstance() as GlanceAppWidgetReceiver
                 val provider = receiver.glanceAppWidget.javaClass
                 ProviderData(
                     provider = provider,
@@ -112,7 +113,8 @@
                                 scope.launch {
                                     manager.requestPinGlanceAppWidget(
                                         receiver = it.receiver,
-                                        preview = it.provider.newInstance(),
+                                        preview = it.provider.getDeclaredConstructor()
+                                            .newInstance(),
                                         previewState = emptyPreferences()
                                     )
                                 }
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
index 75e246a..2ffe9a5 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
@@ -224,7 +224,7 @@
     }
 
     @Test
-    fun checkButtonTextAlignement() {
+    fun checkButtonTextAlignment() {
         TestGlanceAppWidget.uiDefinition = {
             Column(modifier = GlanceModifier.fillMaxSize()) {
                 Row(modifier = GlanceModifier.defaultWeight().fillMaxWidth()) {
@@ -636,8 +636,9 @@
                 fontWeight = FontWeight.Bold,
                 fontStyle = FontStyle.Normal,
             ),
-            colors = CheckBoxColors(
-                checkedColor = ColorProvider(day = Color.Magenta, night = Color.Yellow)
+            colors = checkBoxColors(
+                checkedColor = ColorProvider(day = Color.Magenta, night = Color.Yellow),
+                uncheckedColor = ColorProvider(day = Color.Black, night = Color.Gray)
             )
         )
 
@@ -651,7 +652,7 @@
                 fontWeight = FontWeight.Medium,
                 fontStyle = FontStyle.Italic,
             ),
-            colors = CheckBoxColors(checkedColor = Color.Red, uncheckedColor = Color.Green)
+            colors = checkBoxColors(checkedColor = Color.Red, uncheckedColor = Color.Green)
         )
     }
 }
@@ -668,21 +669,29 @@
                 fontWeight = FontWeight.Bold,
                 fontStyle = FontStyle.Normal,
             ),
-            colors = SwitchColors(
+            colors = switchColors(
                 checkedThumbColor = ColorProvider(day = Color.Blue, night = Color.Red),
+                uncheckedThumbColor = ColorProvider(Color.Magenta),
                 checkedTrackColor = ColorProvider(day = Color.Green, night = Color.Yellow),
+                uncheckedTrackColor = ColorProvider(Color.Magenta)
             )
         )
 
         Switch(
             checked = false,
             onCheckedChange = null,
-            text = "Hello Unchecked Switch",
+            text = "Hello Unchecked Switch. day: thumb magenta / track cyan, night: thumb cyan",
             style = TextStyle(
                 color = ColorProvider(day = Color.Black, night = Color.White),
                 textDecoration = TextDecoration.Underline,
                 fontWeight = FontWeight.Medium,
                 fontStyle = FontStyle.Italic,
+            ),
+            colors = switchColors(
+                checkedThumbColor = ColorProvider(Color.Blue),
+                uncheckedThumbColor = ColorProvider(day = Color.Magenta, night = Color.Cyan),
+                checkedTrackColor = ColorProvider(Color.Blue),
+                uncheckedTrackColor = ColorProvider(day = Color.Cyan, night = Color.Magenta)
             )
         )
     }
@@ -703,7 +712,7 @@
                 fontWeight = FontWeight.Bold,
                 fontStyle = FontStyle.Normal,
             ),
-            colors = RadioButtonColors(
+            colors = radioButtonColors(
                 checkedColor = ColorProvider(day = Color.Magenta, night = Color.Yellow),
                 uncheckedColor = ColorProvider(day = Color.Yellow, night = Color.Magenta)
             )
@@ -719,7 +728,7 @@
                 fontWeight = FontWeight.Medium,
                 fontStyle = FontStyle.Italic,
             ),
-            colors = RadioButtonColors(checkedColor = Color.Red, uncheckedColor = Color.Green)
+            colors = radioButtonColors(checkedColor = Color.Red, uncheckedColor = Color.Green)
         )
     }
 }
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
index d307271..20c2755 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
@@ -480,7 +480,8 @@
         TestGlanceAppWidget.uiDefinition = {
             val context = LocalContext.current
             val bitmap =
-                (context.resources.getDrawable(R.drawable.compose, null) as BitmapDrawable).bitmap
+                (context.resources.getDrawable(R.drawable.compose, null) as BitmapDrawable)
+                    .bitmap
             Text(
                 "Some useful text",
                 modifier = GlanceModifier.fillMaxSize()
@@ -636,13 +637,17 @@
                 Text(
                     "text1",
                     modifier = GlanceModifier.clickable(
-                        actionRunCallback<CallbackTest>(actionParametersOf(CallbackTest.key to 1))
+                        actionRunCallback<CallbackTest>(
+                            actionParametersOf(CallbackTest.key to 1)
+                        )
                     )
                 )
                 Text(
                     "text2",
                     modifier = GlanceModifier.clickable(
-                        actionRunCallback<CallbackTest>(actionParametersOf(CallbackTest.key to 2))
+                        actionRunCallback<CallbackTest>(
+                            actionParametersOf(CallbackTest.key to 2)
+                        )
                     )
                 )
             }
@@ -653,9 +658,13 @@
         CallbackTest.received.set(emptyList())
         CallbackTest.latch = CountDownLatch(2)
         mHostRule.onHostView { root ->
-            checkNotNull(root.findChild<TextView> { it.text == "text1" }?.parent as? View)
+            checkNotNull(
+                root.findChild<TextView> { it.text.toString() == "text1" }?.parent as? View
+            )
                 .performClick()
-            checkNotNull(root.findChild<TextView> { it.text == "text2" }?.parent as? View)
+            checkNotNull(
+                root.findChild<TextView> { it.text.toString() == "text2" }?.parent as? View
+            )
                 .performClick()
         }
         assertThat(CallbackTest.latch.await(5, TimeUnit.SECONDS)).isTrue()
@@ -668,9 +677,13 @@
             Text(
                 "text1",
                 modifier = GlanceModifier.clickable(
-                    actionRunCallback<CallbackTest>(actionParametersOf(CallbackTest.key to 1))
+                    actionRunCallback<CallbackTest>(
+                        actionParametersOf(CallbackTest.key to 1)
+                    )
                 ).clickable(
-                    actionRunCallback<CallbackTest>(actionParametersOf(CallbackTest.key to 2))
+                    actionRunCallback<CallbackTest>(
+                        actionParametersOf(CallbackTest.key to 2)
+                    )
                 )
             )
         }
@@ -680,7 +693,9 @@
         CallbackTest.received.set(emptyList())
         CallbackTest.latch = CountDownLatch(1)
         mHostRule.onHostView { root ->
-            checkNotNull(root.findChild<TextView> { it.text == "text1" }?.parent as? View)
+            checkNotNull(
+                root.findChild<TextView> { it.text.toString() == "text1" }?.parent as? View
+            )
                 .performClick()
         }
         assertThat(CallbackTest.latch.await(5, TimeUnit.SECONDS)).isTrue()
@@ -710,12 +725,12 @@
             val targetHeight = (column.height.pixelsToDp(displayMetrics) - 16.dp) / 2
             val targetWidth = column.width.pixelsToDp(displayMetrics) - 16.dp
 
-            val text1 = checkNotNull(column.findChild<TextView> { it.text == "Text 1" })
+            val text1 = checkNotNull(column.findChild<TextView> { it.text.toString() == "Text 1" })
             val row1 = text1.getParentView<FrameLayout>().getParentView<LinearLayout>()
             assertThat(row1.orientation).isEqualTo(LinearLayout.HORIZONTAL)
             assertViewSize(row1, DpSize(targetWidth, targetHeight))
 
-            val text2 = checkNotNull(column.findChild<TextView> { it.text == "Text 2" })
+            val text2 = checkNotNull(column.findChild<TextView> { it.text.toString() == "Text 2" })
             val row2 = text2.getParentView<FrameLayout>().getParentView<LinearLayout>()
             assertThat(row2.orientation).isEqualTo(LinearLayout.HORIZONTAL)
             assertThat(row2.height).isGreaterThan(20.dp.toPixels(context))
@@ -752,9 +767,9 @@
         CompoundButtonActionTest.received.set(emptyList())
         CompoundButtonActionTest.latch = CountDownLatch(2)
         mHostRule.onHostView { root ->
-            checkNotNull(root.findChild<TextView> { it.text == checkbox })
+            checkNotNull(root.findChild<TextView> { it.text.toString() == checkbox })
                 .performCompoundButtonClick()
-            checkNotNull(root.findChild<TextView> { it.text == switch })
+            checkNotNull(root.findChild<TextView> { it.text.toString() == switch })
                 .performCompoundButtonClick()
         }
         CompoundButtonActionTest.latch.await(5, TimeUnit.SECONDS)
@@ -764,6 +779,44 @@
     }
 
     @Test
+    fun canCreateCheckableColorProvider() {
+        TestGlanceAppWidget.uiDefinition = {
+            Switch(
+                checked = true,
+                onCheckedChange = null,
+                text = "Hello Checked Switch (day: Blue/Green, night: Red/Yellow)",
+                style = TextStyle(
+                    color = androidx.glance.appwidget.unit.ColorProvider(
+                        day = Color.Black,
+                        night = Color.White
+                    ),
+                    fontWeight = FontWeight.Bold,
+                    fontStyle = FontStyle.Normal,
+                ),
+                colors = switchColors(
+                    checkedThumbColor = androidx.glance.appwidget.unit.ColorProvider(
+                        day = Color.Blue,
+                        night = Color.Red
+                    ),
+                    checkedTrackColor = androidx.glance.appwidget.unit.ColorProvider(
+                        day = Color.Green,
+                        night = Color.Yellow
+                    ),
+                    uncheckedThumbColor = ColorProvider(Color.Magenta),
+                    uncheckedTrackColor = ColorProvider(Color.Magenta),
+                )
+            )
+        }
+
+        mHostRule.startHost()
+        runBlocking {
+            mHostRule.updateAppWidget()
+        }
+
+        // if no crash, we're good
+    }
+
+    @Test
     fun radioActionCallback() {
         TestGlanceAppWidget.uiDefinition = {
             RadioButton(
@@ -780,7 +833,7 @@
         CallbackTest.received.set(emptyList())
         CallbackTest.latch = CountDownLatch(1)
         mHostRule.onHostView { root ->
-            checkNotNull(root.findChild<TextView> { it.text == "text1" })
+            checkNotNull(root.findChild<TextView> { it.text.toString() == "text1" })
                 .performCompoundButtonClick()
         }
         assertThat(CallbackTest.latch.await(5, TimeUnit.SECONDS)).isTrue()
@@ -795,7 +848,9 @@
                 "text1",
                 modifier = if (enabled) {
                     GlanceModifier.clickable(
-                        actionRunCallback<CallbackTest>(actionParametersOf(CallbackTest.key to 1))
+                        actionRunCallback<CallbackTest>(
+                            actionParametersOf(CallbackTest.key to 1)
+                        )
                     )
                 } else GlanceModifier
             )
@@ -805,7 +860,9 @@
 
         mHostRule.onHostView { root ->
             val view =
-                checkNotNull(root.findChild<TextView> { it.text == "text1" }?.parent as? View)
+                checkNotNull(
+                    root.findChild<TextView> { it.text.toString() == "text1" }?.parent as? View
+                )
             assertThat(view.hasOnClickListeners()).isTrue()
         }
 
@@ -818,7 +875,9 @@
 
         mHostRule.onHostView { root ->
             val view =
-                checkNotNull(root.findChild<TextView> { it.text == "text1" }?.parent as? View)
+                checkNotNull(
+                    root.findChild<TextView> { it.text.toString() == "text1" }?.parent as? View
+                )
             assertThat(view.hasOnClickListeners()).isFalse()
         }
     }
@@ -843,7 +902,7 @@
         CompoundButtonActionTest.received.set(emptyList())
         CompoundButtonActionTest.latch = CountDownLatch(1)
         mHostRule.onHostView { root ->
-            checkNotNull(root.findChild<TextView> { it.text == "checkbox" })
+            checkNotNull(root.findChild<TextView> { it.text.toString() == "checkbox" })
                 .performCompoundButtonClick()
         }
         CompoundButtonActionTest.latch.await(5, TimeUnit.SECONDS)
@@ -861,7 +920,7 @@
         CompoundButtonActionTest.received.set(emptyList())
         CompoundButtonActionTest.latch = CountDownLatch(1)
         mHostRule.onHostView { root ->
-            checkNotNull(root.findChild<TextView> { it.text == "checkbox" })
+            checkNotNull(root.findChild<TextView> { it.text.toString() == "checkbox" })
                 .performCompoundButtonClick()
         }
         assertThat(CompoundButtonActionTest.latch.await(5, TimeUnit.SECONDS)).isFalse()
@@ -889,7 +948,8 @@
         mHostRule.startHost()
 
         mHostRule.onHostView { root ->
-            val checkbox = checkNotNull(root.findChild<CompoundButton> { it.text == "checkbox" })
+            val checkbox =
+                checkNotNull(root.findChild<CompoundButton> { it.text.toString() == "checkbox" })
             assertThat(checkbox.hasOnClickListeners()).isFalse()
         }
     }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CheckBox.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CheckBox.kt
index a09f2f3..d27cadf 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CheckBox.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/CheckBox.kt
@@ -16,18 +16,19 @@
 
 package androidx.glance.appwidget
 
-import androidx.annotation.ColorRes
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import androidx.glance.Emittable
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
+import androidx.glance.GlanceTheme
 import androidx.glance.action.Action
 import androidx.glance.action.ActionModifier
 import androidx.glance.appwidget.action.CompoundButtonAction
 import androidx.glance.appwidget.unit.CheckableColorProvider
 import androidx.glance.appwidget.unit.CheckedUncheckedColorProvider.Companion.createCheckableColorProvider
 import androidx.glance.appwidget.unit.ResourceCheckableColorProvider
+import androidx.glance.color.DynamicThemeColorProviders
 import androidx.glance.text.TextStyle
 import androidx.glance.unit.ColorProvider
 import androidx.glance.unit.FixedColorProvider
@@ -42,17 +43,18 @@
 ) : CheckBoxColors()
 
 /**
- * [CheckBoxColors] that uses [checkedColor] or [uncheckedColor] depending ons the checked state of the
+ * [checkBoxColors] that uses [checkedColor] or [uncheckedColor] depending ons the checked state of the
  * CheckBox.
  *
  * @param checkedColor the [Color] to use when the CheckBox is checked
  * @param uncheckedColor the [Color] to use when the CheckBox is not checked
  */
-fun CheckBoxColors(checkedColor: Color, uncheckedColor: Color): CheckBoxColors =
-    CheckBoxColors(FixedColorProvider(checkedColor), FixedColorProvider(uncheckedColor))
+@Composable
+fun checkBoxColors(checkedColor: Color, uncheckedColor: Color): CheckBoxColors =
+    checkBoxColors(FixedColorProvider(checkedColor), FixedColorProvider(uncheckedColor))
 
 /**
- * [CheckBoxColors] that uses [checkedColor] or [uncheckedColor] depending on the checked state of
+ * [checkBoxColors] that uses [checkedColor] or [uncheckedColor] depending on the checked state of
  * the CheckBox.
  *
  * None of the [ColorProvider] parameters to this function can be created from resource ids. To use
@@ -63,35 +65,35 @@
  * @param uncheckedColor the [ColorProvider] to use when the check box is not checked, or null to
  * use the default tint
  */
-fun CheckBoxColors(
-    checkedColor: ColorProvider? = null,
-    uncheckedColor: ColorProvider? = null
+@Composable
+fun checkBoxColors(
+    checkedColor: ColorProvider,
+    uncheckedColor: ColorProvider
 ): CheckBoxColors =
     CheckBoxColorsImpl(
         createCheckableColorProvider(
             source = "CheckBoxColors",
             checked = checkedColor,
             unchecked = uncheckedColor,
-            fallback = R.color.glance_default_check_box
         )
     )
 
-/**
- * [CheckBoxColors] set to the color resource [checkBoxColor].
- *
- * This may be a fixed color or a color selector that selects color depending on
- * [android.R.attr.state_checked].
- *
- * @param checkBoxColor the resource to use to tint the check box. If an invalid resource id is
- * provided, the default check box colors will be used.
- */
-fun CheckBoxColors(@ColorRes checkBoxColor: Int): CheckBoxColors =
-    CheckBoxColorsImpl(
-        ResourceCheckableColorProvider(
-            resId = checkBoxColor,
-            fallback = R.color.glance_default_check_box
+@Composable
+fun checkBoxColors(): CheckBoxColors {
+    val colorProvider = if (GlanceTheme.colors == DynamicThemeColorProviders) {
+        // If using the m3 dynamic color theme, we need to create a color provider from xml
+        // because resource backed ColorStateLists cannot be created programmatically
+         ResourceCheckableColorProvider(R.color.glance_default_check_box)
+    } else {
+        createCheckableColorProvider(
+            source = "CheckBoxColors",
+            checked = GlanceTheme.colors.primary,
+            unchecked = GlanceTheme.colors.onSurface
         )
-    )
+    }
+
+    return CheckBoxColorsImpl(colorProvider)
+}
 
 /**
  * Adds a check box view to the glance view.
@@ -115,7 +117,7 @@
     modifier: GlanceModifier = GlanceModifier,
     text: String = "",
     style: TextStyle? = null,
-    colors: CheckBoxColors = CheckBoxColors(),
+    colors: CheckBoxColors = checkBoxColors(),
     maxLines: Int = Int.MAX_VALUE,
 ) {
     val finalModifier = if (onCheckedChange != null) {
@@ -124,7 +126,7 @@
         modifier
     }
     GlanceNode(
-        factory = ::EmittableCheckBox,
+        factory = { EmittableCheckBox(colors) },
         update = {
             this.set(checked) { this.checked = it }
             this.set(text) { this.text = it }
@@ -136,20 +138,20 @@
     )
 }
 
-internal class EmittableCheckBox : Emittable {
+internal class EmittableCheckBox(
+    var colors: CheckBoxColors
+) : Emittable {
     override var modifier: GlanceModifier = GlanceModifier
     var checked: Boolean = false
     var text: String = ""
     var style: TextStyle? = null
-    var colors: CheckBoxColors = CheckBoxColors()
     var maxLines: Int = Int.MAX_VALUE
 
-    override fun copy(): Emittable = EmittableCheckBox().also {
+    override fun copy(): Emittable = EmittableCheckBox(colors = colors).also {
         it.modifier = modifier
         it.checked = checked
         it.text = text
         it.style = style
-        it.colors = colors
         it.maxLines = maxLines
     }
 
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RadioButton.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RadioButton.kt
index 006f600..e4d818e 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RadioButton.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RadioButton.kt
@@ -16,17 +16,18 @@
 
 package androidx.glance.appwidget
 
-import androidx.annotation.ColorRes
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import androidx.glance.Emittable
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
+import androidx.glance.GlanceTheme
 import androidx.glance.action.Action
 import androidx.glance.action.clickable
 import androidx.glance.appwidget.unit.CheckableColorProvider
 import androidx.glance.appwidget.unit.CheckedUncheckedColorProvider.Companion.createCheckableColorProvider
 import androidx.glance.appwidget.unit.ResourceCheckableColorProvider
+import androidx.glance.color.DynamicThemeColorProviders
 import androidx.glance.text.TextStyle
 import androidx.glance.unit.ColorProvider
 import androidx.glance.unit.FixedColorProvider
@@ -45,65 +46,60 @@
  * @param uncheckedColor the tint to apply to the radio button when it is not checked,
  * or null to use the default tint.
  */
-fun RadioButtonColors(
-    checkedColor: ColorProvider?,
-    uncheckedColor: ColorProvider?,
+fun radioButtonColors(
+    checkedColor: ColorProvider,
+    uncheckedColor: ColorProvider,
 ): RadioButtonColors {
     return RadioButtonColors(
         radio = createCheckableColorProvider(
-            source = "RadioButtonColors",
-            checked = checkedColor,
-            unchecked = uncheckedColor,
-            fallback = R.color.glance_default_radio_button
+            source = "RadioButtonColors", checked = checkedColor, unchecked = uncheckedColor
         )
     )
 }
 
 /**
- * [RadioButtonColors] set to color resources.
- *
- * These may be fixed colors or a color selector that selects color depending on
- * [android.R.attr.state_checked].
- *
- * @param color the resource to use to tint the radio button. If an invalid resource id is provided,
- * the default colors will be used.
- */
-fun RadioButtonColors(
-    @ColorRes color: Int = R.color.glance_default_radio_button
-): RadioButtonColors =
-    RadioButtonColors(
-        radio = ResourceCheckableColorProvider(
-            resId = color,
-            fallback = R.color.glance_default_radio_button
-        )
-    )
-
-/**
- * [RadioButtonColors] that uses [checkedColor] or [uncheckedColor] depending on the checked state
+ * [radioButtonColors] that uses [checkedColor] or [uncheckedColor] depending on the checked state
  * of the RadioButton.
  *
  * @param checkedColor the [Color] to use when the RadioButton is checked
  * @param uncheckedColor the [Color] to use when the RadioButton is not checked
  */
-fun RadioButtonColors(checkedColor: Color, uncheckedColor: Color): RadioButtonColors =
-    RadioButtonColors(FixedColorProvider(checkedColor), FixedColorProvider(uncheckedColor))
+fun radioButtonColors(checkedColor: Color, uncheckedColor: Color): RadioButtonColors =
+    radioButtonColors(FixedColorProvider(checkedColor), FixedColorProvider(uncheckedColor))
 
-internal class EmittableRadioButton : Emittable {
+@Composable
+fun radioButtonColors(): RadioButtonColors {
+    val colorProvider = if (GlanceTheme.colors == DynamicThemeColorProviders) {
+        // If using the m3 dynamic color theme, we need to create a color provider from xml
+        // because resource backed ColorStateLists cannot be created programmatically
+        ResourceCheckableColorProvider(R.color.glance_default_radio_button)
+    } else {
+        createCheckableColorProvider(
+            source = "CheckBoxColors",
+            checked = GlanceTheme.colors.primary,
+            unchecked = GlanceTheme.colors.onSurfaceVariant
+        )
+    }
+
+    return RadioButtonColors(colorProvider)
+}
+
+internal class EmittableRadioButton(
+    var colors: RadioButtonColors
+) : Emittable {
     override var modifier: GlanceModifier = GlanceModifier
     var checked: Boolean = false
     var enabled: Boolean = true
     var text: String = ""
     var style: TextStyle? = null
-    var colors: RadioButtonColors = RadioButtonColors()
     var maxLines: Int = Int.MAX_VALUE
 
-    override fun copy(): Emittable = EmittableRadioButton().also {
+    override fun copy(): Emittable = EmittableRadioButton(colors = colors).also {
         it.modifier = modifier
         it.checked = checked
         it.enabled = enabled
         it.text = text
         it.style = style
-        it.colors = colors
         it.maxLines = maxLines
     }
 
@@ -144,22 +140,19 @@
     enabled: Boolean = true,
     text: String = "",
     style: TextStyle? = null,
-    colors: RadioButtonColors = RadioButtonColors(),
+    colors: RadioButtonColors = radioButtonColors(),
     maxLines: Int = Int.MAX_VALUE,
 ) {
     val finalModifier = if (enabled && onClick != null) modifier.clickable(onClick) else modifier
-    GlanceNode(
-        factory = ::EmittableRadioButton,
-        update = {
-            this.set(checked) { this.checked = it }
-            this.set(finalModifier) { this.modifier = it }
-            this.set(enabled) { this.enabled = it }
-            this.set(text) { this.text = it }
-            this.set(style) { this.style = it }
-            this.set(colors) { this.colors = it }
-            this.set(maxLines) { this.maxLines = it }
-        }
-    )
+    GlanceNode(factory = { EmittableRadioButton(colors) }, update = {
+        this.set(checked) { this.checked = it }
+        this.set(finalModifier) { this.modifier = it }
+        this.set(enabled) { this.enabled = it }
+        this.set(text) { this.text = it }
+        this.set(style) { this.style = it }
+        this.set(colors) { this.colors = it }
+        this.set(maxLines) { this.maxLines = it }
+    })
 }
 
 /**
@@ -170,8 +163,7 @@
  * another is selected. When this modifier is used, an error will be thrown if more than one
  * RadioButton has their "checked" value set to true.
  */
-fun GlanceModifier.selectableGroup(): GlanceModifier =
-    this.then(SelectableGroupModifier)
+fun GlanceModifier.selectableGroup(): GlanceModifier = this.then(SelectableGroupModifier)
 
 internal object SelectableGroupModifier : GlanceModifier.Element
 
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Switch.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Switch.kt
index bec7ff6..cdc36f6 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Switch.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/Switch.kt
@@ -16,17 +16,18 @@
 
 package androidx.glance.appwidget
 
-import androidx.annotation.ColorRes
 import androidx.compose.runtime.Composable
 import androidx.glance.Emittable
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
+import androidx.glance.GlanceTheme
 import androidx.glance.action.Action
 import androidx.glance.action.ActionModifier
 import androidx.glance.appwidget.action.CompoundButtonAction
 import androidx.glance.appwidget.unit.CheckableColorProvider
 import androidx.glance.appwidget.unit.CheckedUncheckedColorProvider.Companion.createCheckableColorProvider
 import androidx.glance.appwidget.unit.ResourceCheckableColorProvider
+import androidx.glance.color.DynamicThemeColorProviders
 import androidx.glance.text.TextStyle
 import androidx.glance.unit.ColorProvider
 
@@ -44,70 +45,52 @@
 /**
  * SwitchColors to tint the thumb and track of the [Switch] according to the checked state.
  *
- * None of the [ColorProvider] parameters to this function can be created from resource ids. To use
- * resources to tint the switch color, use `SwitchColors(Int, Int)` instead.
+ * None of the [ColorProvider] parameters to this function can be created from resource ids.
  *
- * @param checkedThumbColor the tint to apply to the thumb of the switch when it is checked, or null
- * to use the default tint
- * @param uncheckedThumbColor the tint to apply to the thumb of the switch when it is not checked,
- * or null to use the default tint
- * @param checkedTrackColor the tint to apply to the track of the switch when it is checked, or null
- * to use the default tints
- * @param uncheckedTrackColor the tint to apply to the track of the switch when it is not checked,
- * or null to use the default tint
+ * @param checkedThumbColor the tint to apply to the thumb of the switch when it is checked
+ * @param uncheckedThumbColor the tint to apply to the thumb of the switch when it is not checked
+ * @param checkedTrackColor the tint to apply to the track of the switch when it is checked
+ * @param uncheckedTrackColor the tint to apply to the track of the switch when it is not checked
  */
-fun SwitchColors(
-    checkedThumbColor: ColorProvider? = null,
-    uncheckedThumbColor: ColorProvider? = null,
-    checkedTrackColor: ColorProvider? = null,
-    uncheckedTrackColor: ColorProvider? = null,
+@Composable
+fun switchColors(
+    checkedThumbColor: ColorProvider,
+    uncheckedThumbColor: ColorProvider,
+    checkedTrackColor: ColorProvider,
+    uncheckedTrackColor: ColorProvider,
 ): SwitchColors {
     return SwitchColorsImpl(
         thumb = createCheckableColorProvider(
             source = "SwitchColors",
             checked = checkedThumbColor,
             unchecked = uncheckedThumbColor,
-            fallback = R.color.glance_default_switch_thumb
         ),
         track = createCheckableColorProvider(
             source = "SwitchColors",
             checked = checkedTrackColor,
             unchecked = uncheckedTrackColor,
-            fallback = R.color.glance_default_switch_track
         )
     )
 }
 
 /**
- * [SwitchColors] set to color resources.
- *
- * These may be fixed colors or a color selector that selects color depending on
- * [android.R.attr.state_checked].
- *
- * @param thumbColor the resource to use to tint the thumb. If an invalid resource id is provided,
- * the default switch colors will be used.
- * @param trackColor the resource to use to tint the track. If an invalid resource id is provided,
- * the default switch colors will be used.
+ * Create a default set of SwitchColors.
  */
-fun SwitchColors(
-    @ColorRes thumbColor: Int,
-    @ColorRes trackColor: Int = R.color.glance_default_switch_track
-): SwitchColors =
-    SwitchColorsImpl(
-        thumb = ResourceCheckableColorProvider(
-            resId = thumbColor,
-            fallback = R.color.glance_default_switch_thumb
-        ),
-        track = ResourceCheckableColorProvider(
-            resId = trackColor,
-            fallback = R.color.glance_default_switch_track
+@Composable
+fun switchColors(): SwitchColors {
+    return if (GlanceTheme.colors == DynamicThemeColorProviders) {
+        SwitchColorsImpl(
+            thumb = ResourceCheckableColorProvider(R.color.glance_default_switch_thumb),
+            track = ResourceCheckableColorProvider(R.color.glance_default_switch_track)
         )
-    )
-
-/** Defaults for the [Switch]. */
-object SwitchDefaults {
-    /** Default [SwitchColors] to apply. */
-    val colors: SwitchColors = SwitchColors()
+    } else {
+        switchColors(
+            checkedThumbColor = GlanceTheme.colors.onPrimary,
+            uncheckedThumbColor = GlanceTheme.colors.outline,
+            checkedTrackColor = GlanceTheme.colors.primary,
+            uncheckedTrackColor = GlanceTheme.colors.surfaceVariant,
+        )
+    }
 }
 
 /**
@@ -132,7 +115,7 @@
     modifier: GlanceModifier = GlanceModifier,
     text: String = "",
     style: TextStyle? = null,
-    colors: SwitchColors = SwitchDefaults.colors,
+    colors: SwitchColors = switchColors(),
     maxLines: Int = Int.MAX_VALUE,
 ) {
     val finalModifier = if (onCheckedChange != null) {
@@ -141,7 +124,7 @@
         modifier
     }
     GlanceNode(
-        factory = ::EmittableSwitch,
+        factory = { EmittableSwitch(colors) },
         update = {
             this.set(checked) { this.checked = it }
             this.set(text) { this.text = it }
@@ -149,24 +132,23 @@
             this.set(style) { this.style = it }
             this.set(colors) { this.colors = it }
             this.set(maxLines) { this.maxLines = it }
-        }
-    )
+        })
 }
 
-internal class EmittableSwitch : Emittable {
+internal class EmittableSwitch(
+    var colors: SwitchColors
+) : Emittable {
     override var modifier: GlanceModifier = GlanceModifier
     var checked: Boolean = false
     var text: String = ""
     var style: TextStyle? = null
-    var colors: SwitchColors = SwitchDefaults.colors
     var maxLines: Int = Int.MAX_VALUE
 
-    override fun copy(): Emittable = EmittableSwitch().also {
+    override fun copy(): Emittable = EmittableSwitch(colors = colors).also {
         it.modifier = modifier
         it.checked = checked
         it.text = text
         it.style = style
-        it.colors = colors
         it.maxLines = maxLines
     }
 
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/RunCallbackAction.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/RunCallbackAction.kt
index 229bb4d..5aeeade 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/RunCallbackAction.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/action/RunCallbackAction.kt
@@ -40,7 +40,7 @@
                 error("Provided class must implement ActionCallback.")
             }
 
-            val actionCallback = workClass.newInstance() as ActionCallback
+            val actionCallback = workClass.getDeclaredConstructor().newInstance() as ActionCallback
             actionCallback.onAction(context, glanceId, parameters)
         }
     }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslator.kt
index 2806c87..06c9f14 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslator.kt
@@ -63,16 +63,14 @@
             is ResourceCheckableColorProvider -> {
                 setCompoundButtonTintList(checkBoxId, colors.resId)
             }
-        }
+        }.let { }
     } else {
         val iconId = inflateViewStub(translationContext, R.id.checkBoxIcon)
         textViewId = inflateViewStub(translationContext, R.id.checkBoxText)
         actionTargetId = viewDef.mainViewId
         setViewEnabled(iconId, element.checked)
-        setImageViewColorFilter(
-            iconId,
-            element.colors.checkBox.getColor(translationContext.context, element.checked)
-        )
+        val color = element.colors.checkBox.getColor(translationContext.context, element.checked)
+        setImageViewColorFilter(iconId, color)
     }
 
     setText(
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslator.kt
index 5cdca85..9b167e2 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslator.kt
@@ -29,6 +29,8 @@
 import androidx.glance.appwidget.unit.isNightMode
 import androidx.glance.appwidget.unit.resolveCheckedColor
 
+internal val checkableColorProviderFallbackColor = Color.Black
+
 private fun CheckedUncheckedColorProvider.toColorStateList(
     context: Context,
     isNightMode: Boolean
@@ -59,14 +61,13 @@
     )
 }
 
-internal fun CheckableColorProvider.getColor(context: Context, isChecked: Boolean) = when (this) {
-    is CheckedUncheckedColorProvider -> getColor(context, context.isNightMode, isChecked)
-    is ResourceCheckableColorProvider -> {
-        // If the user provided res id does not resolve, then resolve using the fallback
-        // (which cannot fail).
-        resolveCheckedColor(context, resId, isChecked)
-            ?: resolveCheckedColor(context, fallback, isChecked)!!
-    }
+internal fun CheckableColorProvider.getColor(context: Context, isChecked: Boolean): Color {
+    return when (this) {
+        is CheckedUncheckedColorProvider -> getColor(context, context.isNightMode, isChecked)
+        is ResourceCheckableColorProvider -> {
+            resolveCheckedColor(context, resId, isChecked)
+        }
+    } ?: checkableColorProviderFallbackColor
 }
 
 /**
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslator.kt
index e20a8e8..336e3ea 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslator.kt
@@ -67,7 +67,8 @@
         textViewId = inflateViewStub(translationContext, R.id.radioText)
         val iconId = inflateViewStub(translationContext, R.id.radioIcon)
         setViewEnabled(iconId, element.checked)
-        setImageViewColorFilter(iconId, element.colors.radio.getColor(context, element.checked))
+        val color = element.colors.radio.getColor(context, element.checked)
+        setImageViewColorFilter(iconId, color)
     }
 
     setText(
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/SwitchTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/SwitchTranslator.kt
index d17ff2d..d1d9afc 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/SwitchTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/SwitchTranslator.kt
@@ -78,8 +78,12 @@
         val trackId = inflateViewStub(translationContext, R.id.switchTrack)
         setViewEnabled(thumbId, element.checked)
         setViewEnabled(trackId, element.checked)
-        setImageViewColorFilter(thumbId, element.colors.thumb.getColor(context, element.checked))
-        setImageViewColorFilter(trackId, element.colors.track.getColor(context, element.checked))
+
+        val thumbColor = element.colors.thumb.getColor(context, element.checked)
+        setImageViewColorFilter(thumbId, thumbColor)
+
+        val trackColor = element.colors.track.getColor(context, element.checked)
+        setImageViewColorFilter(trackId, trackColor)
     }
 
     setText(
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/TextTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/TextTranslator.kt
index 78dd121..7e9c958 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/TextTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/translators/TextTranslator.kt
@@ -151,7 +151,6 @@
                 setTextColor(resId, colorProvider.getColor(translationContext.context).toArgb())
             }
         }
-        null -> {}
         else -> Log.w(GlanceAppWidgetTag, "Unexpected text color: $colorProvider")
     }
 }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt
index e6c1775..0e848d0 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/unit/ColorProvider.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.core.content.ContextCompat
 import androidx.glance.appwidget.GlanceAppWidgetTag
+import androidx.glance.appwidget.R
 import androidx.glance.unit.ColorProvider
 import androidx.glance.unit.FixedColorProvider
 import androidx.glance.unit.ResourceColorProvider
@@ -43,16 +44,14 @@
 }
 
 internal val Context.isNightMode: Boolean
-    get() =
-        resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
-            Configuration.UI_MODE_NIGHT_YES
+    get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
+        Configuration.UI_MODE_NIGHT_YES
 
 /** Provider of different colors depending on a checked state. */
 internal sealed interface CheckableColorProvider
 
 internal data class ResourceCheckableColorProvider(
     @ColorRes val resId: Int,
-    @ColorRes val fallback: Int
 ) : CheckableColorProvider
 
 /**
@@ -61,9 +60,8 @@
  */
 internal data class CheckedUncheckedColorProvider private constructor(
     private val source: String,
-    private val checked: ColorProvider?,
-    private val unchecked: ColorProvider?,
-    @ColorRes private val fallback: Int
+    private val checked: ColorProvider,
+    private val unchecked: ColorProvider,
 ) : CheckableColorProvider {
 
     init {
@@ -72,43 +70,29 @@
         }
     }
 
-    private fun ColorProvider?.toDayNightColorProvider(
-        context: Context,
-        isChecked: Boolean
-    ) = when (this) {
-        is DayNightColorProvider -> this
-        is FixedColorProvider -> DayNightColorProvider(color, color)
-        else -> {
-            if (this != null) {
-                Log.w(GlanceAppWidgetTag, "Unexpected ColorProvider for $source: $this")
-            }
-            val day = resolveCheckedColor(context, fallback, isChecked, isNightMode = false)!!
-            val night = resolveCheckedColor(context, fallback, isChecked, isNightMode = true)!!
-            DayNightColorProvider(day = day, night = night)
-        }
-    }
-
     /**
      * Resolves the [CheckedUncheckedColorProvider] to a single [Color] given the night mode and
      * checked states.
      */
     fun getColor(context: Context, isNightMode: Boolean, isChecked: Boolean) = when {
-        isChecked -> checked.toDayNightColorProvider(context, isChecked).getColor(isNightMode)
-        else -> unchecked.toDayNightColorProvider(context, isChecked).getColor(isNightMode)
+        isChecked -> getColor(colorProvider = checked, isNightMode = isNightMode, context = context)
+        else -> getColor(colorProvider = unchecked, isNightMode = isNightMode, context = context)
     }
 
+    private fun getColor(colorProvider: ColorProvider, isNightMode: Boolean, context: Context) =
+        if (colorProvider is DayNightColorProvider) {
+            colorProvider.getColor(isNightMode)
+        } else {
+            colorProvider.getColor(context)
+        }
+
     companion object {
         fun createCheckableColorProvider(
             source: String,
-            checked: ColorProvider?,
-            unchecked: ColorProvider?,
-            @ColorRes fallback: Int
+            checked: ColorProvider,
+            unchecked: ColorProvider,
         ): CheckableColorProvider {
-            return if (checked == null && unchecked == null) {
-                ResourceCheckableColorProvider(fallback, fallback)
-            } else {
-                CheckedUncheckedColorProvider(source, checked, unchecked, fallback)
-            }
+            return CheckedUncheckedColorProvider(source, checked, unchecked)
         }
     }
 }
@@ -138,8 +122,7 @@
     }
     return Color(
         colorStateList.getColorForState(
-            if (isChecked) CheckedStateSet else UncheckedStateSet,
-            colorStateList.defaultColor
+            if (isChecked) CheckedStateSet else UncheckedStateSet, colorStateList.defaultColor
         )
     )
 }
diff --git a/glance/glance-appwidget/src/androidMain/res/color/glance_default_check_box.xml b/glance/glance-appwidget/src/androidMain/res/color/glance_default_check_box.xml
index 637d59c..00289b5 100644
--- a/glance/glance-appwidget/src/androidMain/res/color/glance_default_check_box.xml
+++ b/glance/glance-appwidget/src/androidMain/res/color/glance_default_check_box.xml
@@ -16,5 +16,5 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="?android:attr/colorControlActivated" android:state_checked="true"/>
-    <item android:color="?android:attr/colorControlNormal" android:alpha="0.6"/>
+    <item android:color="?android:attr/colorControlNormal" />
 </selector>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
index 3979e10..1aa10a4 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
@@ -37,6 +37,7 @@
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import org.mockito.kotlin.mock
+import kotlin.test.assertIs
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
@@ -46,7 +47,6 @@
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
-import kotlin.test.assertIs
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
@@ -97,7 +97,7 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        assertThat(view.text).isEqualTo("40.0.dp x 50.0.dp")
+        assertThat(view.text.toString()).isEqualTo("40.0.dp x 50.0.dp")
     }
 
     @Test
@@ -121,7 +121,7 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        assertThat(view.text).isEqualTo("FOUND")
+        assertThat(view.text.toString()).isEqualTo("FOUND")
     }
 
     @Test
@@ -144,7 +144,7 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        assertThat(view.text).isEqualTo("AppWidgetId(appWidgetId=1)")
+        assertThat(view.text.toString()).isEqualTo("AppWidgetId(appWidgetId=1)")
     }
 
     @Test
@@ -176,7 +176,7 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        assertThat(view.text).isEqualTo("40.0.dp x 50.0.dp")
+        assertThat(view.text.toString()).isEqualTo("40.0.dp x 50.0.dp")
     }
 
     @Config(sdk = [30])
@@ -201,11 +201,11 @@
 
         val portraitView = createPortraitContext().applyRemoteViews(rv)
         assertIs<TextView>(portraitView)
-        assertThat(portraitView.text).isEqualTo("50.0.dp x 100.0.dp")
+        assertThat(portraitView.text.toString()).isEqualTo("50.0.dp x 100.0.dp")
 
         val landscapeView = createLandscapeContext().applyRemoteViews(rv)
         assertIs<TextView>(landscapeView)
-        assertThat(landscapeView.text).isEqualTo("100.0.dp x 50.0.dp")
+        assertThat(landscapeView.text.toString()).isEqualTo("100.0.dp x 50.0.dp")
     }
 
     @Config(sdk = [30])
@@ -237,11 +237,11 @@
 
         val portraitView = createPortraitContext().applyRemoteViews(rv)
         assertIs<TextView>(portraitView)
-        assertThat(portraitView.text).isEqualTo("60.0.dp x 80.0.dp")
+        assertThat(portraitView.text.toString()).isEqualTo("60.0.dp x 80.0.dp")
 
         val landscapeView = createLandscapeContext().applyRemoteViews(rv)
         assertIs<TextView>(landscapeView)
-        assertThat(landscapeView.text).isEqualTo("100.0.dp x 70.0.dp")
+        assertThat(landscapeView.text.toString()).isEqualTo("100.0.dp x 70.0.dp")
     }
 
     @Test
@@ -273,11 +273,11 @@
 
             val portraitView = createPortraitContext().applyRemoteViews(rv)
             assertIs<TextView>(portraitView)
-            assertThat(portraitView.text).isEqualTo("40.0.dp x 50.0.dp")
+            assertThat(portraitView.text.toString()).isEqualTo("40.0.dp x 50.0.dp")
 
             val landscapeView = createLandscapeContext().applyRemoteViews(rv)
             assertIs<TextView>(landscapeView)
-            assertThat(landscapeView.text).isEqualTo("40.0.dp x 50.0.dp")
+            assertThat(landscapeView.text.toString()).isEqualTo("40.0.dp x 50.0.dp")
         }
     }
 
@@ -307,11 +307,11 @@
 
         val portraitView = createPortraitContext().applyRemoteViews(rv)
         assertIs<TextView>(portraitView)
-        assertThat(portraitView.text).isEqualTo("60.0.dp x 80.0.dp")
+        assertThat(portraitView.text.toString()).isEqualTo("60.0.dp x 80.0.dp")
 
         val landscapeView = createLandscapeContext().applyRemoteViews(rv)
         assertIs<TextView>(landscapeView)
-        assertThat(landscapeView.text).isEqualTo("60.0.dp x 80.0.dp")
+        assertThat(landscapeView.text.toString()).isEqualTo("60.0.dp x 80.0.dp")
     }
 
     @Test
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceRemoteViewsTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceRemoteViewsTest.kt
index 40a463c..e080178 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceRemoteViewsTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceRemoteViewsTest.kt
@@ -65,7 +65,7 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        Truth.assertThat(view.text).isEqualTo("40.0.dp x 50.0.dp")
+        Truth.assertThat(view.text.toString()).isEqualTo("40.0.dp x 50.0.dp")
     }
 
     @Test
@@ -85,7 +85,7 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        Truth.assertThat(view.text).isEqualTo("FOUND")
+        Truth.assertThat(view.text.toString()).isEqualTo("FOUND")
     }
 
     @Test
@@ -100,6 +100,6 @@
 
         val view = context.applyRemoteViews(rv)
         assertIs<TextView>(view)
-        Truth.assertThat(view.text).isEqualTo("No error thrown")
+        Truth.assertThat(view.text.toString()).isEqualTo("No error thrown")
     }
 }
\ No newline at end of file
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 bf5d369..66cce3c 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
@@ -47,9 +47,9 @@
 import androidx.glance.appwidget.LinearLayoutSubject.Companion.assertThat
 import androidx.glance.appwidget.TextViewSubject.Companion.assertThat
 import androidx.glance.appwidget.ViewSubject.Companion.assertThat
+import androidx.glance.appwidget.lazy.GridCells
 import androidx.glance.appwidget.lazy.LazyColumn
 import androidx.glance.appwidget.lazy.LazyVerticalGrid
-import androidx.glance.appwidget.lazy.GridCells
 import androidx.glance.appwidget.lazy.ReservedItemIdRangeEnd
 import androidx.glance.appwidget.test.R
 import androidx.glance.background
@@ -66,6 +66,8 @@
 import androidx.glance.visibility
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -76,8 +78,6 @@
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.shadows.ShadowLog
-import kotlin.test.assertFailsWith
-import kotlin.test.assertIs
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
@@ -874,9 +874,9 @@
 
         val view = context.applyRemoteViews(rv)
 
-        val firstText = checkNotNull(view.findView<TextView> { it.text == "first" })
-        val secondText = checkNotNull(view.findView<TextView> { it.text == "second" })
-        val thirdText = checkNotNull(view.findView<TextView> { it.text == "third" })
+        val firstText = checkNotNull(view.findView<TextView> { it.text.toString() == "first" })
+        val secondText = checkNotNull(view.findView<TextView> { it.text.toString() == "second" })
+        val thirdText = checkNotNull(view.findView<TextView> { it.text.toString() == "third" })
 
         assertThat(firstText.visibility).isEqualTo(View.INVISIBLE)
         assertThat(secondText.visibility).isEqualTo(View.GONE)
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt
index c380403..b3073b3 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt
@@ -22,7 +22,7 @@
 import android.widget.ImageView
 import androidx.compose.ui.graphics.Color
 import androidx.glance.appwidget.CheckBox
-import androidx.glance.appwidget.CheckBoxColors
+import androidx.glance.appwidget.checkBoxColors
 import androidx.glance.appwidget.ImageViewSubject.Companion.assertThat
 import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.appwidget.action.actionRunCallback
@@ -30,10 +30,11 @@
 import androidx.glance.appwidget.configurationContext
 import androidx.glance.appwidget.findViewByType
 import androidx.glance.appwidget.runAndTranslate
-import androidx.glance.appwidget.test.R
 import androidx.glance.appwidget.unit.ColorProvider
+import androidx.glance.unit.FixedColorProvider
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertIs
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -42,7 +43,6 @@
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
-import kotlin.test.assertIs
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
@@ -66,7 +66,7 @@
                 checked = false,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(checkedColor = Color.Red, uncheckedColor = Color.Blue)
+                colors = checkBoxColors(checkedColor = Color.Red, uncheckedColor = Color.Blue)
             )
         }
 
@@ -83,7 +83,7 @@
                 checked = true,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(checkedColor = Color.Red, uncheckedColor = Color.Blue)
+                colors = checkBoxColors(checkedColor = Color.Red, uncheckedColor = Color.Blue)
             )
         }
 
@@ -100,7 +100,7 @@
                 checked = false,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(
+                colors = checkBoxColors(
                     checkedColor = ColorProvider(day = Color.Red, night = Color.Blue),
                     uncheckedColor = ColorProvider(day = Color.Yellow, night = Color.Green)
                 )
@@ -120,7 +120,7 @@
                 checked = false,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(
+                colors = checkBoxColors(
                     checkedColor = ColorProvider(day = Color.Red, night = Color.Blue),
                     uncheckedColor = ColorProvider(day = Color.Yellow, night = Color.Green)
                 )
@@ -140,7 +140,7 @@
                 checked = true,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(
+                colors = checkBoxColors(
                     checkedColor = ColorProvider(day = Color.Red, night = Color.Blue),
                     uncheckedColor = ColorProvider(day = Color.Yellow, night = Color.Green)
                 )
@@ -160,7 +160,7 @@
                 checked = true,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(
+                colors = checkBoxColors(
                     checkedColor = ColorProvider(day = Color.Red, night = Color.Blue),
                     uncheckedColor = ColorProvider(day = Color.Yellow, night = Color.Green)
                 )
@@ -180,13 +180,16 @@
                 checked = true,
                 onCheckedChange = null,
                 text = "Check",
-                colors = CheckBoxColors(R.color.my_checkbox_colors)
+                colors = checkBoxColors(
+                    checkedColor = FixedColorProvider(Color.Red),
+                    uncheckedColor = FixedColorProvider(Color.Blue)
+                )
             )
         }
 
         val checkBoxRoot = assertIs<ViewGroup>(context.applyRemoteViews(rv))
         val icon = checkBoxRoot.findViewByType<ImageView>()
-        assertThat(icon).hasColorFilter("#FF0000")
+        assertThat(icon).hasColorFilter(Color.Red)
     }
 
     @Config(sdk = [29])
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslatorTest.kt
index 0ccc124..7b0067b 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CompoundButtonTranslatorTest.kt
@@ -35,29 +35,26 @@
     fun resolveColorResource_0_shouldReturnFallback() {
         val colorProvider = ResourceCheckableColorProvider(
             resId = 0,
-            fallback = R.color.my_color
         )
 
         assertThat(colorProvider.getColor(lightContext, isChecked = true))
-            .isSameColorAs("#EEEEEE")
+            .isSameColorAs(checkableColorProviderFallbackColor)
     }
 
     @Test
     fun resolveColorResource_invalid_shouldReturnFallback() {
         val colorProvider = ResourceCheckableColorProvider(
             resId = -1,
-            fallback = R.color.my_color
         )
 
         assertThat(colorProvider.getColor(lightContext, isChecked = true))
-            .isSameColorAs("#EEEEEE")
+            .isSameColorAs(checkableColorProviderFallbackColor)
     }
 
     @Test
     fun resolveColorResource_valid_day_shouldReturnResolvedColor() {
         val colorProvider = ResourceCheckableColorProvider(
             resId = R.color.my_checkbox_colors,
-            fallback = R.color.my_color
         )
 
         assertThat(colorProvider.getColor(lightContext, isChecked = true))
@@ -68,7 +65,6 @@
     fun resolveColorResource_valid_night_shouldReturnResolvedColor() {
         val colorProvider = ResourceCheckableColorProvider(
             resId = R.color.my_checkbox_colors,
-            fallback = R.color.my_color
         )
 
         assertThat(colorProvider.getColor(darkContext, isChecked = true))
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt
index 40fb745..8c645ee 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt
@@ -24,7 +24,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.glance.appwidget.ImageViewSubject.Companion.assertThat
 import androidx.glance.appwidget.RadioButton
-import androidx.glance.appwidget.RadioButtonColors
+import androidx.glance.appwidget.radioButtonColors
 import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.appwidget.action.actionRunCallback
 import androidx.glance.appwidget.applyRemoteViews
@@ -32,7 +32,6 @@
 import androidx.glance.appwidget.findView
 import androidx.glance.appwidget.findViewByType
 import androidx.glance.appwidget.runAndTranslate
-import androidx.glance.appwidget.test.R
 import androidx.glance.appwidget.unit.ColorProvider
 import androidx.glance.unit.ColorProvider
 import androidx.test.core.app.ApplicationProvider
@@ -70,7 +69,7 @@
                 checked = false,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
+                colors = radioButtonColors(
                     checkedColor = ColorProvider(Color.Blue),
                     uncheckedColor = ColorProvider(Color.Red),
                 )
@@ -88,7 +87,7 @@
                 checked = true,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
+                colors = radioButtonColors(
                     checkedColor = ColorProvider(Color.Blue),
                     uncheckedColor = ColorProvider(Color.Red),
                 )
@@ -106,7 +105,7 @@
                 checked = false,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
+                colors = radioButtonColors(
                     checkedColor = ColorProvider(day = Color.Blue, night = Color.Red),
                     uncheckedColor = ColorProvider(day = Color.Green, night = Color.Yellow),
                 )
@@ -124,7 +123,7 @@
                 checked = false,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
+                colors = radioButtonColors(
                     checkedColor = ColorProvider(day = Color.Blue, night = Color.Red),
                     uncheckedColor = ColorProvider(day = Color.Green, night = Color.Yellow),
                 )
@@ -142,7 +141,7 @@
                 checked = true,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
+                colors = radioButtonColors(
                     checkedColor = ColorProvider(day = Color.Blue, night = Color.Red),
                     uncheckedColor = ColorProvider(day = Color.Green, night = Color.Yellow),
                 )
@@ -160,7 +159,7 @@
                 checked = true,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
+                colors = radioButtonColors(
                     checkedColor = ColorProvider(day = Color.Blue, night = Color.Red),
                     uncheckedColor = ColorProvider(day = Color.Green, night = Color.Yellow),
                 )
@@ -178,14 +177,15 @@
                 checked = false,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
-                    color = R.color.my_switch_thumb_colors,
+                colors = radioButtonColors(
+                    checkedColor = ColorProvider(day = Color.Blue, night = Color.Red),
+                    uncheckedColor = ColorProvider(day = Color.Green, night = Color.Yellow),
                 )
             )
         }
 
         val radioButtonRoot = assertIs<ViewGroup>(lightContext.applyRemoteViews(rv))
-        assertThat(radioButtonRoot.radioImageView).hasColorFilter("#110FF0FF")
+        assertThat(radioButtonRoot.radioImageView).hasColorFilter(Color.Green)
     }
 
     @Test
@@ -195,14 +195,15 @@
                 checked = true,
                 onClick = null,
                 text = "RadioButton",
-                colors = RadioButtonColors(
-                    color = R.color.my_switch_thumb_colors,
+                colors = radioButtonColors(
+                    checkedColor = ColorProvider(day = Color.Blue, night = Color.Red),
+                    uncheckedColor = ColorProvider(day = Color.Green, night = Color.Yellow),
                 )
             )
         }
 
         val radioButtonRoot = assertIs<ViewGroup>(lightContext.applyRemoteViews(rv))
-        assertThat(radioButtonRoot.radioImageView).hasColorFilter("#11040040")
+        assertThat(radioButtonRoot.radioImageView).hasColorFilter(Color.Blue)
     }
 
     @Test
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt
index 8cbe211..89d3739 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt
@@ -23,18 +23,18 @@
 import androidx.compose.ui.graphics.Color
 import androidx.glance.appwidget.ImageViewSubject.Companion.assertThat
 import androidx.glance.appwidget.Switch
-import androidx.glance.appwidget.SwitchColors
 import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.appwidget.action.actionRunCallback
 import androidx.glance.appwidget.applyRemoteViews
 import androidx.glance.appwidget.configurationContext
 import androidx.glance.appwidget.findView
 import androidx.glance.appwidget.runAndTranslate
-import androidx.glance.appwidget.test.R
+import androidx.glance.appwidget.switchColors
 import androidx.glance.appwidget.unit.ColorProvider
 import androidx.glance.unit.ColorProvider
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertIs
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -44,7 +44,6 @@
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
-import kotlin.test.assertIs
 
 @Config(sdk = [29])
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -68,7 +67,7 @@
                 checked = false,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
+                colors = switchColors(
                     checkedThumbColor = ColorProvider(Color.Blue),
                     uncheckedThumbColor = ColorProvider(Color.Red),
                     checkedTrackColor = ColorProvider(Color.Green),
@@ -89,7 +88,7 @@
                 checked = true,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
+                colors = switchColors(
                     checkedThumbColor = ColorProvider(Color.Blue),
                     uncheckedThumbColor = ColorProvider(Color.Red),
                     checkedTrackColor = ColorProvider(Color.Green),
@@ -105,98 +104,102 @@
 
     @Test
     fun canTranslateSwitch_dayNight_unchecked_day() = fakeCoroutineScope.runTest {
+        val thumbColor = Color.Blue
+        val trackColor = Color.Cyan
+        val otherColor = Color.White
+
         val rv = lightContext.runAndTranslate {
             Switch(
                 checked = false,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
-                    checkedThumbColor = ColorProvider(day = Color.Blue, night = Color.Red),
-                    uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Yellow),
-                    checkedTrackColor = ColorProvider(day = Color.White, night = Color.Black),
-                    uncheckedTrackColor = ColorProvider(
-                        day = Color.DarkGray,
-                        night = Color.LightGray
-                    )
+                colors = switchColors(
+                    checkedThumbColor = ColorProvider(otherColor),
+                    uncheckedThumbColor = ColorProvider(day = thumbColor, night = otherColor),
+                    checkedTrackColor = ColorProvider(otherColor),
+                    uncheckedTrackColor = ColorProvider(day = trackColor, night = otherColor)
                 )
             )
         }
 
         val switchRoot = assertIs<ViewGroup>(lightContext.applyRemoteViews(rv))
-        assertThat(switchRoot.thumbImageView).hasColorFilter(Color.Green)
-        assertThat(switchRoot.trackImageView).hasColorFilter(Color.DarkGray)
+        assertThat(switchRoot.thumbImageView).hasColorFilter(thumbColor)
+        assertThat(switchRoot.trackImageView).hasColorFilter(trackColor)
     }
 
     @Test
     fun canTranslateSwitch_dayNight_unchecked_night() = fakeCoroutineScope.runTest {
+        val thumbColor = Color.Blue
+        val trackColor = Color.Cyan
+        val otherColor = Color.White
+
         val rv = darkContext.runAndTranslate {
             Switch(
                 checked = false,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
-                    checkedThumbColor = ColorProvider(day = Color.Blue, night = Color.Red),
-                    uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Yellow),
-                    checkedTrackColor = ColorProvider(day = Color.White, night = Color.Black),
-                    uncheckedTrackColor = ColorProvider(
-                        day = Color.DarkGray,
-                        night = Color.LightGray
-                    )
+                colors = switchColors(
+                    checkedThumbColor = ColorProvider(otherColor),
+                    uncheckedThumbColor = ColorProvider(day = otherColor, night = thumbColor),
+                    checkedTrackColor = ColorProvider(otherColor),
+                    uncheckedTrackColor = ColorProvider(day = otherColor, night = trackColor)
                 )
             )
         }
 
         val switchRoot = assertIs<ViewGroup>(darkContext.applyRemoteViews(rv))
-        assertThat(switchRoot.thumbImageView).hasColorFilter(Color.Yellow)
-        assertThat(switchRoot.trackImageView).hasColorFilter(Color.LightGray)
+        assertThat(switchRoot.thumbImageView).hasColorFilter(thumbColor)
+        assertThat(switchRoot.trackImageView).hasColorFilter(trackColor)
     }
 
     @Test
     fun canTranslateSwitch_dayNight_checked_day() = fakeCoroutineScope.runTest {
+        val thumbColor = Color.Blue
+        val trackColor = Color.Cyan
+        val otherColor = Color.White
+
         val rv = lightContext.runAndTranslate {
             Switch(
                 checked = true,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
-                    checkedThumbColor = ColorProvider(day = Color.Blue, night = Color.Red),
-                    uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Yellow),
-                    checkedTrackColor = ColorProvider(day = Color.White, night = Color.Black),
-                    uncheckedTrackColor = ColorProvider(
-                        day = Color.DarkGray,
-                        night = Color.LightGray
-                    )
+                colors = switchColors(
+                    checkedThumbColor = ColorProvider(day = thumbColor, night = otherColor),
+                    uncheckedThumbColor = ColorProvider(day = otherColor, night = otherColor),
+                    checkedTrackColor = ColorProvider(day = trackColor, night = otherColor),
+                    uncheckedTrackColor = ColorProvider(day = otherColor, night = otherColor)
                 )
             )
         }
 
         val switchRoot = assertIs<ViewGroup>(lightContext.applyRemoteViews(rv))
-        assertThat(switchRoot.thumbImageView).hasColorFilter(Color.Blue)
-        assertThat(switchRoot.trackImageView).hasColorFilter(Color.White)
+        assertThat(switchRoot.thumbImageView).hasColorFilter(thumbColor)
+        assertThat(switchRoot.trackImageView).hasColorFilter(trackColor)
     }
 
     @Test
     fun canTranslateSwitch_dayNight_checked_night() = fakeCoroutineScope.runTest {
+        val thumbColor = Color.Blue
+        val trackColor = Color.Cyan
+        val otherColor = Color.White
+
         val rv = darkContext.runAndTranslate {
             Switch(
                 checked = true,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
-                    checkedThumbColor = ColorProvider(day = Color.Blue, night = Color.Red),
-                    uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Yellow),
-                    checkedTrackColor = ColorProvider(day = Color.White, night = Color.Black),
-                    uncheckedTrackColor = ColorProvider(
-                        day = Color.DarkGray,
-                        night = Color.LightGray
-                    )
+                colors = switchColors(
+                    checkedThumbColor = ColorProvider(day = otherColor, night = thumbColor),
+                    uncheckedThumbColor = ColorProvider(otherColor),
+                    checkedTrackColor = ColorProvider(day = otherColor, night = trackColor),
+                    uncheckedTrackColor = ColorProvider(otherColor)
                 )
             )
         }
 
         val switchRoot = assertIs<ViewGroup>(darkContext.applyRemoteViews(rv))
-        assertThat(switchRoot.thumbImageView).hasColorFilter(Color.Red)
-        assertThat(switchRoot.trackImageView).hasColorFilter(Color.Black)
+        assertThat(switchRoot.thumbImageView).hasColorFilter(thumbColor)
+        assertThat(switchRoot.trackImageView).hasColorFilter(trackColor)
     }
 
     @Test
@@ -206,16 +209,18 @@
                 checked = false,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
-                    thumbColor = R.color.my_switch_thumb_colors,
-                    trackColor = R.color.my_switch_track_colors
+                colors = switchColors(
+                    checkedThumbColor = ColorProvider(Color.Magenta),
+                    uncheckedThumbColor = ColorProvider(Color.Red),
+                    checkedTrackColor = ColorProvider(Color.Magenta),
+                    uncheckedTrackColor = ColorProvider(Color.Blue)
                 )
             )
         }
 
         val switchRoot = assertIs<ViewGroup>(lightContext.applyRemoteViews(rv))
-        assertThat(switchRoot.thumbImageView).hasColorFilter("#110FF0FF")
-        assertThat(switchRoot.trackImageView).hasColorFilter("#220FF0FF")
+        assertThat(switchRoot.thumbImageView).hasColorFilter(Color.Red)
+        assertThat(switchRoot.trackImageView).hasColorFilter(Color.Blue)
     }
 
     @Test
@@ -225,16 +230,18 @@
                 checked = true,
                 onCheckedChange = null,
                 text = "Switch",
-                colors = SwitchColors(
-                    thumbColor = R.color.my_switch_thumb_colors,
-                    trackColor = R.color.my_switch_track_colors
+                colors = switchColors(
+                    checkedThumbColor = ColorProvider(Color.Red),
+                    uncheckedThumbColor = ColorProvider(Color.Magenta),
+                    checkedTrackColor = ColorProvider(Color.Blue),
+                    uncheckedTrackColor = ColorProvider(Color.Magenta),
                 )
             )
         }
 
         val switchRoot = assertIs<ViewGroup>(lightContext.applyRemoteViews(rv))
-        assertThat(switchRoot.thumbImageView).hasColorFilter("#11040040")
-        assertThat(switchRoot.trackImageView).hasColorFilter("#22040040")
+        assertThat(switchRoot.thumbImageView).hasColorFilter(Color.Red)
+        assertThat(switchRoot.trackImageView).hasColorFilter(Color.Blue)
     }
 
     @Test
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/unit/ColorProviderTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/unit/ColorProviderTest.kt
index b2a5637..d52ed7e 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/unit/ColorProviderTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/unit/ColorProviderTest.kt
@@ -37,65 +37,11 @@
     private val context = ApplicationProvider.getApplicationContext<Context>()
 
     @Test
-    fun createCheckableColorProvider_checkedNull_uncheckedNull_shouldFallBackToSingleResource() {
-        val provider = createCheckableColorProvider(
-            source = "ColorProviderTest",
-            checked = null,
-            unchecked = null,
-            fallback = R.color.my_checkbox_colors
-        )
-
-        assertIs<ResourceCheckableColorProvider>(provider)
-        assertThat(provider.resId).isEqualTo(R.color.my_checkbox_colors)
-    }
-
-    @Test
-    fun createCheckableColorProvider_checkedNull_uncheckedNotNull_shouldUseCheckedFromFallback() {
-        val provider = createCheckableColorProvider(
-            source = "ColorProviderTest",
-            checked = null,
-            unchecked = ColorProvider(day = Color.Red, night = Color.Yellow),
-            fallback = R.color.my_checkbox_colors
-        )
-
-        assertIs<CheckedUncheckedColorProvider>(provider)
-        assertThat(provider.getColor(context, isNightMode = false, isChecked = true))
-            .isSameColorAs("#FF0000")
-        assertThat(provider.getColor(context, isNightMode = true, isChecked = true))
-            .isSameColorAs("#FFFF00")
-        assertThat(provider.getColor(context, isNightMode = false, isChecked = false))
-            .isSameColorAs(Color.Red)
-        assertThat(provider.getColor(context, isNightMode = true, isChecked = false))
-            .isSameColorAs(Color.Yellow)
-    }
-
-    @Test
-    fun createCheckableColorProvider_checkedNotNull_uncheckedNull_shouldUseUncheckedFromFallback() {
-        val provider = createCheckableColorProvider(
-            source = "ColorProviderTest",
-            checked = ColorProvider(day = Color.Blue, night = Color.Green),
-            unchecked = null,
-            fallback = R.color.my_checkbox_colors
-        )
-
-        assertIs<CheckedUncheckedColorProvider>(provider)
-        assertThat(provider.getColor(context, isNightMode = false, isChecked = true))
-            .isSameColorAs(Color.Blue)
-        assertThat(provider.getColor(context, isNightMode = true, isChecked = true))
-            .isSameColorAs(Color.Green)
-        assertThat(provider.getColor(context, isNightMode = false, isChecked = false))
-            .isSameColorAs("#0000FF")
-        assertThat(provider.getColor(context, isNightMode = true, isChecked = false))
-            .isSameColorAs("#00FFFF")
-    }
-
-    @Test
     fun createCheckableColorProvider_checkedNotNull_uncheckedNotNull_shouldNotUseFallback() {
         val provider = createCheckableColorProvider(
             source = "ColorProviderTest",
             checked = ColorProvider(day = Color.Blue, night = Color.Green),
             unchecked = ColorProvider(day = Color.Red, night = Color.Yellow),
-            fallback = R.color.my_checkbox_colors
         )
 
         assertIs<CheckedUncheckedColorProvider>(provider)
@@ -115,7 +61,6 @@
             source = "ColorProviderTest",
             checked = ColorProvider(Color.Blue),
             unchecked = ColorProvider(Color.Red),
-            fallback = R.color.my_checkbox_colors
         )
 
         assertIs<CheckedUncheckedColorProvider>(provider)
@@ -136,7 +81,6 @@
                 source = "ColorProviderTest",
                 checked = ColorProvider(day = Color.Blue, night = Color.Green),
                 unchecked = ColorProvider(R.color.my_checkbox_colors),
-                fallback = R.color.my_checkbox_colors
             )
         }
     }
@@ -148,7 +92,6 @@
                 source = "ColorProviderTest",
                 checked = ColorProvider(R.color.my_checkbox_colors),
                 unchecked = ColorProvider(Color.Blue),
-                fallback = R.color.my_checkbox_colors
             )
         }
     }
diff --git a/glance/glance-wear-tiles/src/androidAndroidTest/kotlin/androidx/glance/wear/tiles/ScreenshotTests.kt b/glance/glance-wear-tiles/src/androidAndroidTest/kotlin/androidx/glance/wear/tiles/ScreenshotTests.kt
index bc60ddf..13e0b39 100644
--- a/glance/glance-wear-tiles/src/androidAndroidTest/kotlin/androidx/glance/wear/tiles/ScreenshotTests.kt
+++ b/glance/glance-wear-tiles/src/androidAndroidTest/kotlin/androidx/glance/wear/tiles/ScreenshotTests.kt
@@ -83,6 +83,10 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class ScreenshotTests {
+
+    private val defaultTextColor = ColorProvider(Color.White)
+    private val defaultTextStyle = TextStyle(defaultTextColor)
+
     @get:Rule
     var screenshotRule = AndroidXScreenshotTestRule("glance/glance-wear-tiles")
 
@@ -157,13 +161,16 @@
     @Test
     fun basicText() = runSingleGoldenTest("basic-text") {
         Column {
-            Text(text = "Normal")
-            Text(text = "Bold", style = TextStyle(fontWeight = FontWeight.Bold))
-            Text(text = "Italic", style = TextStyle(fontStyle = FontStyle.Italic))
-            Text(text = "Underline", style = TextStyle(textDecoration = TextDecoration.Underline))
+            Text(text = "Normal", style = defaultTextStyle)
+            Text(text = "Bold", style = defaultTextStyle.copy(fontWeight = FontWeight.Bold))
+            Text(text = "Italic", style = defaultTextStyle.copy(fontStyle = FontStyle.Italic))
+            Text(
+                text = "Underline",
+                style = defaultTextStyle.copy(textDecoration = TextDecoration.Underline)
+            )
             Text(
                 text = "Everything",
-                style = TextStyle(
+                style = defaultTextStyle.copy(
                     fontWeight = FontWeight.Bold,
                     fontStyle = FontStyle.Italic,
                     textDecoration = TextDecoration.Underline
@@ -178,23 +185,25 @@
             Column {
                 Text(
                     text = "Hello World",
+                    style = defaultTextStyle,
                     modifier = GlanceModifier.width(95.dp).height(60.dp).background(Color.Green)
                 )
                 Text(
                     text = "Hello World",
-                    style = TextStyle(textAlign = TextAlign.Start),
+                    style = defaultTextStyle.copy(textAlign = TextAlign.Start),
                     modifier = GlanceModifier.width(95.dp).height(60.dp).background(Color.Blue)
                 )
 
                 Text(
                     text = "Hello World",
-                    style = TextStyle(textAlign = TextAlign.End),
+                    style = defaultTextStyle.copy(textAlign = TextAlign.End),
                     modifier = GlanceModifier.width(95.dp).height(60.dp).background(Color.Red)
                 )
             }
             Column {
                 Text(
                     text = "Hello World! This is a multiline test",
+                    style = defaultTextStyle,
                     maxLines = 3,
                     modifier = GlanceModifier.width(100.dp).height(60.dp).background(Color.Red)
                 )
@@ -202,7 +211,7 @@
                 Text(
                     text = "Hello World! This is a multiline test",
                     maxLines = 3,
-                    style = TextStyle(
+                    style = defaultTextStyle.copy(
                         textAlign = TextAlign.Start
                     ),
                     modifier = GlanceModifier.width(100.dp).height(60.dp).background(Color.Green)
@@ -211,7 +220,7 @@
                 Text(
                     text = "Hello World! This is a multiline test",
                     maxLines = 3,
-                    style = TextStyle(
+                    style = defaultTextStyle.copy(
                         textAlign = TextAlign.End
                     ),
                     modifier = GlanceModifier.width(100.dp).height(60.dp).background(Color.Blue)
@@ -331,7 +340,7 @@
     @Test
     fun visibility() = runSingleGoldenTest("visibility") {
         Column(modifier = GlanceModifier.fillMaxSize().background(Color.DarkGray)) {
-            Text("First", style = TextStyle(color = ColorProvider(Color.Red)))
+            Text("First", style = defaultTextStyle.copy(color = ColorProvider(Color.Red)))
             Text("gone", modifier = GlanceModifier.visibility(Visibility.Gone))
             Row {
                 Text(
@@ -339,15 +348,15 @@
                     modifier = GlanceModifier.visibility(Visibility.Invisible)
                         .background(ColorProvider(Color.Red))
                 )
-                Text("after")
+                Text("after", style = defaultTextStyle)
             }
-            Text("Third")
+            Text("Third", style = defaultTextStyle)
             Row(
                 modifier = GlanceModifier.visibility(Visibility.Invisible).background(Color.Green)
             ) {
                 Spacer(modifier = GlanceModifier.size(10.dp).background(Color.Red))
             }
-            Text("Last")
+            Text("Last", style = defaultTextStyle)
         }
     }
 
@@ -418,8 +427,6 @@
         )
 
         // Blit it to a bitmap for further testing.
-
-        // Blit it to a bitmap for further testing.
         val bmp = Bitmap.createBitmap(
             SCREEN_WIDTH,
             SCREEN_HEIGHT,
diff --git a/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/WearCompositionTranslator.kt b/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/WearCompositionTranslator.kt
index 141f83b..cec3b08 100644
--- a/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/WearCompositionTranslator.kt
+++ b/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/WearCompositionTranslator.kt
@@ -406,7 +406,7 @@
 ): LayoutElementBuilders.FontStyle {
     val fontStyleBuilder = LayoutElementBuilders.FontStyle.Builder()
 
-    style.color?.let { fontStyleBuilder.setColor(argb(it.getColorAsArgb(context))) }
+    style.color.let { fontStyleBuilder.setColor(argb(it.getColorAsArgb(context))) }
     // TODO(b/203656358): Can we support Em here too?
     style.fontSize?.let {
         if (!it.isSp) {
diff --git a/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/action/RunCallbackAction.kt b/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/action/RunCallbackAction.kt
index 0e0a597..88293fe 100644
--- a/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/action/RunCallbackAction.kt
+++ b/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/action/RunCallbackAction.kt
@@ -36,7 +36,7 @@
                 error("Provided class must implement ActionCallback.")
             }
 
-            val actionCallback = workClass.newInstance() as ActionCallback
+            val actionCallback = workClass.getDeclaredConstructor().newInstance() as ActionCallback
             actionCallback.onAction(context, glanceId)
         }
     }
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 6bf32f6..b854d30 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -73,6 +73,16 @@
   @androidx.glance.GlanceComposable public final class GlanceNodeKt {
   }
 
+  public final class GlanceTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public androidx.glance.color.ColorProviders getColors();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public final androidx.glance.color.ColorProviders colors;
+    field public static final androidx.glance.GlanceTheme INSTANCE;
+  }
+
+  public final class GlanceThemeKt {
+    method @androidx.compose.runtime.Composable public static void GlanceTheme(optional androidx.glance.color.ColorProviders colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   public final class ImageKt {
     method @androidx.compose.runtime.Composable public static void Image(androidx.glance.ImageProvider provider, String? contentDescription, optional androidx.glance.GlanceModifier modifier, optional int contentScale);
     method public static androidx.glance.ImageProvider ImageProvider(@DrawableRes int resId);
@@ -712,20 +722,27 @@
     property public final int Underline;
   }
 
+  public final class TextDefaults {
+    method public androidx.glance.text.TextStyle defaultTextStyle();
+    method public androidx.glance.unit.ColorProvider getDefaultTextColor();
+    property public final androidx.glance.unit.ColorProvider defaultTextColor;
+    field public static final androidx.glance.text.TextDefaults INSTANCE;
+  }
+
   public final class TextKt {
-    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle? style, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle style, optional int maxLines);
   }
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
-    ctor public TextStyle(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
-    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
-    method public androidx.glance.unit.ColorProvider? getColor();
+    ctor public TextStyle(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.unit.ColorProvider getColor();
     method public androidx.compose.ui.unit.TextUnit? getFontSize();
     method public androidx.glance.text.FontStyle? getFontStyle();
     method public androidx.glance.text.FontWeight? getFontWeight();
     method public androidx.glance.text.TextAlign? getTextAlign();
     method public androidx.glance.text.TextDecoration? getTextDecoration();
-    property public final androidx.glance.unit.ColorProvider? color;
+    property public final androidx.glance.unit.ColorProvider color;
     property public final androidx.compose.ui.unit.TextUnit? fontSize;
     property public final androidx.glance.text.FontStyle? fontStyle;
     property public final androidx.glance.text.FontWeight? fontWeight;
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index 6bf32f6..b854d30 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -73,6 +73,16 @@
   @androidx.glance.GlanceComposable public final class GlanceNodeKt {
   }
 
+  public final class GlanceTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public androidx.glance.color.ColorProviders getColors();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public final androidx.glance.color.ColorProviders colors;
+    field public static final androidx.glance.GlanceTheme INSTANCE;
+  }
+
+  public final class GlanceThemeKt {
+    method @androidx.compose.runtime.Composable public static void GlanceTheme(optional androidx.glance.color.ColorProviders colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   public final class ImageKt {
     method @androidx.compose.runtime.Composable public static void Image(androidx.glance.ImageProvider provider, String? contentDescription, optional androidx.glance.GlanceModifier modifier, optional int contentScale);
     method public static androidx.glance.ImageProvider ImageProvider(@DrawableRes int resId);
@@ -712,20 +722,27 @@
     property public final int Underline;
   }
 
+  public final class TextDefaults {
+    method public androidx.glance.text.TextStyle defaultTextStyle();
+    method public androidx.glance.unit.ColorProvider getDefaultTextColor();
+    property public final androidx.glance.unit.ColorProvider defaultTextColor;
+    field public static final androidx.glance.text.TextDefaults INSTANCE;
+  }
+
   public final class TextKt {
-    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle? style, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle style, optional int maxLines);
   }
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
-    ctor public TextStyle(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
-    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
-    method public androidx.glance.unit.ColorProvider? getColor();
+    ctor public TextStyle(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.unit.ColorProvider getColor();
     method public androidx.compose.ui.unit.TextUnit? getFontSize();
     method public androidx.glance.text.FontStyle? getFontStyle();
     method public androidx.glance.text.FontWeight? getFontWeight();
     method public androidx.glance.text.TextAlign? getTextAlign();
     method public androidx.glance.text.TextDecoration? getTextDecoration();
-    property public final androidx.glance.unit.ColorProvider? color;
+    property public final androidx.glance.unit.ColorProvider color;
     property public final androidx.compose.ui.unit.TextUnit? fontSize;
     property public final androidx.glance.text.FontStyle? fontStyle;
     property public final androidx.glance.text.FontWeight? fontWeight;
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 6bf32f6..b854d30 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -73,6 +73,16 @@
   @androidx.glance.GlanceComposable public final class GlanceNodeKt {
   }
 
+  public final class GlanceTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public androidx.glance.color.ColorProviders getColors();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public final androidx.glance.color.ColorProviders colors;
+    field public static final androidx.glance.GlanceTheme INSTANCE;
+  }
+
+  public final class GlanceThemeKt {
+    method @androidx.compose.runtime.Composable public static void GlanceTheme(optional androidx.glance.color.ColorProviders colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   public final class ImageKt {
     method @androidx.compose.runtime.Composable public static void Image(androidx.glance.ImageProvider provider, String? contentDescription, optional androidx.glance.GlanceModifier modifier, optional int contentScale);
     method public static androidx.glance.ImageProvider ImageProvider(@DrawableRes int resId);
@@ -712,20 +722,27 @@
     property public final int Underline;
   }
 
+  public final class TextDefaults {
+    method public androidx.glance.text.TextStyle defaultTextStyle();
+    method public androidx.glance.unit.ColorProvider getDefaultTextColor();
+    property public final androidx.glance.unit.ColorProvider defaultTextColor;
+    field public static final androidx.glance.text.TextDefaults INSTANCE;
+  }
+
   public final class TextKt {
-    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle? style, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle style, optional int maxLines);
   }
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
-    ctor public TextStyle(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
-    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider? color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
-    method public androidx.glance.unit.ColorProvider? getColor();
+    ctor public TextStyle(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration);
+    method public androidx.glance.unit.ColorProvider getColor();
     method public androidx.compose.ui.unit.TextUnit? getFontSize();
     method public androidx.glance.text.FontStyle? getFontStyle();
     method public androidx.glance.text.FontWeight? getFontWeight();
     method public androidx.glance.text.TextAlign? getTextAlign();
     method public androidx.glance.text.TextDecoration? getTextDecoration();
-    property public final androidx.glance.unit.ColorProvider? color;
+    property public final androidx.glance.unit.ColorProvider color;
     property public final androidx.compose.ui.unit.TextUnit? fontSize;
     property public final androidx.glance.text.FontStyle? fontStyle;
     property public final androidx.glance.text.FontWeight? fontWeight;
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/Button.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/Button.kt
index 7e7b696..a41cba2 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/Button.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/Button.kt
@@ -43,7 +43,7 @@
     modifier: GlanceModifier = GlanceModifier,
     enabled: Boolean = true,
     style: TextStyle? = null,
-    colors: ButtonColors = DEFAULT_COLORS,
+    colors: ButtonColors = defaultButtonColors(),
     maxLines: Int = Int.MAX_VALUE,
 ) {
     var finalModifier = if (enabled) modifier.clickable(onClick) else modifier
@@ -70,7 +70,7 @@
     override var modifier: GlanceModifier = GlanceModifier
     var text: String = ""
     var style: TextStyle? = null
-    var colors: ButtonColors = DEFAULT_COLORS
+    var colors: ButtonColors? = null
     var enabled: Boolean = true
     var maxLines: Int = Int.MAX_VALUE
 
@@ -117,7 +117,8 @@
     }
 }
 
-internal val DEFAULT_COLORS = ButtonColors(
-    ColorProvider(R.color.glance_colorPrimary),
-    ColorProvider(R.color.glance_colorOnPrimary)
+@Composable
+internal fun defaultButtonColors() = ButtonColors(
+    GlanceTheme.colors.primary,
+    GlanceTheme.colors.onPrimary
 )
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt
index d2d086e..39a8849 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/CompositionLocals.kt
@@ -18,11 +18,14 @@
 
 import android.content.Context
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.State
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.unit.DpSize
 import androidx.datastore.preferences.core.Preferences
+import androidx.glance.color.ColorProviders
+import androidx.glance.color.DynamicThemeColorProviders
 import androidx.glance.state.GlanceStateDefinition
 
 /**
@@ -73,3 +76,11 @@
 @Composable
 inline fun <reified T> currentState(key: Preferences.Key<T>): T? =
     currentState<Preferences>()[key]
+
+/**
+ * The colors currently provided by [GlanceTheme]. If no overrides are provided, it will provide a
+ * standard set of Material3 colors, see [DynamicThemeColorProviders]. This should usually be
+ * accessed through [GlanceTheme.colors] rather than directly.
+ */
+internal val LocalColors: ProvidableCompositionLocal<ColorProviders> =
+    staticCompositionLocalOf { DynamicThemeColorProviders }
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceTheme.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceTheme.kt
new file mode 100644
index 0000000..9541979
--- /dev/null
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceTheme.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.glance
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.glance.color.ColorProviders
+
+/**
+ * Object that provides access to the current theme.
+ */
+object GlanceTheme {
+    val colors: ColorProviders
+        @GlanceComposable @Composable
+        @ReadOnlyComposable
+        get() = LocalColors.current
+}
+
+/**
+ * A top level theme for Glance code. Unlike a standard compose theme, this only provides
+ * color. Like a standard compose theme, it should be at the top level of a compose hierarchy with
+ * all themed UI provided as [content].
+ */
+@Composable
+fun GlanceTheme(
+    colors: ColorProviders = LocalColors.current,
+    content: @GlanceComposable @Composable () -> Unit
+) {
+    CompositionLocalProvider(
+        LocalColors provides colors,
+        content = content
+    )
+}
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/text/Text.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/text/Text.kt
index 387bd5b..6878e55 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/text/Text.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/text/Text.kt
@@ -19,9 +19,12 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
 import androidx.glance.Emittable
 import androidx.glance.GlanceModifier
 import androidx.glance.GlanceNode
+import androidx.glance.text.TextDefaults.defaultTextStyle
+import androidx.glance.unit.ColorProvider
 
 /**
  * Adds a text view to the glance view.
@@ -36,7 +39,7 @@
 fun Text(
     text: String,
     modifier: GlanceModifier = GlanceModifier,
-    style: TextStyle? = null,
+    style: TextStyle = defaultTextStyle(),
     maxLines: Int = Int.MAX_VALUE,
 ) {
     GlanceNode(
@@ -50,6 +53,11 @@
     )
 }
 
+object TextDefaults {
+    val defaultTextColor = ColorProvider(Color.Black)
+    fun defaultTextStyle(): TextStyle = TextStyle(color = defaultTextColor)
+}
+
 /** @suppress */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class EmittableText : Emittable {
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt
index be3f8b2..f8c01f9 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/text/TextStyle.kt
@@ -21,11 +21,11 @@
 import androidx.glance.unit.ColorProvider
 
 /**
- * Description of a text style for the [androidx.glance.layout.Text] composable.
+ * Description of a text style for the [androidx.glance.text.Text] composable.
  */
 @Immutable
 class TextStyle(
-    val color: ColorProvider? = null,
+    val color: ColorProvider = TextDefaults.defaultTextColor,
     val fontSize: TextUnit? = null,
     val fontWeight: FontWeight? = null,
     val fontStyle: FontStyle? = null,
@@ -33,7 +33,7 @@
     val textDecoration: TextDecoration? = null,
 ) {
     fun copy(
-        color: ColorProvider? = this.color,
+        color: ColorProvider = this.color,
         fontSize: TextUnit? = this.fontSize,
         fontWeight: FontWeight? = this.fontWeight,
         fontStyle: FontStyle? = this.fontStyle,
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
index a106652..8bc9546 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
@@ -70,6 +71,7 @@
         WorkManager.getInstance(context).cancelAllWork()
     }
 
+    @Ignore // b/254048913
     @Test
     fun startSession() = runTest {
         assertThat(sessionManager.isSessionRunning(context, key)).isFalse()
@@ -78,6 +80,7 @@
         assertThat(sessionManager.getSession(key)).isSameInstanceAs(session)
     }
 
+    @Ignore // b/254048913
     @Test
     fun closeSession() = runTest {
         sessionManager.startSession(context, session)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 71cbc7c..9eb5e55 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -82,6 +82,7 @@
 apacheAnt = { module = "org.apache.ant:ant", version = "1.10.11" }
 apacheCommonsCodec = { module = "commons-codec:commons-codec", version = "1.15" }
 apacheCommonIo = { module = "commons-io:commons-io", version = "2.4" }
+apacheCommonsMath = { module = "org.apache.commons:commons-math3", version = "3.6.1" }
 assertj = { module = "org.assertj:assertj-core", version = "3.23.1" }
 asm = { module = "org.ow2.asm:asm", version.ref = "asm"}
 asmCommons = { module = "org.ow2.asm:asm-commons", version.ref = "asm"}
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index 6334dd7..787b19f 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -4496,6 +4496,42 @@
 -----END PGP PUBLIC KEY BLOCK-----
 
 
+pub    64A16FAAEC16A4BE
+uid    Evan Ward <[email protected]>
+
+sub    1E8F1D57A4450BCB
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQENBFbqsT0BCADwERe1Rc9qNWwXOvwZHsjauVDy0TpqNVY8I3S+OYm4rX1dkjyh
++6bTEH1ys6bKevvR+PLhYzTGKboHnMT0RIINY/DQQSzHr/GRyCiiRlRvULbt9Fnz
+kJJDgH2BcbNSmWJlrCqYk+E3GAyQial+szkEZED+02wXHsbs0z3vozjQGHy0RVOj
+Gc5Shwk7Hr/F3vw9EQKa1nNffWmcGEx9B+WcC9ALPVd/fpQVXvlqfbi+kaIbqv2x
+NHQr7BL8j3SpN6vhfZM/3zeghlxQ5HYWER983XwkmvbNdMxt5HWsMKWZ0utt4ocK
+TnQP8NFGlPWEQhPvRRFNb9BI0wvGD0NUb1gjABEBAAG0H0V2YW4gV2FyZCA8ZXZh
+bndhcmRAYXBhY2hlLm9yZz6JATkEEwECACMFAlbqsT0CGwMHCwkIBwMCAQYVCAIJ
+CgsEFgIDAQIeAQIXgAAKCRBkoW+q7BakvnygB/41oiYgfDqkG5srQ4nC7jE0Pe5V
+MnuLVHqsfJBGPvt2tz5+Z1ciIFFwUi/xsafX5DhC+FVOOGdeEnkKnskPBOI7uMFh
+v/s90lbhNV62LfwcS9hptE4qn0JTg7mYiiL0Zue99mlkeP105+GlMmvH5q54X2Le
+hIDBVR8DehL8ZqZCvNEVK1ftpx45mvF/4yh0YK0oVuCAAzwF9+6OxeWTCUTRHTZC
+4CWjtXKUHMq4nTRSp0wGdqd5UV0VbMn0bKTkhgRNCJAKyFw6lJ0FZWwmuG28T0s+
+bKuRAJHTAZmSM7UmBnKo22t9whNcozcqxWhK1lcS4OWEArXpCKxAx4kXmbwnuQEN
+BFbqsT0BCADj5dHK9K27rmkFscDY6x53w4L/X6AYKmVu7Qol7VhR+1WtZXgxZaLM
+xDxj5RK4sNOIqh6R3vEMlAVS+iYbzahI/A0fcSciCoLCgjJKCR3STnTu2k0D/MO0
+la+wF/bGPa0UADGIJLRCjalkl5uv4c7zZbyLnRl8a9XSc81cp2WJTxafZJlJkFOU
+4cyewwFuH0pwMvc9Wmwhkh5IeBF6w8Asj77M5bzJINXYxtKMGYA506609HrvN0+r
+obfgx4Aqy4hGKsqXMsSZiuPDvbdtH3gIRV8NPdYRq+dQg/gv222Y3G1xprDVbl1A
+1CCHlaUqT2lIFPovjoB2O2SBeX9xKRJzABEBAAGJAR8EGAECAAkFAlbqsT0CGwwA
+CgkQZKFvquwWpL4yawf/WDI4VqLkR9RqaX3am/kS8481pZPWZUlCCL7jONB7X7eG
+Bit/FjmQWzfL5nWAEB5qhm2qqCgvgtPmVxCrQLECVmaGmDFmhGIFh8TQsYvQJPK6
+HZDxZj97lUKsG/ojOY4ZArvZnsXBU6C963QUZF+P5UL52n1pE/ByMV1R3enEfrYI
+X+wZslOx5uRFOR8dgUpG/ohh2vkFCaKD6KJQHm6C5lGBgUNqGMFxp1nknKJaNqYq
+jvippm6KcocWARfTHx6Xm3mBqxigmpsalUKAGpjcsxsIEY6jnnN/5i5y1XeokTY8
+6fqEt2OSFSkWiApqq6lxMRluTiq33bSquTxSomKfQQ==
+=PImz
+-----END PGP PUBLIC KEY BLOCK-----
+
+
 pub    6525FD70CC303655
 uid    Stephane Nicoll <[email protected]>
 
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index ca84657..c11dc85e 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -146,6 +146,7 @@
          <trusted-key id="3f36885c24df4b75" group="org.mozilla"/>
          <trusted-key id="3faad2cd5ecbb314" group="org.apache.commons"/>
          <trusted-key id="41321490758aad6f" group="org.codehaus.groovy"/>
+         <trusted-key id="41a1a08c62fca78b79d3081164a16faaec16a4be" group="org.apache.commons" name="commons-math3"/>
          <trusted-key id="41cd49b4ef5876f9e9f691dabac30622339994c4">
             <trusting group="com.google.testing.compile"/>
             <trusting group="com.google.truth"/>
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index b526a23..1943714 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -293,33 +293,32 @@
   }
 
   public final class CervicalMucusRecord implements androidx.health.connect.client.records.Record {
-    ctor public CervicalMucusRecord(java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional String? appearance, optional String? sensation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
-    method public String? getAppearance();
+    ctor public CervicalMucusRecord(java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional int appearance, optional int sensation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    method public int getAppearance();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public String? getSensation();
+    method public int getSensation();
     method public java.time.Instant getTime();
     method public java.time.ZoneOffset? getZoneOffset();
-    property public final String? appearance;
+    property public final int appearance;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public final String? sensation;
+    property public final int sensation;
     property public java.time.Instant time;
     property public java.time.ZoneOffset? zoneOffset;
+    field public static final int APPEARANCE_CREAMY = 3; // 0x3
+    field public static final int APPEARANCE_DRY = 1; // 0x1
+    field public static final int APPEARANCE_EGG_WHITE = 5; // 0x5
+    field public static final int APPEARANCE_STICKY = 2; // 0x2
+    field public static final int APPEARANCE_UNKNOWN = 0; // 0x0
+    field public static final int APPEARANCE_UNUSUAL = 6; // 0x6
+    field public static final int APPEARANCE_WATERY = 4; // 0x4
+    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Companion Companion;
+    field public static final int SENSATION_HEAVY = 3; // 0x3
+    field public static final int SENSATION_LIGHT = 1; // 0x1
+    field public static final int SENSATION_MEDIUM = 2; // 0x2
+    field public static final int SENSATION_UNKNOWN = 0; // 0x0
   }
 
-  public static final class CervicalMucusRecord.Appearance {
-    field public static final String CLEAR = "clear";
-    field public static final String CREAMY = "creamy";
-    field public static final String DRY = "dry";
-    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Appearance INSTANCE;
-    field public static final String STICKY = "sticky";
-    field public static final String WATERY = "watery";
-  }
-
-  public static final class CervicalMucusRecord.Sensation {
-    field public static final String HEAVY = "heavy";
-    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Sensation INSTANCE;
-    field public static final String LIGHT = "light";
-    field public static final String MEDIUM = "medium";
+  public static final class CervicalMucusRecord.Companion {
   }
 
   public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.Record {
diff --git a/health/connect/connect-client/api/public_plus_experimental_current.txt b/health/connect/connect-client/api/public_plus_experimental_current.txt
index b526a23..1943714 100644
--- a/health/connect/connect-client/api/public_plus_experimental_current.txt
+++ b/health/connect/connect-client/api/public_plus_experimental_current.txt
@@ -293,33 +293,32 @@
   }
 
   public final class CervicalMucusRecord implements androidx.health.connect.client.records.Record {
-    ctor public CervicalMucusRecord(java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional String? appearance, optional String? sensation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
-    method public String? getAppearance();
+    ctor public CervicalMucusRecord(java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional int appearance, optional int sensation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    method public int getAppearance();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public String? getSensation();
+    method public int getSensation();
     method public java.time.Instant getTime();
     method public java.time.ZoneOffset? getZoneOffset();
-    property public final String? appearance;
+    property public final int appearance;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public final String? sensation;
+    property public final int sensation;
     property public java.time.Instant time;
     property public java.time.ZoneOffset? zoneOffset;
+    field public static final int APPEARANCE_CREAMY = 3; // 0x3
+    field public static final int APPEARANCE_DRY = 1; // 0x1
+    field public static final int APPEARANCE_EGG_WHITE = 5; // 0x5
+    field public static final int APPEARANCE_STICKY = 2; // 0x2
+    field public static final int APPEARANCE_UNKNOWN = 0; // 0x0
+    field public static final int APPEARANCE_UNUSUAL = 6; // 0x6
+    field public static final int APPEARANCE_WATERY = 4; // 0x4
+    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Companion Companion;
+    field public static final int SENSATION_HEAVY = 3; // 0x3
+    field public static final int SENSATION_LIGHT = 1; // 0x1
+    field public static final int SENSATION_MEDIUM = 2; // 0x2
+    field public static final int SENSATION_UNKNOWN = 0; // 0x0
   }
 
-  public static final class CervicalMucusRecord.Appearance {
-    field public static final String CLEAR = "clear";
-    field public static final String CREAMY = "creamy";
-    field public static final String DRY = "dry";
-    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Appearance INSTANCE;
-    field public static final String STICKY = "sticky";
-    field public static final String WATERY = "watery";
-  }
-
-  public static final class CervicalMucusRecord.Sensation {
-    field public static final String HEAVY = "heavy";
-    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Sensation INSTANCE;
-    field public static final String LIGHT = "light";
-    field public static final String MEDIUM = "medium";
+  public static final class CervicalMucusRecord.Companion {
   }
 
   public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.Record {
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 4910c88..86bb039 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -293,33 +293,32 @@
   }
 
   public final class CervicalMucusRecord implements androidx.health.connect.client.records.InstantaneousRecord {
-    ctor public CervicalMucusRecord(java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional String? appearance, optional String? sensation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
-    method public String? getAppearance();
+    ctor public CervicalMucusRecord(java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional int appearance, optional int sensation, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    method public int getAppearance();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
-    method public String? getSensation();
+    method public int getSensation();
     method public java.time.Instant getTime();
     method public java.time.ZoneOffset? getZoneOffset();
-    property public final String? appearance;
+    property public final int appearance;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
-    property public final String? sensation;
+    property public final int sensation;
     property public java.time.Instant time;
     property public java.time.ZoneOffset? zoneOffset;
+    field public static final int APPEARANCE_CREAMY = 3; // 0x3
+    field public static final int APPEARANCE_DRY = 1; // 0x1
+    field public static final int APPEARANCE_EGG_WHITE = 5; // 0x5
+    field public static final int APPEARANCE_STICKY = 2; // 0x2
+    field public static final int APPEARANCE_UNKNOWN = 0; // 0x0
+    field public static final int APPEARANCE_UNUSUAL = 6; // 0x6
+    field public static final int APPEARANCE_WATERY = 4; // 0x4
+    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Companion Companion;
+    field public static final int SENSATION_HEAVY = 3; // 0x3
+    field public static final int SENSATION_LIGHT = 1; // 0x1
+    field public static final int SENSATION_MEDIUM = 2; // 0x2
+    field public static final int SENSATION_UNKNOWN = 0; // 0x0
   }
 
-  public static final class CervicalMucusRecord.Appearance {
-    field public static final String CLEAR = "clear";
-    field public static final String CREAMY = "creamy";
-    field public static final String DRY = "dry";
-    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Appearance INSTANCE;
-    field public static final String STICKY = "sticky";
-    field public static final String WATERY = "watery";
-  }
-
-  public static final class CervicalMucusRecord.Sensation {
-    field public static final String HEAVY = "heavy";
-    field public static final androidx.health.connect.client.records.CervicalMucusRecord.Sensation INSTANCE;
-    field public static final String LIGHT = "light";
-    field public static final String MEDIUM = "medium";
+  public static final class CervicalMucusRecord.Companion {
   }
 
   public final class CyclingPedalingCadenceRecord implements androidx.health.connect.client.records.SeriesRecord<androidx.health.connect.client.records.CyclingPedalingCadenceRecord.Sample> {
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index 5101275..4e62508 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -28,6 +28,8 @@
 import androidx.health.connect.client.records.BodyWaterMassRecord
 import androidx.health.connect.client.records.BoneMassRecord
 import androidx.health.connect.client.records.CervicalMucusRecord
+import androidx.health.connect.client.records.CervicalMucusRecord.Companion.APPEARANCE_STRING_TO_INT_MAP
+import androidx.health.connect.client.records.CervicalMucusRecord.Companion.SENSATION_STRING_TO_INT_MAP
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ElevationGainedRecord
@@ -155,8 +157,18 @@
                 )
             "CervicalMucus" ->
                 CervicalMucusRecord(
-                    appearance = getEnum("texture"),
-                    sensation = getEnum("amount"),
+                    appearance =
+                        mapEnum(
+                            "texture",
+                            APPEARANCE_STRING_TO_INT_MAP,
+                            CervicalMucusRecord.APPEARANCE_UNKNOWN
+                        ),
+                    sensation =
+                        mapEnum(
+                            "amount",
+                            SENSATION_STRING_TO_INT_MAP,
+                            CervicalMucusRecord.SENSATION_UNKNOWN
+                        ),
                     time = time,
                     zoneOffset = zoneOffset,
                     metadata = metadata
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
index 25f805d..9894ddb 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
@@ -65,6 +65,16 @@
     return valuesMap[key]?.enumVal
 }
 
+/** Maps a string enum field to public API integers. */
+internal fun DataPointOrBuilder.mapEnum(
+    key: String,
+    stringToIntMap: Map<String, Int>,
+    default: Int
+): Int {
+    val value = getEnum(key) ?: return default
+    return stringToIntMap.getOrDefault(value, default)
+}
+
 internal fun SeriesValueOrBuilder.getLong(key: String, defaultVal: Long = 0): Long =
     valuesMap[key]?.longVal ?: defaultVal
 
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
index e00d9d2..5f85f66 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
@@ -28,6 +28,8 @@
 import androidx.health.connect.client.records.BodyWaterMassRecord
 import androidx.health.connect.client.records.BoneMassRecord
 import androidx.health.connect.client.records.CervicalMucusRecord
+import androidx.health.connect.client.records.CervicalMucusRecord.Companion.APPEARANCE_INT_TO_STRING_MAP
+import androidx.health.connect.client.records.CervicalMucusRecord.Companion.SENSATION_INT_TO_STRING_MAP
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ElevationGainedRecord
@@ -136,8 +138,12 @@
             instantaneousProto()
                 .setDataType(protoDataType("CervicalMucus"))
                 .apply {
-                    appearance?.let { putValues("texture", enumVal(it)) }
-                    sensation?.let { putValues("amount", enumVal(it)) }
+                    enumValFromInt(appearance, APPEARANCE_INT_TO_STRING_MAP)?.let {
+                        putValues("texture", it)
+                    }
+                    enumValFromInt(sensation, SENSATION_INT_TO_STRING_MAP)?.let {
+                        putValues("amount", it)
+                    }
                 }
                 .build()
         is CyclingPedalingCadenceRecord ->
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ValueExt.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ValueExt.kt
index d32fdfb..b2b38cad 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ValueExt.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ValueExt.kt
@@ -36,3 +36,7 @@
 
 internal fun enumVal(value: String): DataProto.Value =
     DataProto.Value.newBuilder().setEnumVal(value).build()
+
+internal fun enumValFromInt(value: Int, intToStringMap: Map<Int, String>): DataProto.Value? {
+    return intToStringMap[value]?.let(::enumVal)
+}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/CervicalMucusRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/CervicalMucusRecord.kt
index ccd7386..c1cc1d0 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/CervicalMucusRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/CervicalMucusRecord.kt
@@ -13,10 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:RestrictTo(RestrictTo.Scope.LIBRARY)
+
 package androidx.health.connect.client.records
 
+import androidx.annotation.IntDef
 import androidx.annotation.RestrictTo
-import androidx.annotation.StringDef
 import androidx.health.connect.client.records.CervicalMucusRecord.Appearances
 import androidx.health.connect.client.records.CervicalMucusRecord.Sensations
 import androidx.health.connect.client.records.metadata.Metadata
@@ -31,46 +33,64 @@
 public class CervicalMucusRecord(
     override val time: Instant,
     override val zoneOffset: ZoneOffset?,
-    /**
-     * The consistency of the user's cervical mucus. Optional field. Allowed values: [Appearance].
-     *
-     * @see Appearance
-     */
-    @property:Appearances public val appearance: String? = null,
-    /**
-     * The feel of the user's cervical mucus. Optional field. Allowed values: [Sensation].
-     *
-     * @see Sensation
-     */
-    @property:Sensations public val sensation: String? = null,
+    /** The consistency of the user's cervical mucus. */
+    @property:Appearances public val appearance: Int = APPEARANCE_UNKNOWN,
+    /** The feel of the user's cervical mucus. */
+    @property:Sensations public val sensation: Int = SENSATION_UNKNOWN,
     override val metadata: Metadata = Metadata.EMPTY,
 ) : InstantaneousRecord {
 
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is CervicalMucusRecord) return false
+    companion object {
+        const val APPEARANCE_UNKNOWN = 0
+        const val APPEARANCE_DRY = 1
+        const val APPEARANCE_STICKY = 2
+        const val APPEARANCE_CREAMY = 3
+        const val APPEARANCE_WATERY = 4
+        /** A constant describing clear or egg white like looking cervical mucus. */
+        const val APPEARANCE_EGG_WHITE = 5
+        /** A constant describing an unusual (worth attention) kind of cervical mucus. */
+        const val APPEARANCE_UNUSUAL = 6
 
-        if (appearance != other.appearance) return false
-        if (sensation != other.sensation) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
+        const val SENSATION_UNKNOWN = 0
+        const val SENSATION_LIGHT = 1
+        const val SENSATION_MEDIUM = 2
+        const val SENSATION_HEAVY = 3
 
-        return true
-    }
+        /** Internal mappings useful for interoperability between integers and strings. */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmField
+        val APPEARANCE_STRING_TO_INT_MAP: Map<String, Int> =
+            mapOf(
+                Appearance.CLEAR to APPEARANCE_EGG_WHITE,
+                Appearance.CREAMY to APPEARANCE_CREAMY,
+                Appearance.DRY to APPEARANCE_DRY,
+                Appearance.STICKY to APPEARANCE_STICKY,
+                Appearance.WATERY to APPEARANCE_WATERY,
+                Appearance.UNUSUAL to APPEARANCE_UNUSUAL
+            )
 
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + appearance.hashCode()
-        result = 31 * result + sensation.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmField
+        val APPEARANCE_INT_TO_STRING_MAP =
+            APPEARANCE_STRING_TO_INT_MAP.entries.associate { it.value to it.key }
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmField
+        val SENSATION_STRING_TO_INT_MAP: Map<String, Int> =
+            mapOf(
+                Sensation.LIGHT to SENSATION_LIGHT,
+                Sensation.MEDIUM to SENSATION_MEDIUM,
+                Sensation.HEAVY to SENSATION_HEAVY
+            )
+
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @JvmField
+        val SENSATION_INT_TO_STRING_MAP =
+            SENSATION_STRING_TO_INT_MAP.entries.associate { it.value to it.key }
     }
 
     /** List of supported Cervical Mucus Sensation types on Health Platform. */
-    public object Sensation {
+    internal object Sensation {
         const val LIGHT = "light"
         const val MEDIUM = "medium"
         const val HEAVY = "heavy"
@@ -81,24 +101,18 @@
      * @suppress
      */
     @Retention(AnnotationRetention.SOURCE)
-    @StringDef(
-        value =
-            [
-                Sensation.LIGHT,
-                Sensation.MEDIUM,
-                Sensation.HEAVY,
-            ]
-    )
+    @IntDef(value = [SENSATION_UNKNOWN, SENSATION_LIGHT, SENSATION_MEDIUM, SENSATION_HEAVY])
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     annotation class Sensations
 
     /** The consistency or appearance of the user's cervical mucus. */
-    public object Appearance {
+    internal object Appearance {
         const val DRY = "dry"
         const val STICKY = "sticky"
         const val CREAMY = "creamy"
         const val WATERY = "watery"
         const val CLEAR = "clear"
+        const val UNUSUAL = "unusual"
     }
 
     /**
@@ -106,16 +120,42 @@
      * @suppress
      */
     @Retention(AnnotationRetention.SOURCE)
-    @StringDef(
+    @IntDef(
         value =
             [
-                Appearance.DRY,
-                Appearance.STICKY,
-                Appearance.CREAMY,
-                Appearance.WATERY,
-                Appearance.CLEAR,
+                APPEARANCE_UNKNOWN,
+                APPEARANCE_DRY,
+                APPEARANCE_STICKY,
+                APPEARANCE_CREAMY,
+                APPEARANCE_WATERY,
+                APPEARANCE_EGG_WHITE,
+                APPEARANCE_UNUSUAL
             ]
     )
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     annotation class Appearances
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as CervicalMucusRecord
+
+        if (time != other.time) return false
+        if (zoneOffset != other.zoneOffset) return false
+        if (appearance != other.appearance) return false
+        if (sensation != other.sensation) return false
+        if (metadata != other.metadata) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = time.hashCode()
+        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
+        result = 31 * result + appearance
+        result = 31 * result + sensation
+        result = 31 * result + metadata.hashCode()
+        return result
+    }
 }
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index be9cdbe..cdde08f 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -27,8 +27,6 @@
 import androidx.health.connect.client.records.BodyWaterMassRecord
 import androidx.health.connect.client.records.BoneMassRecord
 import androidx.health.connect.client.records.CervicalMucusRecord
-import androidx.health.connect.client.records.CervicalMucusRecord.Appearance
-import androidx.health.connect.client.records.CervicalMucusRecord.Sensation
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ElevationGainedRecord
@@ -254,8 +252,8 @@
     fun testCervicalMucus() {
         val data =
             CervicalMucusRecord(
-                appearance = Appearance.CLEAR,
-                sensation = Sensation.HEAVY,
+                appearance = CervicalMucusRecord.APPEARANCE_EGG_WHITE,
+                sensation = CervicalMucusRecord.SENSATION_HEAVY,
                 time = START_TIME,
                 zoneOffset = END_ZONE_OFFSET,
                 metadata = TEST_METADATA
diff --git a/hilt/hilt-navigation-compose/build.gradle b/hilt/hilt-navigation-compose/build.gradle
index 0fad8e6..83fb1c1 100644
--- a/hilt/hilt-navigation-compose/build.gradle
+++ b/hilt/hilt-navigation-compose/build.gradle
@@ -36,7 +36,7 @@
 dependencies {
 
     implementation(libs.kotlinStdlib)
-    api("androidx.hilt:hilt-navigation:1.0.0")
+    api projectOrArtifact(":hilt:hilt-navigation")
     api("androidx.compose.runtime:runtime:1.0.1")
     api("androidx.compose.ui:ui:1.0.1")
     api("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
diff --git a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
index 403a9d8..b2fb118 100644
--- a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
+++ b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.hilt.navigation.compose
 
+import android.content.Context
 import androidx.activity.ComponentActivity
 import androidx.compose.material.Button
 import androidx.compose.material.Text
@@ -34,6 +35,7 @@
 import com.google.common.truth.Truth.assertThat
 import dagger.hilt.android.AndroidEntryPoint
 import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
 import dagger.hilt.android.testing.HiltAndroidRule
 import dagger.hilt.android.testing.HiltAndroidTest
 import org.junit.Rule
@@ -171,7 +173,10 @@
     @HiltViewModel
     class SimpleViewModel @Inject constructor(
         val handle: SavedStateHandle,
-        val logger: MyLogger
+        val logger: MyLogger,
+        // TODO(kuanyingchou) Remove this after https://github.com/google/dagger/issues/3601 is
+        //  resolved.
+        @ApplicationContext val context: Context
     ) : ViewModel()
 
     class MyLogger @Inject constructor()
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleFragmentTestActivity.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleFragmentTestActivity.java
index 9b052a5..2f3d994 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleFragmentTestActivity.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleFragmentTestActivity.java
@@ -70,7 +70,8 @@
         if (savedInstanceState == null && findViewById(R.id.main_frame) != null) {
             try {
                 Fragment fragment = (Fragment) Class.forName(
-                        intent.getStringExtra(EXTRA_FRAGMENT_NAME)).newInstance();
+                        intent.getStringExtra(EXTRA_FRAGMENT_NAME))
+                        .getDeclaredConstructor().newInstance();
                 FragmentTransaction ft = getFragmentManager().beginTransaction();
                 ft.replace(R.id.main_frame, fragment);
                 ft.commit();
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleSupportFragmentTestActivity.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleSupportFragmentTestActivity.java
index 54440c6..e63e568 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleSupportFragmentTestActivity.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/SingleSupportFragmentTestActivity.java
@@ -67,7 +67,8 @@
         if (savedInstanceState == null && findViewById(R.id.main_frame) != null) {
             try {
                 Fragment fragment = (Fragment) Class.forName(
-                        intent.getStringExtra(EXTRA_FRAGMENT_NAME)).newInstance();
+                        intent.getStringExtra(EXTRA_FRAGMENT_NAME))
+                        .getDeclaredConstructor().newInstance();
                 FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                 ft.replace(R.id.main_frame, fragment);
                 ft.commit();
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/app/TestActivity.java b/leanback/leanback/src/androidTest/java/androidx/leanback/app/TestActivity.java
index cc83090..512db7e 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/app/TestActivity.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/app/TestActivity.java
@@ -128,7 +128,8 @@
         super.onCreate(savedInstanceState);
         mProviderName = getIntent().getStringExtra(EXTRA_PROVIDER);
         try {
-            mProvider = (Provider) Class.forName(mProviderName).newInstance();
+            mProvider = (Provider) Class.forName(mProviderName)
+                    .getDeclaredConstructor().newInstance();
         } catch (Exception ex) {
             ex.printStackTrace();
         }
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java
index 03200f2..cfc1991 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridActivity.java
@@ -32,6 +32,7 @@
 import androidx.recyclerview.widget.ConcatAdapter;
 import androidx.recyclerview.widget.RecyclerView;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 
 public class GridActivity extends Activity {
@@ -167,15 +168,15 @@
         try {
             if (alignmentClass != null) {
                 mAlignmentProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
-                        Class.forName(alignmentClass).newInstance();
+                        Class.forName(alignmentClass).getDeclaredConstructor().newInstance();
             }
             if (alignmentViewTypeClass != null) {
                 mAlignmentViewTypeProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
-                        Class.forName(alignmentViewTypeClass).newInstance();
+                        Class.forName(alignmentViewTypeClass).getDeclaredConstructor().newInstance();
             }
             if (viewTypeClass != null) {
                 mViewTypeProvider = (GridWidgetTest.ViewTypeProvider)
-                        Class.forName(viewTypeClass).newInstance();
+                        Class.forName(viewTypeClass).getDeclaredConstructor().newInstance();
             }
         } catch (ClassNotFoundException ex) {
             throw new RuntimeException(ex);
@@ -183,6 +184,10 @@
             throw new RuntimeException(ex);
         } catch (IllegalAccessException ex) {
             throw new RuntimeException(ex);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(e);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
         }
 
         super.onCreate(savedInstanceState);
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
index 3eefd71..59b07b8 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
@@ -119,11 +119,8 @@
         try {
             vm[MyAndroidViewModel::class.java]
             fail("Creating an AndroidViewModel should fail when no Application is provided")
-        } catch (e: RuntimeException) {
-            assertThat(e).hasMessageThat().isEqualTo(
-                "Cannot create an instance of " +
-                    MyAndroidViewModel::class.java
-            )
+        } catch (e: NoSuchMethodException) {
+            // Exception message varies across platform versions, just make sure it's thrown.
         }
         assertThat(vm[MyViewModel::class.java].handle).isNotNull()
     }
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
index fb5616b..ae7b4ab 100644
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/ViewModelProvider.kt
@@ -199,7 +199,7 @@
         @Suppress("DocumentExceptions")
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             return try {
-                modelClass.newInstance()
+                modelClass.getDeclaredConstructor().newInstance()
             } catch (e: InstantiationException) {
                 throw RuntimeException("Cannot create an instance of $modelClass", e)
             } catch (e: IllegalAccessException) {
diff --git a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderReifiedTest.kt b/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderReifiedTest.kt
index 0a702cf..2862a73 100644
--- a/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderReifiedTest.kt
+++ b/lifecycle/lifecycle-viewmodel/src/test/java/androidx/lifecycle/ViewModelProviderReifiedTest.kt
@@ -28,7 +28,8 @@
     @Test
     fun providerReifiedGet() {
         val factory = object : ViewModelProvider.Factory {
-            override fun <T : ViewModel> create(modelClass: Class<T>) = modelClass.newInstance()
+            override fun <T : ViewModel> create(modelClass: Class<T>) = modelClass
+                .getDeclaredConstructor().newInstance()
         }
         val provider = ViewModelProvider(ViewModelStore(), factory)
 
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index bfd8234..a39575d 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,7 +25,7 @@
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=9163047
+androidx.playground.snapshotBuildId=9189666
 androidx.playground.metalavaBuildId=8993580
 androidx.playground.dokkaBuildId=7472101
 androidx.studio.type=playground
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
index 38f9a0c..0b3528e 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
@@ -18,6 +18,7 @@
 
 import androidx.privacysandbox.tools.core.generator.build
 import androidx.privacysandbox.tools.core.generator.poetSpec
+import androidx.privacysandbox.tools.core.generator.stubDelegateNameSpec
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.model.getOnlyService
@@ -69,8 +70,9 @@
                 "val sdk = ${getCreateServiceFunctionName(api.getOnlyService())}(context!!)"
             )
             addStatement(
-                "return ${SANDBOXED_SDK_CLASS.simpleName}" +
-                    "(${api.getOnlyService().stubDelegateName()}(sdk))"
+                "return %T(%T(sdk))",
+                SANDBOXED_SDK_CLASS,
+                api.getOnlyService().stubDelegateNameSpec()
             )
         }
     }
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
index 4195f09..55185ab 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
@@ -19,8 +19,9 @@
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.generator.AidlCompiler
 import androidx.privacysandbox.tools.core.generator.AidlGenerator
+import androidx.privacysandbox.tools.core.generator.ClientProxyTypeGenerator
+import androidx.privacysandbox.tools.core.generator.StubDelegatesGenerator
 import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
-import androidx.privacysandbox.tools.core.generator.converterNameSpec
 import com.google.devtools.ksp.processing.CodeGenerator
 import com.google.devtools.ksp.processing.Dependencies
 import com.squareup.kotlinpoet.FileSpec
@@ -35,17 +36,14 @@
     fun generate() {
         generateAidlSources()
         generateValueConverters()
-        AbstractSdkProviderGenerator(api).generate()?.also(::write)
-        StubDelegatesGenerator(api).generate().forEach(::write)
+        generateCallbackProxies()
+        generateAbstractSdkProvider()
+        generateStubDelegates()
     }
 
     private fun generateValueConverters() {
         api.values.forEach { value ->
-            val file = ValueConverterFileGenerator(api, value).generate()
-            codeGenerator.createNewFile(
-                Dependencies(false), value.converterNameSpec().packageName,
-                value.converterNameSpec().simpleName,
-            ).write(file)
+            write(ValueConverterFileGenerator(api, value).generate())
         }
     }
 
@@ -69,6 +67,24 @@
         }
     }
 
+    private fun generateCallbackProxies() {
+        for (callback in api.callbacks) {
+            val classSpec = ClientProxyTypeGenerator(api, callback).generate()
+            write(
+                FileSpec.builder(callback.type.packageName, classSpec.name!!).addType(classSpec)
+                    .build()
+            )
+        }
+    }
+
+    private fun generateAbstractSdkProvider() {
+        AbstractSdkProviderGenerator(api).generate()?.also(::write)
+    }
+
+    private fun generateStubDelegates() {
+        StubDelegatesGenerator(api).generate().forEach(::write)
+    }
+
     private fun write(spec: FileSpec) {
         codeGenerator.createNewFile(Dependencies(false), spec.packageName, spec.name)
             .write(spec)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
index 8b88411..77da6f1 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
@@ -58,6 +58,8 @@
                 "com/mysdk/ParcelableRequest.java",
                 "com/mysdk/ParcelableResponse.java",
                 "com/mysdk/IResponseTransactionCallback.java",
+                "com/mysdk/MyCallbackClientProxy.kt",
+                "com/mysdk/IMyCallback.java",
             )
         }.also {
             it.generatesSourcesWithContents(expectedOutput)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/input/com/mysdk/MySdk.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/input/com/mysdk/MySdk.kt
index 8a358cf..b0cc37c 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/input/com/mysdk/MySdk.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/input/com/mysdk/MySdk.kt
@@ -1,5 +1,6 @@
 package com.mysdk
 
+import androidx.privacysandbox.tools.PrivacySandboxCallback
 import androidx.privacysandbox.tools.PrivacySandboxService
 import androidx.privacysandbox.tools.PrivacySandboxValue
 
@@ -11,6 +12,8 @@
 
     suspend fun logRequest(request: Request)
 
+    fun setListener(listener: MyCallback)
+
     fun doMoreStuff()
 }
 
@@ -18,4 +21,11 @@
 data class Request(val query: String)
 
 @PrivacySandboxValue
-data class Response(val response: String)
\ No newline at end of file
+data class Response(val response: String)
+
+@PrivacySandboxCallback
+interface MyCallback {
+    fun onComplete(response: Response)
+
+    fun onClick(x: Int, y: Int)
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MyCallbackClientProxy.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MyCallbackClientProxy.kt
new file mode 100644
index 0000000..351984d
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MyCallbackClientProxy.kt
@@ -0,0 +1,17 @@
+package com.mysdk
+
+import com.mysdk.ResponseConverter.toParcelable
+import kotlin.Int
+import kotlin.Unit
+
+internal class MyCallbackClientProxy(
+  private val remote: IMyCallback,
+) : MyCallback {
+  public override fun onComplete(response: Response): Unit {
+    remote.onComplete(toParcelable(response))
+  }
+
+  public override fun onClick(x: Int, y: Int): Unit {
+    remote.onClick(x, y)
+  }
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MySdkStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MySdkStubDelegate.kt
index 84a9d3b..2626f44 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MySdkStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/testinterface/output/com/mysdk/MySdkStubDelegate.kt
@@ -60,6 +60,10 @@
     transactionCallback.onCancellable(cancellationSignal)
   }
 
+  public override fun setListener(listener: IMyCallback): Unit {
+    delegate.setListener(MyCallbackClientProxy(listener))
+  }
+
   public override fun doMoreStuff(): Unit {
     delegate.doMoreStuff()
   }
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceInterfaceFileGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt
similarity index 81%
rename from privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceInterfaceFileGenerator.kt
rename to privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt
index c0a5e9e..9c431f0 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceInterfaceFileGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/InterfaceFileGenerator.kt
@@ -26,15 +26,16 @@
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.TypeSpec
 
-internal class ServiceInterfaceFileGenerator(private val service: AnnotatedInterface) {
+internal class InterfaceFileGenerator(private val annotatedInterface: AnnotatedInterface) {
 
     fun generate(): FileSpec {
         val annotatedInterface =
-            TypeSpec.interfaceBuilder(service.type.poetSpec()).build {
-                addFunctions(service.methods.map(::generateInterfaceMethod))
+            TypeSpec.interfaceBuilder(annotatedInterface.type.poetSpec()).build {
+                addFunctions(annotatedInterface.methods.map(::generateInterfaceMethod))
             }
 
-        return FileSpec.get(service.type.packageName, annotatedInterface).toBuilder().build {
+        return FileSpec.get(this.annotatedInterface.type.packageName, annotatedInterface)
+            .toBuilder().build {
             addCommonSettings()
         }
     }
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
index 0affdc17..734b235 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
@@ -20,6 +20,7 @@
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.generator.AidlCompiler
 import androidx.privacysandbox.tools.core.generator.AidlGenerator
+import androidx.privacysandbox.tools.core.generator.StubDelegatesGenerator
 import androidx.privacysandbox.tools.core.generator.ValueConverterFileGenerator
 import androidx.privacysandbox.tools.core.generator.ValueFileGenerator
 import java.io.File
@@ -59,9 +60,13 @@
             ValueConverterFileGenerator(sdkApi, it).generate().writeTo(output)
         }
         sdkApi.services.forEach {
-            ServiceInterfaceFileGenerator(it).generate().writeTo(output)
+            InterfaceFileGenerator(it).generate().writeTo(output)
             ServiceFactoryFileGenerator(sdkApi, it).generate().writeTo(output)
         }
+        sdkApi.callbacks.forEach {
+            InterfaceFileGenerator(it).generate().writeTo(output)
+            StubDelegatesGenerator(sdkApi).generateInterfaceStubDelegate(it).writeTo(output)
+        }
     }
 
     private fun generateBinders(sdkApi: ParsedApi, aidlCompiler: AidlCompiler, output: File) {
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
index b927183..443875e 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
@@ -47,7 +47,7 @@
 
             addFunction(generateFactoryFunction())
 
-            addType(proxyTypeGenerator.generate())
+            addType(proxyTypeGenerator.generate(KModifier.PRIVATE))
         }
 
     private fun generateFactoryFunction() =
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorTest.kt
new file mode 100644
index 0000000..bc17d9d
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/CallbacksApiGeneratorTest.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.privacysandbox.tools.apigenerator
+
+import java.io.File
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class CallbacksApiGeneratorTest : BaseApiGeneratorTest() {
+    override val inputDirectory = File("src/test/test-data/callbacks/input")
+    override val outputDirectory = File("src/test/test-data/callbacks/output")
+    override val relativePathsToExpectedAidlClasses = listOf(
+        "com/sdkwithcallbacks/ParcelableResponse.java",
+        "com/sdkwithcallbacks/ISdkCallback.java",
+        "com/sdkwithcallbacks/ICancellationSignal.java",
+        "com/sdkwithcallbacks/IUnitTransactionCallback.java",
+        "com/sdkwithcallbacks/ISdkService.java",
+    )
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
new file mode 100644
index 0000000..2bc8ff1
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/input/com/sdkwithcallbacks/SdkService.kt
@@ -0,0 +1,22 @@
+package com.sdkwithcallbacks
+
+import androidx.privacysandbox.tools.PrivacySandboxService
+import androidx.privacysandbox.tools.PrivacySandboxCallback
+import androidx.privacysandbox.tools.PrivacySandboxValue
+
+@PrivacySandboxService
+interface SdkService {
+    suspend fun registerCallback(callback: SdkCallback)
+}
+
+@PrivacySandboxCallback
+interface SdkCallback {
+    fun onValueReceived(response: Response)
+
+    fun onPrimitivesReceived(x: Int, y: Int)
+
+    fun onEmptyEvent()
+}
+
+@PrivacySandboxValue
+data class Response(val response: String)
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt
new file mode 100644
index 0000000..569c215
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/Response.kt
@@ -0,0 +1,5 @@
+package com.sdkwithcallbacks
+
+public data class Response(
+    public val response: String,
+)
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt
new file mode 100644
index 0000000..e1d05a5
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/ResponseConverter.kt
@@ -0,0 +1,15 @@
+package com.sdkwithcallbacks
+
+public object ResponseConverter {
+    public fun fromParcelable(parcelable: ParcelableResponse): Response {
+        val annotatedValue = Response(
+                response = parcelable.response)
+        return annotatedValue
+    }
+
+    public fun toParcelable(annotatedValue: Response): ParcelableResponse {
+        val parcelable = ParcelableResponse()
+        parcelable.response = annotatedValue.response
+        return parcelable
+    }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
new file mode 100644
index 0000000..da06ec7
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallback.kt
@@ -0,0 +1,9 @@
+package com.sdkwithcallbacks
+
+public interface SdkCallback {
+    public fun onEmptyEvent(): Unit
+
+    public fun onPrimitivesReceived(x: Int, y: Int): Unit
+
+    public fun onValueReceived(response: Response): Unit
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
new file mode 100644
index 0000000..e4a39953
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkCallbackStubDelegate.kt
@@ -0,0 +1,21 @@
+package com.sdkwithcallbacks
+
+import com.sdkwithcallbacks.ResponseConverter.fromParcelable
+import kotlin.Int
+import kotlin.Unit
+
+public class SdkCallbackStubDelegate internal constructor(
+  private val `delegate`: SdkCallback,
+) : ISdkCallback.Stub() {
+  public override fun onEmptyEvent(): Unit {
+    delegate.onEmptyEvent()
+  }
+
+  public override fun onPrimitivesReceived(x: Int, y: Int): Unit {
+    delegate.onPrimitivesReceived(x, y)
+  }
+
+  public override fun onValueReceived(response: ParcelableResponse): Unit {
+    delegate.onValueReceived(fromParcelable(response))
+  }
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt
new file mode 100644
index 0000000..8363e3d
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkService.kt
@@ -0,0 +1,5 @@
+package com.sdkwithcallbacks
+
+public interface SdkService {
+    public suspend fun registerCallback(callback: SdkCallback): Unit
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt
new file mode 100644
index 0000000..66c836d
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/SdkServiceFactory.kt
@@ -0,0 +1,51 @@
+package com.sdkwithcallbacks
+
+import android.app.sdksandbox.LoadSdkException
+import android.app.sdksandbox.SandboxedSdk
+import android.app.sdksandbox.SdkSandboxManager
+import android.content.Context
+import android.os.Bundle
+import android.os.OutcomeReceiver
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+public suspend fun createSdkService(context: Context): SdkService = suspendCancellableCoroutine {
+    val sdkSandboxManager = context.getSystemService(SdkSandboxManager::class.java)
+    val outcomeReceiver = object: OutcomeReceiver<SandboxedSdk, LoadSdkException> {
+        override fun onResult(result: SandboxedSdk) {
+            it.resume(SdkServiceClientProxy(ISdkService.Stub.asInterface(result.getInterface())))
+        }
+        override fun onError(error: LoadSdkException) {
+            it.resumeWithException(error)
+        }
+    }
+    sdkSandboxManager.loadSdk("com.sdkwithcallbacks", Bundle.EMPTY, Runnable::run, outcomeReceiver)
+}
+
+private class SdkServiceClientProxy(
+    private val remote: ISdkService,
+) : SdkService {
+    public override suspend fun registerCallback(callback: SdkCallback): Unit =
+            suspendCancellableCoroutine {
+        var mCancellationSignal: ICancellationSignal? = null
+        val transactionCallback = object: IUnitTransactionCallback.Stub() {
+            override fun onCancellable(cancellationSignal: ICancellationSignal) {
+                if (it.isCancelled) {
+                    cancellationSignal.cancel()
+                }
+                mCancellationSignal = cancellationSignal
+            }
+            override fun onSuccess() {
+                it.resumeWith(Result.success(Unit))
+            }
+            override fun onFailure(errorCode: Int, errorMessage: String) {
+                it.resumeWithException(RuntimeException(errorMessage))
+            }
+        }
+        remote.registerCallback(SdkCallbackStubDelegate(callback), transactionCallback)
+        it.invokeOnCancellation {
+            mCancellationSignal?.cancel()
+        }
+    }
+}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt
new file mode 100644
index 0000000..ebf87d1
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverter.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.privacysandbox.tools.core.generator
+
+import androidx.privacysandbox.tools.core.model.Parameter
+import androidx.privacysandbox.tools.core.model.ParsedApi
+import androidx.privacysandbox.tools.core.model.Type
+import androidx.privacysandbox.tools.core.model.Types
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+
+/** Utility to generate [CodeBlock]s that convert values to/from their binder equivalent. */
+class BinderCodeConverter(private val api: ParsedApi) {
+
+    /**
+     * Generate a block that converts the given expression from the binder representation to its
+     * model equivalent.
+     *
+     * @param type the type of the resulting expression. Can reference a primitive on annotated
+     * interface/value.
+     * @param expression the expression to be converted.
+     * @return a [CodeBlock] containing the generated code to perform the conversion.
+     */
+    fun convertToModelCode(type: Type, expression: String): CodeBlock {
+        require(type != Types.unit) { "Cannot convert Unit." }
+        val value = api.valueMap[type]
+        if (value != null) {
+            return CodeBlock.of("%M(%L)", value.fromParcelableNameSpec(), expression)
+        }
+        val callback = api.callbackMap[type]
+        if (callback != null) {
+            return CodeBlock.of("%T(%L)", callback.clientProxyNameSpec(), expression)
+        }
+        return CodeBlock.of(expression)
+    }
+
+    /**
+     * Convert the given [Parameter] from the binder representation to its model equivalent.
+     *
+     * See [convertToModelCode].
+     */
+    fun convertToModelCode(parameter: Parameter): CodeBlock {
+        return convertToModelCode(parameter.type, parameter.name)
+    }
+
+    /**
+     * Generate a block that converts the given expression from the model representation to its
+     * binder equivalent.
+     *
+     * @param type the type of the given expression. Can reference a primitive on annotated
+     * interface/value.
+     * @param expression the expression to be converted.
+     * @return a [CodeBlock] containing the generated code to perform the conversion.
+     */
+    fun convertToBinderCode(type: Type, expression: String): CodeBlock {
+        require(type != Types.unit) { "Cannot convert to Unit." }
+        val value = api.valueMap[type]
+        if (value != null) {
+            return CodeBlock.of("%M(%L)", value.toParcelableNameSpec(), expression)
+        }
+        val callback = api.callbackMap[type]
+        if (callback != null) {
+            return CodeBlock.of("%T(%L)", callback.stubDelegateNameSpec(), expression)
+        }
+        return CodeBlock.of(expression)
+    }
+
+    /**
+     * Convert the given [Parameter] from the model representation to its binder equivalent.
+     *
+     * See [convertToBinderCode].
+     */
+    fun convertToBinderCode(parameter: Parameter): CodeBlock {
+        return convertToBinderCode(parameter.type, parameter.name)
+    }
+
+    /** Convert the given model type declaration to its binder equivalent. */
+    fun convertToBinderType(type: Type): ClassName {
+        val value = api.valueMap[type]
+        if (value != null) {
+            return value.parcelableNameSpec()
+        }
+        val callback = api.callbackMap[type]
+        if (callback != null) {
+            return callback.aidlType().innerType.poetSpec()
+        }
+        return type.poetSpec()
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
index dbd3473..a234f9c 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ClientProxyTypeGenerator.kt
@@ -32,30 +32,31 @@
     private val api: ParsedApi,
     private val service: AnnotatedInterface
 ) {
-    val className =
-        ClassName(service.type.packageName, "${service.type.simpleName}ClientProxy")
+    val className = service.clientProxyNameSpec()
     val remoteBinderClassName =
         ClassName(service.type.packageName, "I${service.type.simpleName}")
     private val cancellationSignalClassName =
         ClassName(service.type.packageName, "ICancellationSignal")
+    private val binderCodeConverter = BinderCodeConverter(api)
 
-    fun generate(): TypeSpec = TypeSpec.classBuilder(className).build {
-        addModifiers(KModifier.PRIVATE)
-        addSuperinterface(ClassName(service.type.packageName, service.type.simpleName))
-        primaryConstructor(
-            listOf(
-                PropertySpec.builder("remote", remoteBinderClassName)
-                    .addModifiers(KModifier.PRIVATE).build()
+    fun generate(visibilityModifier: KModifier = KModifier.INTERNAL): TypeSpec =
+        TypeSpec.classBuilder(className).build {
+            addModifiers(visibilityModifier)
+            addSuperinterface(ClassName(service.type.packageName, service.type.simpleName))
+            primaryConstructor(
+                listOf(
+                    PropertySpec.builder("remote", remoteBinderClassName)
+                        .addModifiers(KModifier.PRIVATE).build()
+                )
             )
-        )
-        addFunctions(service.methods.map { method ->
-            if (method.isSuspend) {
-                generateSuspendProxyMethodImplementation(method)
-            } else {
-                generateProxyMethodImplementation(method)
-            }
-        })
-    }
+            addFunctions(service.methods.map { method ->
+                if (method.isSuspend) {
+                    generateSuspendProxyMethodImplementation(method)
+                } else {
+                    generateProxyMethodImplementation(method)
+                }
+            })
+        }
 
     private fun generateProxyMethodImplementation(method: Method) =
         FunSpec.builder(method.name).build {
@@ -124,25 +125,15 @@
             }
         }
 
-        val value = api.valueMap[method.returnType]
-        if (value != null) {
-            return CodeBlock.builder().build {
-                addControlFlow(
-                    "override fun onSuccess(result: %T)",
-                    value.parcelableNameSpec()
-                ) {
-                    addStatement("it.resumeWith(Result.success(%M(result)))",
-                        value.fromParcelableNameSpec())
-                }
-            }
-        }
-
         return CodeBlock.builder().build {
             addControlFlow(
                 "override fun onSuccess(result: %T)",
-                method.returnType.poetSpec()
+                binderCodeConverter.convertToBinderType(method.returnType)
             ) {
-                addStatement("it.resumeWith(Result.success(result))")
+                addStatement(
+                    "it.resumeWith(Result.success(%L))",
+                    binderCodeConverter.convertToModelCode(method.returnType, "result")
+                )
             }
         }
     }
@@ -151,11 +142,8 @@
         method: Method,
         extraParameters: List<CodeBlock> = emptyList(),
     ) = CodeBlock.builder().build {
-        val parameters = method.parameters.map { parameter ->
-            api.valueMap[parameter.type]?.toParcelableNameSpec()?.let { converterFunctionName ->
-                CodeBlock.of("%M(${parameter.name})", converterFunctionName)
-            } ?: CodeBlock.of(parameter.name)
-        } + extraParameters
+        val parameters =
+            method.parameters.map { binderCodeConverter.convertToBinderCode(it) } + extraParameters
         addStatement {
             add("remote.${method.name}(")
             add(parameters.joinToCode())
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/KotlinPoetSpecs.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/KotlinPoetSpecs.kt
index 85cbd2f..17a226a 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/KotlinPoetSpecs.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/KotlinPoetSpecs.kt
@@ -16,6 +16,7 @@
 
 package androidx.privacysandbox.tools.core.generator
 
+import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.AnnotatedValue
 import androidx.privacysandbox.tools.core.model.Parameter
 import androidx.privacysandbox.tools.core.model.Type
@@ -46,6 +47,10 @@
     MemberName(converterNameSpec(), "fromParcelable")
 fun AnnotatedValue.parcelableNameSpec() =
     ClassName(type.packageName, "Parcelable${type.simpleName}")
+fun AnnotatedInterface.clientProxyNameSpec() =
+    ClassName(type.packageName, "${type.simpleName}ClientProxy")
+fun AnnotatedInterface.stubDelegateNameSpec() =
+    ClassName(type.packageName, "${type.simpleName}StubDelegate")
 
 /**
  * Defines the primary constructor of this type with the given list of properties.
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/StubDelegatesGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt
similarity index 71%
rename from privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/StubDelegatesGenerator.kt
rename to privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt
index c4611bc..333e0732 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/StubDelegatesGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/StubDelegatesGenerator.kt
@@ -14,21 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.privacysandbox.tools.apicompiler.generator
+package androidx.privacysandbox.tools.core.generator
 
-import androidx.privacysandbox.tools.core.generator.AidlGenerator
-import androidx.privacysandbox.tools.core.generator.SpecNames
-import androidx.privacysandbox.tools.core.generator.addCode
-import androidx.privacysandbox.tools.core.generator.addControlFlow
-import androidx.privacysandbox.tools.core.generator.addStatement
-import androidx.privacysandbox.tools.core.generator.aidlName
-import androidx.privacysandbox.tools.core.generator.build
-import androidx.privacysandbox.tools.core.generator.toParcelableNameSpec
-import androidx.privacysandbox.tools.core.generator.fromParcelableNameSpec
-import androidx.privacysandbox.tools.core.generator.parcelableNameSpec
-import androidx.privacysandbox.tools.core.generator.poetSpec
-import androidx.privacysandbox.tools.core.generator.primaryConstructor
-import androidx.privacysandbox.tools.core.generator.transactionCallbackName
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.Method
 import androidx.privacysandbox.tools.core.model.ParsedApi
@@ -47,6 +34,8 @@
 import com.squareup.kotlinpoet.joinToCode
 
 class StubDelegatesGenerator(private val api: ParsedApi) {
+    private val binderCodeConverter = BinderCodeConverter(api)
+
     companion object {
         private val ATOMIC_BOOLEAN_CLASS = ClassName("java.util.concurrent.atomic", "AtomicBoolean")
     }
@@ -55,12 +44,12 @@
         if (api.services.isEmpty()) {
             return emptyList()
         }
-        return api.services.map(::generateServiceStubDelegate) +
+        return api.services.map(::generateInterfaceStubDelegate) +
             generateTransportCancellationCallback()
     }
 
-    private fun generateServiceStubDelegate(service: AnnotatedInterface): FileSpec {
-        val className = service.stubDelegateName()
+    fun generateInterfaceStubDelegate(service: AnnotatedInterface): FileSpec {
+        val className = service.stubDelegateNameSpec().simpleName
         val aidlBaseClassName = ClassName(service.type.packageName, service.aidlName(), "Stub")
 
         val classSpec = TypeSpec.classBuilder(className).build {
@@ -104,18 +93,13 @@
                             add("val result = ")
                             add(getDelegateCallBlock(method))
                         }
-                        val value = api.valueMap[method.returnType]
-                        when {
-                            value != null -> {
-                                addStatement(
-                                    "transactionCallback.onSuccess(%M(result))",
-                                    value.toParcelableNameSpec()
-                                )
-                            }
-                            method.returnType == Types.unit -> {
-                                addStatement("transactionCallback.onSuccess()")
-                            }
-                            else -> addStatement("transactionCallback.onSuccess(result)")
+                        if (method.returnType == Types.unit) {
+                            addStatement("transactionCallback.onSuccess()")
+                        } else {
+                            addStatement(
+                                "transactionCallback.onSuccess(%L)",
+                                binderCodeConverter.convertToBinderCode(method.returnType, "result")
+                            )
                         }
                     }
                     addControlFlow("catch (t: Throwable)") {
@@ -138,9 +122,7 @@
 
     private fun getParameters(method: Method) = buildList {
         addAll(method.parameters.map { parameter ->
-            api.valueMap[parameter.type]?.let { value ->
-                ParameterSpec(parameter.name, value.parcelableNameSpec())
-            } ?: ParameterSpec(parameter.name, parameter.type.poetSpec())
+            ParameterSpec(parameter.name, binderCodeConverter.convertToBinderType(parameter.type))
         })
         if (method.isSuspend) add(
             ParameterSpec(
@@ -153,16 +135,8 @@
     }
 
     private fun getDelegateCallBlock(method: Method) = CodeBlock.builder().build {
-        val parameters = method.parameters.map {
-            val value = api.valueMap[it.type]
-            if (value != null) {
-                CodeBlock.of("%M(${it.name})", value.fromParcelableNameSpec())
-            } else {
-                CodeBlock.of(it.name)
-            }
-        }
         add("delegate.${method.name}(")
-        add(parameters.joinToCode())
+        add(method.parameters.map { binderCodeConverter.convertToModelCode(it) }.joinToCode())
         add(")")
     }
 
@@ -201,5 +175,3 @@
         return FileSpec.builder(packageName, className).addType(classSpec).build()
     }
 }
-
-internal fun AnnotatedInterface.stubDelegateName() = "${type.simpleName}StubDelegate"
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverterTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverterTest.kt
new file mode 100644
index 0000000..5c0a149
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/BinderCodeConverterTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.privacysandbox.tools.core.generator
+
+import androidx.privacysandbox.tools.core.model.AnnotatedInterface
+import androidx.privacysandbox.tools.core.model.AnnotatedValue
+import androidx.privacysandbox.tools.core.model.ParsedApi
+import androidx.privacysandbox.tools.core.model.Type
+import androidx.privacysandbox.tools.core.model.Types
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class BinderCodeConverterTest {
+    private val converter = BinderCodeConverter(
+        ParsedApi(
+            services = setOf(
+                AnnotatedInterface(
+                    type = Type(packageName = "com.mysdk", simpleName = "MySdk"),
+                )
+            ),
+            values = setOf(
+                AnnotatedValue(
+                    type = Type(packageName = "com.mysdk", simpleName = "Value"),
+                    properties = listOf()
+                )
+            ),
+            callbacks = setOf(
+                AnnotatedInterface(
+                    type = Type(packageName = "com.mysdk", simpleName = "Callback"),
+                )
+            ),
+        )
+    )
+
+    @Test
+    fun convertToModelCode_primitive() {
+        assertThat(
+            converter.convertToModelCode(
+                Types.int, expression = "5"
+            ).toString()
+        ).isEqualTo("5")
+    }
+
+    @Test
+    fun convertToModelCode_value() {
+        assertThat(
+            converter.convertToModelCode(
+                Type(packageName = "com.mysdk", simpleName = "Value"), expression = "value"
+            ).toString()
+        ).isEqualTo("com.mysdk.ValueConverter.fromParcelable(value)")
+    }
+
+    @Test
+    fun convertToModelCode_callback() {
+        assertThat(
+            converter.convertToModelCode(
+                Type(packageName = "com.mysdk", simpleName = "Callback"), expression = "callback"
+            ).toString()
+        ).isEqualTo("com.mysdk.CallbackClientProxy(callback)")
+    }
+
+    @Test
+    fun convertToBinderCode_primitive() {
+        assertThat(
+            converter.convertToBinderCode(
+                Types.int, expression = "5"
+            ).toString()
+        ).isEqualTo("5")
+    }
+
+    @Test
+    fun convertToBinderCode_value() {
+        assertThat(
+            converter.convertToBinderCode(
+                Type(packageName = "com.mysdk", simpleName = "Value"), expression = "value"
+            ).toString()
+        ).isEqualTo("com.mysdk.ValueConverter.toParcelable(value)")
+    }
+
+    @Test
+    fun convertToBinderCode_callback() {
+        assertThat(
+            converter.convertToBinderCode(
+                Type(packageName = "com.mysdk", simpleName = "Callback"), expression = "callback"
+            ).toString()
+        ).isEqualTo("com.mysdk.CallbackStubDelegate(callback)")
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
index c874d53..d5bc9e5 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
@@ -44,7 +44,7 @@
     )
 
     companion object {
-        val TEST_DB = "migration-test"
+        const val TEST_DB = "migration-test"
     }
 
     abstract class EmptyDb : RoomDatabase()
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
index 9df26de..fb06e90 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
@@ -149,7 +149,7 @@
                 true,
                 MIGRATION_1_0
         );
-        DatabaseConfiguration config = helper.getDbConfigurationAfterMigrations();
+        DatabaseConfiguration config = helper.databaseConfiguration;
         assertThat(config).isNotNull();
         assertThat(config.migrationContainer.findMigrationPath(1, 2)).isNotNull();
         assertThat(config.migrationContainer.findMigrationPath(1, 2)).isNotEmpty();
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
index b83df2c..e298302 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/MigrationTest.java
@@ -70,11 +70,14 @@
     public MigrationTestHelper helper;
 
     public MigrationTest() {
-        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
-                MigrationDb.class.getCanonicalName());
+        helper = new MigrationTestHelper(
+                InstrumentationRegistry.getInstrumentation(),
+                MigrationDb.class.getCanonicalName()
+        );
     }
 
     @Test
+    @SuppressWarnings("deprecation") // This test is for verifying the old deprecated constructor
     public void giveBadResource() throws IOException {
         MigrationTestHelper helper = new MigrationTestHelper(
                 InstrumentationRegistry.getInstrumentation(),
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
index a862b1b..affa165 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeKotlinPoetExt.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing.ksp
 
+import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
 import com.google.devtools.ksp.KspExperimental
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.symbol.KSDeclaration
@@ -27,6 +28,7 @@
 import com.google.devtools.ksp.symbol.KSTypeReference
 import com.google.devtools.ksp.symbol.Variance
 import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.javapoet.KClassName
 import com.squareup.kotlinpoet.javapoet.KTypeName
@@ -155,10 +157,59 @@
 }
 
 /**
+ * See [KTypeVariableNameFactory.newInstance]
+ */
+private val typeVarNameCompanionInstance by lazy {
+    try {
+        KTypeVariableName::class.java.getDeclaredField("Companion")
+            .apply { trySetAccessible() }
+            .get(null)
+    } catch (ex: NoSuchFieldException) {
+        throw IllegalStateException(
+            """
+            Room couldn't find the field it is looking for in KotlinPoet.
+            Please file a bug at $ISSUE_TRACKER_LINK.
+            """.trimIndent(),
+            ex
+        )
+    }
+}
+
+/**
+ * See [KTypeVariableNameFactory.newInstance] and
+ * https://github.com/square/kotlinpoet/blob/1.12.0/kotlinpoet/src/main/java/com/squareup/kotlinpoet/TypeVariableName.kt#L70-L74
+ */
+private val typeVarNameFactoryMethod by lazy {
+    try {
+        typeVarNameCompanionInstance::class.java.methods.first {
+            it.name.startsWith("of") &&
+                it.parameterCount == 3 &&
+                it.parameters[0].type == String::class.java &&
+                it.parameters[1].type == List::class.java &&
+                it.parameters[2].type == KModifier::class.java
+        }.apply { trySetAccessible() }
+    } catch (ex: NoSuchElementException) {
+        throw IllegalStateException(
+            """
+            Room couldn't find the method it is looking for in KotlinPoet.
+            Please file a bug at $ISSUE_TRACKER_LINK.
+            """.trimIndent(),
+        )
+    }
+}
+
+/**
  * Creates a TypeVariableName where we can change the bounds after constructor.
  * This is used to workaround a case for self referencing type declarations.
  */
 private fun createModifiableTypeVariableName(
     name: String,
     bounds: List<KTypeName>
-): KTypeVariableName = KTypeVariableNameFactory.newInstance(name, bounds)
+): KTypeVariableName =
+    try {
+        KTypeVariableNameFactory.newInstance(name, bounds)
+    } catch (ex: NoSuchMethodError) {
+        typeVarNameFactoryMethod.invoke(
+            typeVarNameCompanionInstance, name, bounds, null
+        ) as KTypeVariableName
+    }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
index 7c6a68e..e78d084 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -312,6 +312,7 @@
         )
 
         val resultColumns = query.resultInfo?.columns
+
         if (resultColumns != null) {
             context.checker.check(
                 keyColumn.isEmpty() || resultColumns.contains(keyColumn, keyTable),
diff --git a/room/room-ktx/src/test/java/androidx/room/MigrationTest.kt b/room/room-ktx/src/test/java/androidx/room/MigrationTest.kt
index 753a700..162f50b 100644
--- a/room/room-ktx/src/test/java/androidx/room/MigrationTest.kt
+++ b/room/room-ktx/src/test/java/androidx/room/MigrationTest.kt
@@ -116,7 +116,7 @@
         throw UnsupportedOperationException()
     }
 
-    override fun query(query: String, bindArgs: Array<Any?>): Cursor {
+    override fun query(query: String, bindArgs: Array<out Any?>): Cursor {
         throw UnsupportedOperationException()
     }
 
@@ -135,7 +135,7 @@
         throw UnsupportedOperationException()
     }
 
-    override fun delete(table: String, whereClause: String?, whereArgs: Array<Any?>?): Int {
+    override fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int {
         throw UnsupportedOperationException()
     }
 
@@ -144,7 +144,7 @@
         conflictAlgorithm: Int,
         values: ContentValues,
         whereClause: String?,
-        whereArgs: Array<Any?>?
+        whereArgs: Array<out Any?>?
     ): Int {
         throw UnsupportedOperationException()
     }
@@ -153,7 +153,7 @@
         throw UnsupportedOperationException()
     }
 
-    override fun execSQL(sql: String, bindArgs: Array<Any?>) {
+    override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
         throw UnsupportedOperationException()
     }
 
diff --git a/room/room-runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.kt b/room/room-runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.kt
index 4cafb11..5c5b923 100644
--- a/room/room-runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.kt
+++ b/room/room-runtime/src/main/java/androidx/room/AutoClosingRoomOpenHelper.kt
@@ -231,7 +231,7 @@
             return KeepAliveCursor(result, autoCloser)
         }
 
-        override fun query(query: String, bindArgs: Array<Any?>): Cursor {
+        override fun query(query: String, bindArgs: Array<out Any?>): Cursor {
             val result = try {
                 autoCloser.incrementCountAndEnsureDbIsOpen().query(query, bindArgs)
             } catch (throwable: Throwable) {
@@ -275,7 +275,7 @@
             }
         }
 
-        override fun delete(table: String, whereClause: String?, whereArgs: Array<Any?>?): Int {
+        override fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int {
             return autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
                 db.delete(
                     table,
@@ -290,7 +290,7 @@
             conflictAlgorithm: Int,
             values: ContentValues,
             whereClause: String?,
-            whereArgs: Array<Any?>?
+            whereArgs: Array<out Any?>?
         ): Int {
             return autoCloser.executeRefCountingFunction { db: SupportSQLiteDatabase ->
                 db.update(
@@ -309,7 +309,7 @@
         }
 
         @Throws(SQLException::class)
-        override fun execSQL(sql: String, bindArgs: Array<Any?>) {
+        override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
             autoCloser.executeRefCountingFunction<Any?> { db: SupportSQLiteDatabase ->
                 db.execSQL(sql, bindArgs)
                 null
diff --git a/room/room-runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.kt b/room/room-runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.kt
index 5131681..d140a3e 100644
--- a/room/room-runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.kt
+++ b/room/room-runtime/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.kt
@@ -91,7 +91,7 @@
      * @param entities Entities to delete or update
      * @return The number of affected rows
      */
-    fun handleMultiple(entities: Array<T>): Int {
+    fun handleMultiple(entities: Array<out T>): Int {
         val stmt: SupportSQLiteStatement = acquire()
         return try {
             var total = 0
diff --git a/room/room-runtime/src/main/java/androidx/room/EntityInsertionAdapter.kt b/room/room-runtime/src/main/java/androidx/room/EntityInsertionAdapter.kt
index 2aec5e5..0e2473a 100644
--- a/room/room-runtime/src/main/java/androidx/room/EntityInsertionAdapter.kt
+++ b/room/room-runtime/src/main/java/androidx/room/EntityInsertionAdapter.kt
@@ -60,7 +60,7 @@
      *
      * @param entities Entities to insert
      */
-    fun insert(entities: Array<T>) {
+    fun insert(entities: Array<out T>) {
         val stmt: SupportSQLiteStatement = acquire()
         try {
             entities.forEach { entity ->
@@ -131,7 +131,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    fun insertAndReturnIdsArray(entities: Array<T>): LongArray {
+    fun insertAndReturnIdsArray(entities: Array<out T>): LongArray {
         val stmt: SupportSQLiteStatement = acquire()
         return try {
             val result = LongArray(entities.size)
@@ -151,7 +151,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    fun insertAndReturnIdsArrayBox(entities: Collection<T>): Array<Long> {
+    fun insertAndReturnIdsArrayBox(entities: Collection<T>): Array<out Long> {
         val stmt: SupportSQLiteStatement = acquire()
         val iterator = entities.iterator()
         return try {
@@ -172,7 +172,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    fun insertAndReturnIdsArrayBox(entities: Array<T>): Array<Long> {
+    fun insertAndReturnIdsArrayBox(entities: Array<out T>): Array<out Long> {
         val stmt: SupportSQLiteStatement = acquire()
         val iterator = entities.iterator()
         return try {
@@ -193,7 +193,7 @@
      * @param entities Entities to insert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    fun insertAndReturnIdsList(entities: Array<T>): List<Long> {
+    fun insertAndReturnIdsList(entities: Array<out T>): List<Long> {
         val stmt: SupportSQLiteStatement = acquire()
         return try {
             buildList {
diff --git a/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt b/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt
index 1ce5838..aaefe4c 100644
--- a/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt
+++ b/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt
@@ -70,7 +70,7 @@
      *
      * @param entities array of entities to upsert
      */
-    fun upsert(entities: Array<T>) {
+    fun upsert(entities: Array<out T>) {
         entities.forEach { entity ->
             try {
                 insertionAdapter.insert(entity)
@@ -116,7 +116,7 @@
      * @param entities Entities to upsert
      * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
      */
-    fun upsertAndReturnIdsArray(entities: Array<T>): LongArray {
+    fun upsertAndReturnIdsArray(entities: Array<out T>): LongArray {
         return LongArray(entities.size) { index ->
             try {
                 insertionAdapter.insertAndReturnId(entities[index])
@@ -142,7 +142,7 @@
         }
     }
 
-    fun upsertAndReturnIdsList(entities: Array<T>): List<Long> {
+    fun upsertAndReturnIdsList(entities: Array<out T>): List<Long> {
         return buildList {
             entities.forEach { entity ->
                 try {
@@ -170,7 +170,7 @@
         }
     }
 
-    fun upsertAndReturnIdsArrayBox(entities: Array<T>): Array<Long> {
+    fun upsertAndReturnIdsArrayBox(entities: Array<out T>): Array<out Long> {
         return Array(entities.size) { index ->
             try {
                 insertionAdapter.insertAndReturnId(entities[index])
@@ -182,7 +182,7 @@
         }
     }
 
-    fun upsertAndReturnIdsArrayBox(entities: Collection<T>): Array<Long> {
+    fun upsertAndReturnIdsArrayBox(entities: Collection<T>): Array<out Long> {
         val iterator = entities.iterator()
         return Array(entities.size) {
             val entity = iterator.next()
diff --git a/room/room-runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.kt b/room/room-runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.kt
index 2a51d0c..73e7ee6 100644
--- a/room/room-runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.kt
+++ b/room/room-runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.kt
@@ -32,7 +32,7 @@
     internal val liveDataSet: MutableSet<LiveData<*>> = Collections.newSetFromMap(IdentityHashMap())
 
     fun <T> create(
-        tableNames: Array<String>,
+        tableNames: Array<out String>,
         inTransaction: Boolean,
         computeFunction: Callable<T>
     ): LiveData<T> {
diff --git a/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
index b766d6f..251b82a 100644
--- a/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
@@ -60,7 +60,7 @@
     vararg tableNames: String
 ) {
     internal val tableIdLookup: Map<String, Int>
-    internal val tablesNames: Array<String>
+    internal val tablesNames: Array<out String>
 
     private var autoCloser: AutoCloser? = null
 
@@ -267,7 +267,7 @@
         }
     }
 
-    private fun validateAndResolveTableNames(tableNames: Array<String>): Array<String> {
+    private fun validateAndResolveTableNames(tableNames: Array<out String>): Array<out String> {
         val resolved = resolveViews(tableNames)
         resolved.forEach { tableName ->
             require(tableIdLookup.containsKey(tableName.lowercase(Locale.US))) {
@@ -283,7 +283,7 @@
      * @param names The names of tables or views.
      * @return The names of the underlying tables.
      */
-    private fun resolveViews(names: Array<String>): Array<String> {
+    private fun resolveViews(names: Array<out String>): Array<out String> {
         return buildSet {
             names.forEach { name ->
                 if (viewTables.containsKey(name.lowercase(Locale.US))) {
@@ -548,7 +548,7 @@
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     @Deprecated("Use [createLiveData(String[], boolean, Callable)]")
     open fun <T> createLiveData(
-        tableNames: Array<String>,
+        tableNames: Array<out String>,
         computeFunction: Callable<T>
     ): LiveData<T> {
         return createLiveData(tableNames, false, computeFunction)
@@ -571,7 +571,7 @@
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     open fun <T> createLiveData(
-        tableNames: Array<String>,
+        tableNames: Array<out String>,
         inTransaction: Boolean,
         computeFunction: Callable<T>
     ): LiveData<T> {
@@ -589,7 +589,7 @@
     internal class ObserverWrapper(
         internal val observer: Observer,
         internal val tableIds: IntArray,
-        private val tableNames: Array<String>
+        private val tableNames: Array<out String>
     ) {
         private val singleTableSet = if (tableNames.isNotEmpty()) {
             setOf(tableNames[0])
@@ -663,7 +663,7 @@
     /**
      * An observer that can listen for changes in the database.
      */
-    abstract class Observer(internal val tables: Array<String>) {
+    abstract class Observer(internal val tables: Array<out String>) {
         /**
          * Observes the given list of tables and views.
          *
diff --git a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt
index e4eb340..0659f92 100644
--- a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt
+++ b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationClient.kt
@@ -56,7 +56,7 @@
 
     val callback: IMultiInstanceInvalidationCallback =
         object : IMultiInstanceInvalidationCallback.Stub() {
-            override fun onInvalidation(tables: Array<String>) {
+            override fun onInvalidation(tables: Array<out String>) {
                 executor.execute { invalidationTracker.notifyObserversByTableNames(*tables) }
             }
         }
diff --git a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt
index 9d72873..761541d 100644
--- a/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt
+++ b/room/room-runtime/src/main/java/androidx/room/MultiInstanceInvalidationService.kt
@@ -86,7 +86,7 @@
 
             // Broadcasts table invalidation to other instances of the same database file.
             // The broadcast is not sent to the caller itself.
-            override fun broadcastInvalidation(clientId: Int, tables: Array<String>) {
+            override fun broadcastInvalidation(clientId: Int, tables: Array<out String>) {
                 synchronized(callbackList) {
                     val name = clientNames[clientId]
                     if (name == null) {
diff --git a/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt b/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt
index 248dfe5..4fb5661 100644
--- a/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt
+++ b/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt
@@ -94,7 +94,7 @@
         return delegate.query(query)
     }
 
-    override fun query(query: String, bindArgs: Array<Any?>): Cursor {
+    override fun query(query: String, bindArgs: Array<out Any?>): Cursor {
         queryCallbackExecutor.execute { queryCallback.onQuery(query, bindArgs.toList()) }
         return delegate.query(query, bindArgs)
     }
@@ -136,7 +136,7 @@
     // Suppress warning about `SQL` in execSQL not being camel case. This is an override function
     // and it can't be renamed.
     @Suppress("AcronymName")
-    override fun execSQL(sql: String, bindArgs: Array<Any?>) {
+    override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
         val inputArguments = mutableListOf<Any>()
         inputArguments.addAll(listOf(bindArgs))
         queryCallbackExecutor.execute {
diff --git a/room/room-runtime/src/main/java/androidx/room/Room.kt b/room/room-runtime/src/main/java/androidx/room/Room.kt
index 9a68205..fcd1527 100644
--- a/room/room-runtime/src/main/java/androidx/room/Room.kt
+++ b/room/room-runtime/src/main/java/androidx/room/Room.kt
@@ -53,7 +53,7 @@
             val aClass = Class.forName(
                 fullClassName, true, klass.classLoader
             ) as Class<T>
-            aClass.newInstance()
+            aClass.getDeclaredConstructor().newInstance()
         } catch (e: ClassNotFoundException) {
             throw RuntimeException(
                 "Cannot find implementation for ${klass.canonicalName}. $implName does not " +
diff --git a/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt b/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
index 1c5732e..599d9d3 100644
--- a/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
+++ b/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
@@ -460,7 +460,7 @@
      * @param args  The bind arguments for the placeholders in the query
      * @return A Cursor obtained by running the given query in the Room database.
      */
-    open fun query(query: String, args: Array<Any?>?): Cursor {
+    open fun query(query: String, args: Array<out Any?>?): Cursor {
         return openHelper.writableDatabase.query(SimpleSQLiteQuery(query, args))
     }
 
diff --git a/room/room-runtime/src/main/java/androidx/room/RoomTrackingLiveData.kt b/room/room-runtime/src/main/java/androidx/room/RoomTrackingLiveData.kt
index a2785b8..8b0d7a6 100644
--- a/room/room-runtime/src/main/java/androidx/room/RoomTrackingLiveData.kt
+++ b/room/room-runtime/src/main/java/androidx/room/RoomTrackingLiveData.kt
@@ -44,7 +44,7 @@
     private val container: InvalidationLiveDataContainer,
     val inTransaction: Boolean,
     val computeFunction: Callable<T>,
-    tableNames: Array<String>
+    tableNames: Array<out String>
 ) : LiveData<T>() {
     val observer: InvalidationTracker.Observer = object : InvalidationTracker.Observer(tableNames) {
         override fun onInvalidated(tables: Set<String>) {
diff --git a/room/room-testing/api/current.ignore b/room/room-testing/api/current.ignore
new file mode 100644
index 0000000..abbadbf
--- /dev/null
+++ b/room/room-testing/api/current.ignore
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+ChangedThrows: androidx.room.testing.MigrationTestHelper#runMigrationsAndValidate(String, int, boolean, androidx.room.migration.Migration...):
+    Method androidx.room.testing.MigrationTestHelper.runMigrationsAndValidate no longer throws exception java.io.IOException
+
+
+RemovedMethod: androidx.room.testing.MigrationTestHelper#MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec>):
+    Removed constructor androidx.room.testing.MigrationTestHelper(android.app.Instrumentation,Class<? extends androidx.room.RoomDatabase>,java.util.List<androidx.room.migration.AutoMigrationSpec>)
+RemovedMethod: androidx.room.testing.MigrationTestHelper#MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory):
+    Removed constructor androidx.room.testing.MigrationTestHelper(android.app.Instrumentation,Class<? extends androidx.room.RoomDatabase>,java.util.List<androidx.room.migration.AutoMigrationSpec>,androidx.sqlite.db.SupportSQLiteOpenHelper.Factory)
diff --git a/room/room-testing/api/current.txt b/room/room-testing/api/current.txt
index ea0639e..7138ac0 100644
--- a/room/room-testing/api/current.txt
+++ b/room/room-testing/api/current.txt
@@ -2,15 +2,15 @@
 package androidx.room.testing {
 
   public class MigrationTestHelper extends org.junit.rules.TestWatcher {
-    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!);
-    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
-    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
-    method public void closeWhenFinished(androidx.room.RoomDatabase!);
-    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
-    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
+    method public void closeWhenFinished(androidx.room.RoomDatabase db);
+    method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public androidx.sqlite.db.SupportSQLiteDatabase createDatabase(String name, int version) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase runMigrationsAndValidate(String name, int version, boolean validateDroppedTables, androidx.room.migration.Migration... migrations);
   }
 
 }
diff --git a/room/room-testing/api/public_plus_experimental_current.txt b/room/room-testing/api/public_plus_experimental_current.txt
index ea0639e..7138ac0 100644
--- a/room/room-testing/api/public_plus_experimental_current.txt
+++ b/room/room-testing/api/public_plus_experimental_current.txt
@@ -2,15 +2,15 @@
 package androidx.room.testing {
 
   public class MigrationTestHelper extends org.junit.rules.TestWatcher {
-    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!);
-    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
-    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
-    method public void closeWhenFinished(androidx.room.RoomDatabase!);
-    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
-    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
+    method public void closeWhenFinished(androidx.room.RoomDatabase db);
+    method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public androidx.sqlite.db.SupportSQLiteDatabase createDatabase(String name, int version) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase runMigrationsAndValidate(String name, int version, boolean validateDroppedTables, androidx.room.migration.Migration... migrations);
   }
 
 }
diff --git a/room/room-testing/api/restricted_current.ignore b/room/room-testing/api/restricted_current.ignore
new file mode 100644
index 0000000..abbadbf
--- /dev/null
+++ b/room/room-testing/api/restricted_current.ignore
@@ -0,0 +1,9 @@
+// Baseline format: 1.0
+ChangedThrows: androidx.room.testing.MigrationTestHelper#runMigrationsAndValidate(String, int, boolean, androidx.room.migration.Migration...):
+    Method androidx.room.testing.MigrationTestHelper.runMigrationsAndValidate no longer throws exception java.io.IOException
+
+
+RemovedMethod: androidx.room.testing.MigrationTestHelper#MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec>):
+    Removed constructor androidx.room.testing.MigrationTestHelper(android.app.Instrumentation,Class<? extends androidx.room.RoomDatabase>,java.util.List<androidx.room.migration.AutoMigrationSpec>)
+RemovedMethod: androidx.room.testing.MigrationTestHelper#MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory):
+    Removed constructor androidx.room.testing.MigrationTestHelper(android.app.Instrumentation,Class<? extends androidx.room.RoomDatabase>,java.util.List<androidx.room.migration.AutoMigrationSpec>,androidx.sqlite.db.SupportSQLiteOpenHelper.Factory)
diff --git a/room/room-testing/api/restricted_current.txt b/room/room-testing/api/restricted_current.txt
index ea0639e..7138ac0 100644
--- a/room/room-testing/api/restricted_current.txt
+++ b/room/room-testing/api/restricted_current.txt
@@ -2,15 +2,15 @@
 package androidx.room.testing {
 
   public class MigrationTestHelper extends org.junit.rules.TestWatcher {
-    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!);
-    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>);
-    ctor public MigrationTestHelper(android.app.Instrumentation, Class<? extends androidx.room.RoomDatabase>, java.util.List<androidx.room.migration.AutoMigrationSpec!>, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory);
-    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
-    method public void closeWhenFinished(androidx.room.RoomDatabase!);
-    method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
-    method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration!...) throws java.io.IOException;
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
+    method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
+    method public void closeWhenFinished(androidx.room.RoomDatabase db);
+    method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public androidx.sqlite.db.SupportSQLiteDatabase createDatabase(String name, int version) throws java.io.IOException;
+    method public androidx.sqlite.db.SupportSQLiteDatabase runMigrationsAndValidate(String name, int version, boolean validateDroppedTables, androidx.room.migration.Migration... migrations);
   }
 
 }
diff --git a/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
deleted file mode 100644
index 8e282c4..0000000
--- a/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
+++ /dev/null
@@ -1,719 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.room.testing;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptySet;
-
-import android.annotation.SuppressLint;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.database.Cursor;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.arch.core.executor.ArchTaskExecutor;
-import androidx.room.AutoMigration;
-import androidx.room.DatabaseConfiguration;
-import androidx.room.Room;
-import androidx.room.RoomDatabase;
-import androidx.room.RoomOpenHelper;
-import androidx.room.RoomOpenHelper.ValidationResult;
-import androidx.room.migration.AutoMigrationSpec;
-import androidx.room.migration.Migration;
-import androidx.room.migration.bundle.DatabaseBundle;
-import androidx.room.migration.bundle.DatabaseViewBundle;
-import androidx.room.migration.bundle.EntityBundle;
-import androidx.room.migration.bundle.FieldBundle;
-import androidx.room.migration.bundle.ForeignKeyBundle;
-import androidx.room.migration.bundle.FtsEntityBundle;
-import androidx.room.migration.bundle.IndexBundle;
-import androidx.room.migration.bundle.SchemaBundle;
-import androidx.room.util.FtsTableInfo;
-import androidx.room.util.TableInfo;
-import androidx.room.util.ViewInfo;
-import androidx.sqlite.db.SupportSQLiteDatabase;
-import androidx.sqlite.db.SupportSQLiteOpenHelper;
-import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A class that can be used in your Instrumentation tests that can create the database in an
- * older schema.
- * <p>
- * You must copy the schema json files (created by passing {@code room.schemaLocation} argument
- * into the annotation processor) into your test assets and pass in the path for that folder into
- * the constructor. This class will read the folder and extract the schemas from there.
- * <pre>
- * android {
- *   defaultConfig {
- *     javaCompileOptions {
- *       annotationProcessorOptions {
- *         arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
- *       }
- *     }
- *   }
- *   sourceSets {
- *     androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
- *   }
- * }
- * </pre>
- */
-public class MigrationTestHelper extends TestWatcher {
-    private static final String TAG = "MigrationTestHelper";
-    private final String mAssetsFolder;
-    private final SupportSQLiteOpenHelper.Factory mOpenFactory;
-    private List<WeakReference<SupportSQLiteDatabase>> mManagedDatabases = new ArrayList<>();
-    private List<WeakReference<RoomDatabase>> mManagedRoomDatabases = new ArrayList<>();
-    private boolean mTestStarted;
-    private Instrumentation mInstrumentation;
-    @Nullable
-    private List<AutoMigrationSpec> mSpecs;
-    @Nullable
-    private Class<? extends RoomDatabase> mDatabaseClass;
-    @NonNull
-    private DatabaseConfiguration mDatabaseConfiguration;
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     *
-     * @deprecated Cannot be used to run migration tests involving {@link AutoMigration}.
-     * <p>
-     * To test {@link AutoMigration}, you must use
-     * {@link #MigrationTestHelper(Instrumentation, Class, List, SupportSQLiteOpenHelper.Factory)}
-     * for tests containing a {@link androidx.room.ProvidedAutoMigrationSpec}, or use
-     * {@link #MigrationTestHelper(Instrumentation, Class, List)}
-     * otherwise.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param assetsFolder    The asset folder in the assets directory.
-     */
-    @Deprecated
-    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder) {
-        this(instrumentation, assetsFolder, new FrameworkSQLiteOpenHelperFactory());
-    }
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     *
-     * @deprecated Cannot be used to run migration tests involving {@link AutoMigration}.
-     * <p>
-     * To test {@link AutoMigration}, you must use
-     * {@link #MigrationTestHelper(Instrumentation, Class, List, SupportSQLiteOpenHelper.Factory)}
-     * for tests containing a {@link androidx.room.ProvidedAutoMigrationSpec}, or use
-     * {@link #MigrationTestHelper(Instrumentation, Class, List)}
-     * otherwise.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param assetsFolder    The asset folder in the assets directory.
-     * @param openFactory     Factory class that allows creation of {@link SupportSQLiteOpenHelper}
-     */
-    @Deprecated
-    public MigrationTestHelper(Instrumentation instrumentation, String assetsFolder,
-            SupportSQLiteOpenHelper.Factory openFactory) {
-        mInstrumentation = instrumentation;
-        mAssetsFolder = assetsFolder;
-        mOpenFactory = openFactory;
-        mDatabaseClass = null;
-        mSpecs = new ArrayList<>();
-    }
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param databaseClass   The Database class to be tested.
-     */
-    public MigrationTestHelper(@NonNull Instrumentation instrumentation,
-            @NonNull Class<? extends RoomDatabase> databaseClass) {
-        this(instrumentation, databaseClass, new ArrayList<>(),
-                new FrameworkSQLiteOpenHelperFactory());
-    }
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     * <p>
-     * An instance of a class annotated with {@link androidx.room.ProvidedAutoMigrationSpec} has
-     * to be provided to Room using this constructor. MigrationTestHelper will map auto migration
-     * spec classes to their provided instances before running and validatingt the Migrations.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param databaseClass   The Database class to be tested.
-     * @param specs           The list of available auto migration specs that will be provided to
-     *                        Room at runtime.
-     */
-    public MigrationTestHelper(@NonNull Instrumentation instrumentation,
-            @NonNull Class<? extends RoomDatabase> databaseClass,
-            @NonNull List<AutoMigrationSpec> specs) {
-        this(instrumentation, databaseClass, specs, new FrameworkSQLiteOpenHelperFactory());
-    }
-
-    /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
-     * (falls back to the app resources) and the target context to create the database.
-     * <p>
-     * An instance of a class annotated with {@link androidx.room.ProvidedAutoMigrationSpec} has
-     * to be provided to Room using this constructor. MigrationTestHelper will map auto migration
-     * spec classes to their provided instances before running and validatingt the Migrations.
-     *
-     * @param instrumentation The instrumentation instance.
-     * @param databaseClass   The Database class to be tested.
-     * @param specs           The list of available auto migration specs that will be provided to
-     *                        Room at runtime.
-     * @param openFactory     Factory class that allows creation of {@link SupportSQLiteOpenHelper}
-     */
-    public MigrationTestHelper(@NonNull Instrumentation instrumentation,
-            @NonNull Class<? extends RoomDatabase> databaseClass,
-            @NonNull List<AutoMigrationSpec> specs,
-            @NonNull SupportSQLiteOpenHelper.Factory openFactory
-    ) {
-        String assetsFolder = databaseClass.getCanonicalName();
-        mInstrumentation = instrumentation;
-        if (assetsFolder.endsWith("/")) {
-            assetsFolder = assetsFolder.substring(0, assetsFolder.length() - 1);
-        }
-        mAssetsFolder = assetsFolder;
-        mOpenFactory = openFactory;
-        mDatabaseClass = databaseClass;
-        mSpecs = specs;
-    }
-
-    @Override
-    protected void starting(Description description) {
-        super.starting(description);
-        mTestStarted = true;
-    }
-
-    /**
-     * Creates the database in the given version.
-     * If the database file already exists, it tries to delete it first. If delete fails, throws
-     * an exception.
-     *
-     * @param name    The name of the database.
-     * @param version The version in which the database should be created.
-     * @return A database connection which has the schema in the requested version.
-     * @throws IOException If it cannot find the schema description in the assets folder.
-     */
-    @SuppressLint("RestrictedApi")
-    @SuppressWarnings("SameParameterValue")
-    public SupportSQLiteDatabase createDatabase(String name, int version) throws IOException {
-        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
-        if (dbPath.exists()) {
-            Log.d(TAG, "deleting database file " + name);
-            if (!dbPath.delete()) {
-                throw new IllegalStateException("There is a database file and I could not delete"
-                        + " it. Make sure you don't have any open connections to that database"
-                        + " before calling this method.");
-            }
-        }
-        SchemaBundle schemaBundle = loadSchema(version);
-        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
-        DatabaseConfiguration configuration = new DatabaseConfiguration(
-                mInstrumentation.getTargetContext(),
-                name,
-                mOpenFactory,
-                container,
-                null,
-                true,
-                RoomDatabase.JournalMode.TRUNCATE,
-                ArchTaskExecutor.getIOThreadExecutor(),
-                ArchTaskExecutor.getIOThreadExecutor(),
-                null,
-                true,
-                false,
-                emptySet(),
-                null,
-                null,
-                null,
-                null,
-                emptyList(),
-                emptyList());
-        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
-                new CreatingDelegate(schemaBundle.getDatabase()),
-                schemaBundle.getDatabase().getIdentityHash(),
-                // we pass the same hash twice since an old schema does not necessarily have
-                // a legacy hash and we would not even persist it.
-                schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, roomOpenHelper);
-    }
-
-    /**
-     * Runs the given set of migrations on the provided database.
-     * <p>
-     * It uses the same algorithm that Room uses to choose migrations so the migrations instances
-     * that are provided to this method must be sufficient to bring the database from current
-     * version to the desired version.
-     * <p>
-     * After the migration, the method validates the database schema to ensure that migration
-     * result matches the expected schema. Handling of dropped tables depends on the
-     * {@code validateDroppedTables} argument. If set to true, the verification will fail if it
-     * finds a table that is not registered in the Database. If set to false, extra tables in the
-     * database will be ignored (this is the runtime library behavior).
-     *
-     * @param name                  The database name. You must first create this database via
-     *                              {@link #createDatabase(String, int)}.
-     * @param version               The final version after applying the migrations.
-     * @param validateDroppedTables If set to true, validation will fail if the database has
-     *                              unknown
-     *                              tables.
-     * @param migrations            The list of available migrations.
-     * @throws IOException           If it cannot find the schema for {@code toVersion}.
-     * @throws IllegalStateException If the schema validation fails.
-     */
-    @SuppressLint("RestrictedApi")
-    public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
-            boolean validateDroppedTables, Migration... migrations) throws IOException {
-        File dbPath = mInstrumentation.getTargetContext().getDatabasePath(name);
-        if (!dbPath.exists()) {
-            throw new IllegalStateException("Cannot find the database file for " + name + ". "
-                    + "Before calling runMigrations, you must first create the database via "
-                    + "createDatabase.");
-        }
-        SchemaBundle schemaBundle = loadSchema(version);
-        RoomDatabase.MigrationContainer container = new RoomDatabase.MigrationContainer();
-        container.addMigrations(migrations);
-        List<Migration> autoMigrations = getAutoMigrations(mSpecs);
-        for (Migration autoMigration : autoMigrations) {
-            boolean migrationExists = container.contains(
-                    autoMigration.startVersion,
-                    autoMigration.endVersion
-            );
-            if (!migrationExists) {
-                container.addMigrations(autoMigration);
-            }
-        }
-
-        mDatabaseConfiguration = new DatabaseConfiguration(
-                mInstrumentation.getTargetContext(),
-                name,
-                mOpenFactory,
-                container,
-                null,
-                true,
-                RoomDatabase.JournalMode.TRUNCATE,
-                ArchTaskExecutor.getIOThreadExecutor(),
-                ArchTaskExecutor.getIOThreadExecutor(),
-                null,
-                true,
-                false,
-                emptySet(),
-                null,
-                null,
-                null,
-                null,
-                emptyList(),
-                emptyList());
-        RoomOpenHelper roomOpenHelper = new RoomOpenHelper(mDatabaseConfiguration,
-                new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
-                // we pass the same hash twice since an old schema does not necessarily have
-                // a legacy hash and we would not even persist it.
-                schemaBundle.getDatabase().getIdentityHash(),
-                schemaBundle.getDatabase().getIdentityHash());
-        return openDatabase(name, roomOpenHelper);
-    }
-
-    /**
-     * Returns the {@link DatabaseConfiguration} at the current state of the database after all
-     * migrations have been run and validated in
-     * {@link MigrationTestHelper#runMigrationsAndValidate(String, int, boolean, Migration...)}.
-     * <p>
-     * Returns null if runMigrationsAndValidate() has not been called yet.
-     *
-     * @hide
-     */
-    @Nullable
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public DatabaseConfiguration getDbConfigurationAfterMigrations() {
-        return mDatabaseConfiguration;
-    }
-
-    /**
-     * Returns a list of {@link Migration} of a database that has been generated using
-     * {@link AutoMigration}.
-     */
-    @NonNull
-    private List<Migration> getAutoMigrations(List<AutoMigrationSpec> userProvidedSpecs) {
-        if (mDatabaseClass == null) {
-            if (userProvidedSpecs.isEmpty()) {
-                // TODO: Detect that there are auto migrations to test when a deprecated
-                //  constructor is used.
-                Log.e(TAG, "If you have any AutoMigrations in your implementation, you must use "
-                        + "a non-deprecated MigrationTestHelper constructor to provide the "
-                        + "Database class in order to test them. If you do not have any "
-                        + "AutoMigrations to test, you may ignore this warning.");
-                return new ArrayList<>();
-            } else {
-                throw new IllegalStateException("You must provide the database class in the "
-                        + "MigrationTestHelper constructor in order to test auto migrations.");
-            }
-        }
-
-        RoomDatabase db = Room.getGeneratedImplementation(mDatabaseClass, "_Impl");
-        Set<Class<? extends AutoMigrationSpec>> requiredAutoMigrationSpecs =
-                db.getRequiredAutoMigrationSpecs();
-        return db.getAutoMigrations(
-                createAutoMigrationSpecMap(requiredAutoMigrationSpecs, userProvidedSpecs)
-        );
-    }
-
-    /**
-     * Maps auto migration spec classes to their provided instance.
-     */
-    private Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> createAutoMigrationSpecMap(
-            Set<Class<? extends AutoMigrationSpec>> requiredAutoMigrationSpecs,
-            List<AutoMigrationSpec> userProvidedSpecs) {
-        Map<Class<? extends AutoMigrationSpec>, AutoMigrationSpec> specMap = new HashMap<>();
-        if (requiredAutoMigrationSpecs.isEmpty()) {
-            return specMap;
-        }
-
-        if (userProvidedSpecs == null) {
-            throw new IllegalStateException(
-                    "You must provide all required auto migration specs in the "
-                            + "MigrationTestHelper constructor."
-            );
-        }
-
-        for (Class<? extends AutoMigrationSpec> spec : requiredAutoMigrationSpecs) {
-            boolean found = false;
-            AutoMigrationSpec match = null;
-            for (AutoMigrationSpec provided : userProvidedSpecs) {
-                if (spec.isAssignableFrom(provided.getClass())) {
-                    found = true;
-                    match = provided;
-                    break;
-                }
-            }
-            if (!found) {
-                throw new IllegalArgumentException(
-                        "A required auto migration spec (" + spec.getCanonicalName() + ") has not"
-                                + " been provided."
-                );
-            }
-            specMap.put(spec, match);
-        }
-        return specMap;
-    }
-
-
-    private SupportSQLiteDatabase openDatabase(String name, RoomOpenHelper roomOpenHelper) {
-        SupportSQLiteOpenHelper.Configuration config =
-                SupportSQLiteOpenHelper.Configuration
-                        .builder(mInstrumentation.getTargetContext())
-                        .callback(roomOpenHelper)
-                        .name(name)
-                        .build();
-        SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
-        mManagedDatabases.add(new WeakReference<>(db));
-        return db;
-    }
-
-    @Override
-    protected void finished(Description description) {
-        super.finished(description);
-        for (WeakReference<SupportSQLiteDatabase> dbRef : mManagedDatabases) {
-            SupportSQLiteDatabase db = dbRef.get();
-            if (db != null && db.isOpen()) {
-                try {
-                    db.close();
-                } catch (Throwable ignored) {
-                }
-            }
-        }
-        for (WeakReference<RoomDatabase> dbRef : mManagedRoomDatabases) {
-            final RoomDatabase roomDatabase = dbRef.get();
-            if (roomDatabase != null) {
-                roomDatabase.close();
-            }
-        }
-    }
-
-    /**
-     * Registers a database connection to be automatically closed when the test finishes.
-     * <p>
-     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
-     * {@link org.junit.Rule Rule} annotation.
-     *
-     * @param db The database connection that should be closed after the test finishes.
-     */
-    public void closeWhenFinished(SupportSQLiteDatabase db) {
-        if (!mTestStarted) {
-            throw new IllegalStateException("You cannot register a database to be closed before"
-                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
-                    + " test rule? (@Rule)");
-        }
-        mManagedDatabases.add(new WeakReference<>(db));
-    }
-
-    /**
-     * Registers a database connection to be automatically closed when the test finishes.
-     * <p>
-     * This only works if {@code MigrationTestHelper} is registered as a Junit test rule via
-     * {@link org.junit.Rule Rule} annotation.
-     *
-     * @param db The RoomDatabase instance which holds the database.
-     */
-    public void closeWhenFinished(RoomDatabase db) {
-        if (!mTestStarted) {
-            throw new IllegalStateException("You cannot register a database to be closed before"
-                    + " the test starts. Maybe you forgot to annotate MigrationTestHelper as a"
-                    + " test rule? (@Rule)");
-        }
-        mManagedRoomDatabases.add(new WeakReference<>(db));
-    }
-
-    private SchemaBundle loadSchema(int version) throws IOException {
-        try {
-            return loadSchema(mInstrumentation.getContext(), version);
-        } catch (FileNotFoundException testAssetsIOExceptions) {
-            Log.w(TAG, "Could not find the schema file in the test assets. Checking the"
-                    + " application assets");
-            try {
-                return loadSchema(mInstrumentation.getTargetContext(), version);
-            } catch (FileNotFoundException appAssetsException) {
-                // throw the test assets exception instead
-                throw new FileNotFoundException("Cannot find the schema file in the assets folder. "
-                        + "Make sure to include the exported json schemas in your test assert "
-                        + "inputs. See "
-                        + "https://developer.android.com/training/data-storage/room/"
-                        + "migrating-db-versions#export-schema for details. Missing file: "
-                        + testAssetsIOExceptions.getMessage());
-            }
-        }
-    }
-
-    private SchemaBundle loadSchema(Context context, int version) throws IOException {
-        InputStream input = context.getAssets().open(mAssetsFolder + "/" + version + ".json");
-        return SchemaBundle.deserialize(input);
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static TableInfo toTableInfo(EntityBundle entityBundle) {
-        return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
-                toForeignKeys(entityBundle.getForeignKeys()), toIndices(entityBundle.getIndices()));
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static FtsTableInfo toFtsTableInfo(FtsEntityBundle ftsEntityBundle) {
-        return new FtsTableInfo(ftsEntityBundle.getTableName(), toColumnNamesSet(ftsEntityBundle),
-                ftsEntityBundle.getCreateSql());
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static ViewInfo toViewInfo(DatabaseViewBundle viewBundle) {
-        return new ViewInfo(viewBundle.getViewName(), viewBundle.createView());
-    }
-
-    private static Set<TableInfo.Index> toIndices(List<IndexBundle> indices) {
-        if (indices == null) {
-            return emptySet();
-        }
-        Set<TableInfo.Index> result = new HashSet<>();
-        for (IndexBundle bundle : indices) {
-            result.add(new TableInfo.Index(bundle.getName(), bundle.isUnique(),
-                    bundle.getColumnNames(), bundle.getOrders()));
-        }
-        return result;
-    }
-
-    private static Set<TableInfo.ForeignKey> toForeignKeys(
-            List<ForeignKeyBundle> bundles) {
-        if (bundles == null) {
-            return emptySet();
-        }
-        Set<TableInfo.ForeignKey> result = new HashSet<>(bundles.size());
-        for (ForeignKeyBundle bundle : bundles) {
-            result.add(new TableInfo.ForeignKey(bundle.getTable(),
-                    bundle.getOnDelete(), bundle.getOnUpdate(),
-                    bundle.getColumns(), bundle.getReferencedColumns()));
-        }
-        return result;
-    }
-
-    private static Set<String> toColumnNamesSet(EntityBundle entity) {
-        Set<String> result = new HashSet<>();
-        for (FieldBundle field : entity.getFields()) {
-            result.add(field.getColumnName());
-        }
-        return result;
-    }
-
-    private static Map<String, TableInfo.Column> toColumnMap(EntityBundle entity) {
-        Map<String, TableInfo.Column> result = new HashMap<>();
-        for (FieldBundle bundle : entity.getFields()) {
-            TableInfo.Column column = toColumn(entity, bundle);
-            result.put(column.name, column);
-        }
-        return result;
-    }
-
-    private static TableInfo.Column toColumn(EntityBundle entity, FieldBundle field) {
-        return new TableInfo.Column(field.getColumnName(), field.getAffinity(),
-                field.isNonNull(), findPrimaryKeyPosition(entity, field), field.getDefaultValue(),
-                TableInfo.CREATED_FROM_ENTITY);
-    }
-
-    private static int findPrimaryKeyPosition(EntityBundle entity, FieldBundle field) {
-        List<String> columnNames = entity.getPrimaryKey().getColumnNames();
-        int i = 0;
-        for (String columnName : columnNames) {
-            i++;
-            if (field.getColumnName().equalsIgnoreCase(columnName)) {
-                return i;
-            }
-        }
-        return 0;
-    }
-
-    static class MigratingDelegate extends RoomOpenHelperDelegate {
-        private final boolean mVerifyDroppedTables;
-
-        MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
-            super(databaseBundle);
-            mVerifyDroppedTables = verifyDroppedTables;
-        }
-
-        @Override
-        public void createAllTables(SupportSQLiteDatabase database) {
-            throw new UnsupportedOperationException("Was expecting to migrate but received create."
-                    + "Make sure you have created the database first.");
-        }
-
-        @NonNull
-        @Override
-        public RoomOpenHelper.ValidationResult onValidateSchema(
-                @NonNull SupportSQLiteDatabase db) {
-            final Map<String, EntityBundle> tables = mDatabaseBundle.getEntitiesByTableName();
-            for (EntityBundle entity : tables.values()) {
-                if (entity instanceof FtsEntityBundle) {
-                    final FtsTableInfo expected = toFtsTableInfo((FtsEntityBundle) entity);
-                    final FtsTableInfo found = FtsTableInfo.read(db, entity.getTableName());
-                    if (!expected.equals(found)) {
-                        return new ValidationResult(false, expected.name
-                                + "\nExpected: " + expected + "\nFound: " + found);
-                    }
-                } else {
-                    final TableInfo expected = toTableInfo(entity);
-                    final TableInfo found = TableInfo.read(db, entity.getTableName());
-                    if (!expected.equals(found)) {
-                        return new ValidationResult(false, expected.name
-                                + "\nExpected: " + expected + " \nfound: " + found);
-                    }
-                }
-            }
-            for (DatabaseViewBundle view : mDatabaseBundle.getViews()) {
-                final ViewInfo expected = toViewInfo(view);
-                final ViewInfo found = ViewInfo.read(db, view.getViewName());
-                if (!expected.equals(found)) {
-                    return new ValidationResult(false, expected
-                                + "\nExpected: " + expected + " \nfound: " + found);
-                }
-            }
-            if (mVerifyDroppedTables) {
-                // now ensure tables that should be removed are removed.
-                Set<String> expectedTables = new HashSet<>();
-                for (EntityBundle entity : tables.values()) {
-                    expectedTables.add(entity.getTableName());
-                    if (entity instanceof FtsEntityBundle) {
-                        expectedTables.addAll(((FtsEntityBundle) entity).getShadowTableNames());
-                    }
-                }
-                Cursor cursor = db.query("SELECT name FROM sqlite_master WHERE type='table'"
-                                + " AND name NOT IN(?, ?, ?)",
-                        new String[]{Room.MASTER_TABLE_NAME, "android_metadata",
-                                "sqlite_sequence"});
-                //noinspection TryFinallyCanBeTryWithResources
-                try {
-                    while (cursor.moveToNext()) {
-                        final String tableName = cursor.getString(0);
-                        if (!expectedTables.contains(tableName)) {
-                            return new ValidationResult(false, "Unexpected table "
-                                    + tableName);
-                        }
-                    }
-                } finally {
-                    cursor.close();
-                }
-            }
-            return new ValidationResult(true, null);
-        }
-    }
-
-    static class CreatingDelegate extends RoomOpenHelperDelegate {
-
-        CreatingDelegate(DatabaseBundle databaseBundle) {
-            super(databaseBundle);
-        }
-
-        @Override
-        public void createAllTables(SupportSQLiteDatabase database) {
-            for (String query : mDatabaseBundle.buildCreateQueries()) {
-                database.execSQL(query);
-            }
-        }
-
-        @NonNull
-        @Override
-        public RoomOpenHelper.ValidationResult onValidateSchema(
-                @NonNull SupportSQLiteDatabase db) {
-            throw new UnsupportedOperationException("This open helper just creates the database but"
-                    + " it received a migration request.");
-        }
-    }
-
-    abstract static class RoomOpenHelperDelegate extends RoomOpenHelper.Delegate {
-        final DatabaseBundle mDatabaseBundle;
-
-        RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
-            super(databaseBundle.getVersion());
-            mDatabaseBundle = databaseBundle;
-        }
-
-        @Override
-        public void dropAllTables(SupportSQLiteDatabase database) {
-            throw new UnsupportedOperationException("cannot drop all tables in the test");
-        }
-
-        @Override
-        public void onCreate(SupportSQLiteDatabase database) {
-        }
-
-        @Override
-        public void onOpen(SupportSQLiteDatabase database) {
-        }
-    }
-}
diff --git a/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.kt b/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.kt
new file mode 100644
index 0000000..0f33171
--- /dev/null
+++ b/room/room-testing/src/main/java/androidx/room/testing/MigrationTestHelper.kt
@@ -0,0 +1,679 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.testing
+
+import android.annotation.SuppressLint
+import android.app.Instrumentation
+import android.content.Context
+import android.util.Log
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.room.DatabaseConfiguration
+import androidx.room.Room
+import androidx.room.Room.getGeneratedImplementation
+import androidx.room.RoomDatabase
+import androidx.room.RoomOpenHelper
+import androidx.room.migration.AutoMigrationSpec
+import androidx.room.migration.Migration
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.DatabaseViewBundle
+import androidx.room.migration.bundle.EntityBundle
+import androidx.room.migration.bundle.FieldBundle
+import androidx.room.migration.bundle.ForeignKeyBundle
+import androidx.room.migration.bundle.FtsEntityBundle
+import androidx.room.migration.bundle.IndexBundle
+import androidx.room.migration.bundle.SchemaBundle
+import androidx.room.migration.bundle.SchemaBundle.Companion.deserialize
+import androidx.room.util.FtsTableInfo
+import androidx.room.util.TableInfo
+import androidx.room.util.ViewInfo
+import androidx.room.util.useCursor
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteOpenHelper
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.lang.ref.WeakReference
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * A class that can be used in your Instrumentation tests that can create the database in an
+ * older schema.
+ *
+ * You must copy the schema json files (created by passing `room.schemaLocation` argument
+ * into the annotation processor) into your test assets and pass in the path for that folder into
+ * the constructor. This class will read the folder and extract the schemas from there.
+ *
+ * ```
+ * android {
+ *     defaultConfig {
+ *         javaCompileOptions {
+ *             annotationProcessorOptions {
+ *                 arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+ *             }
+ *         }
+ *     }
+ *     sourceSets {
+ *         androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
+ *     }
+ * }
+ * ```
+ */
+open class MigrationTestHelper : TestWatcher {
+    private val assetsFolder: String
+    private val openFactory: SupportSQLiteOpenHelper.Factory
+    private val managedDatabases = mutableListOf<WeakReference<SupportSQLiteDatabase>>()
+    private val managedRoomDatabases = mutableListOf<WeakReference<RoomDatabase>>()
+    private var testStarted = false
+    private val instrumentation: Instrumentation
+    private val specs: List<AutoMigrationSpec>
+    private val databaseClass: Class<out RoomDatabase>?
+    internal lateinit var databaseConfiguration: DatabaseConfiguration
+
+    /**
+     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param assetsFolder    The asset folder in the assets directory.
+     * @param openFactory factory for creating an [SupportSQLiteOpenHelper]
+     */
+    @Deprecated(
+        """
+            Cannot be used to run migration tests involving [AutoMigration].
+            To test [AutoMigration], you must use [MigrationTestHelper(Instrumentation, Class, List,
+            SupportSQLiteOpenHelper.Factory)] for tests containing a
+            [androidx.room.ProvidedAutoMigrationSpec], or use
+            [MigrationTestHelper(Instrumentation, Class, List)] otherwise.
+      """
+    )
+    @JvmOverloads
+    constructor(
+        instrumentation: Instrumentation,
+        assetsFolder: String,
+        openFactory: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory()
+    ) {
+        this.instrumentation = instrumentation
+        this.assetsFolder = assetsFolder
+        this.openFactory = openFactory
+        databaseClass = null
+        specs = mutableListOf()
+    }
+
+    /**
+     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     * An instance of a class annotated with [androidx.room.ProvidedAutoMigrationSpec] has
+     * to be provided to Room using this constructor. MigrationTestHelper will map auto migration
+     * spec classes to their provided instances before running and validating the Migrations.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param databaseClass   The Database class to be tested.
+     */
+    constructor(
+        instrumentation: Instrumentation,
+        databaseClass: Class<out RoomDatabase>
+    ) : this(
+        instrumentation, databaseClass, emptyList(), FrameworkSQLiteOpenHelperFactory()
+    )
+
+    /**
+     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     *
+     * An instance of a class annotated with [androidx.room.ProvidedAutoMigrationSpec] has
+     * to be provided to Room using this constructor. MigrationTestHelper will map auto migration
+     * spec classes to their provided instances before running and validating the Migrations.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param databaseClass   The Database class to be tested.
+     * @param specs           The list of available auto migration specs that will be provided to
+     * Room at runtime.
+     * @param openFactory factory for creating an [SupportSQLiteOpenHelper]
+     */
+    @JvmOverloads
+    constructor(
+        instrumentation: Instrumentation,
+        databaseClass: Class<out RoomDatabase>,
+        specs: List<AutoMigrationSpec>,
+        openFactory: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory()
+    ) {
+        this.assetsFolder = checkNotNull(databaseClass.canonicalName).let {
+            if (it.endsWith("/")) {
+                it.substring(0, databaseClass.canonicalName!!.length - 1)
+            } else {
+                it
+            }
+        }
+        this.instrumentation = instrumentation
+        this.openFactory = openFactory
+        this.databaseClass = databaseClass
+        this.specs = specs
+    }
+
+    override fun starting(description: Description?) {
+        super.starting(description)
+        testStarted = true
+    }
+
+    /**
+     * Creates the database in the given version.
+     * If the database file already exists, it tries to delete it first. If delete fails, throws
+     * an exception.
+     *
+     * @param name    The name of the database.
+     * @param version The version in which the database should be created.
+     * @return A database connection which has the schema in the requested version.
+     */
+    @SuppressLint("RestrictedApi")
+    @Throws(IOException::class)
+    open fun createDatabase(name: String, version: Int): SupportSQLiteDatabase {
+        val dbPath: File = instrumentation.targetContext.getDatabasePath(name)
+        if (dbPath.exists()) {
+            Log.d(TAG, "deleting database file $name")
+            check(dbPath.delete()) {
+                "There is a database file and I could not delete" +
+                    " it. Make sure you don't have any open connections to that database" +
+                    " before calling this method."
+            }
+        }
+        val schemaBundle = loadSchema(version)
+        val container: RoomDatabase.MigrationContainer = RoomDatabase.MigrationContainer()
+        val configuration = DatabaseConfiguration(
+            context = instrumentation.targetContext,
+            name = name,
+            sqliteOpenHelperFactory = openFactory,
+            migrationContainer = container,
+            callbacks = null,
+            allowMainThreadQueries = true,
+            journalMode = RoomDatabase.JournalMode.TRUNCATE,
+            queryExecutor = ArchTaskExecutor.getIOThreadExecutor(),
+            transactionExecutor = ArchTaskExecutor.getIOThreadExecutor(),
+            multiInstanceInvalidationServiceIntent = null,
+            requireMigration = true,
+            allowDestructiveMigrationOnDowngrade = false,
+            migrationNotRequiredFrom = emptySet(),
+            copyFromAssetPath = null,
+            copyFromFile = null,
+            copyFromInputStream = null,
+            prepackagedDatabaseCallback = null,
+            typeConverters = emptyList(),
+            autoMigrationSpecs = emptyList()
+        )
+        val roomOpenHelper = RoomOpenHelper(
+            configuration = configuration,
+            delegate = CreatingDelegate(schemaBundle.database),
+            identityHash = schemaBundle.database.identityHash,
+            // we pass the same hash twice since an old schema does not necessarily have
+            // a legacy hash and we would not even persist it.
+            legacyHash = schemaBundle.database.identityHash
+        )
+        return openDatabase(name, roomOpenHelper)
+    }
+
+    /**
+     * Runs the given set of migrations on the provided database.
+     *
+     * It uses the same algorithm that Room uses to choose migrations so the migrations instances
+     * that are provided to this method must be sufficient to bring the database from current
+     * version to the desired version.
+     *
+     * After the migration, the method validates the database schema to ensure that migration
+     * result matches the expected schema. Handling of dropped tables depends on the
+     * `validateDroppedTables` argument. If set to true, the verification will fail if it
+     * finds a table that is not registered in the Database. If set to false, extra tables in the
+     * database will be ignored (this is the runtime library behavior).
+     *
+     * @param name                  The database name. You must first create this database via
+     * [createDatabase].
+     * @param version               The final version after applying the migrations.
+     * @param validateDroppedTables If set to true, validation will fail if the database has
+     * unknown tables.
+     * @param migrations            The list of available migrations.
+     * @throws IllegalArgumentException If the schema validation fails.
+     */
+    @SuppressLint("RestrictedApi")
+    open fun runMigrationsAndValidate(
+        name: String,
+        version: Int,
+        validateDroppedTables: Boolean,
+        vararg migrations: Migration
+    ): SupportSQLiteDatabase {
+        val dbPath = instrumentation.targetContext.getDatabasePath(name)
+        check(dbPath.exists()) {
+            "Cannot find the database file for $name. " +
+                "Before calling runMigrations, you must first create the database via " +
+                "createDatabase."
+        }
+        val schemaBundle = loadSchema(version)
+        val container = RoomDatabase.MigrationContainer()
+        container.addMigrations(*migrations)
+        val autoMigrations = getAutoMigrations(specs)
+        autoMigrations.forEach { autoMigration ->
+            val migrationExists = container.contains(
+                autoMigration.startVersion,
+                autoMigration.endVersion
+            )
+            if (!migrationExists) {
+                container.addMigrations(autoMigration)
+            }
+        }
+        databaseConfiguration = DatabaseConfiguration(
+            context = instrumentation.targetContext,
+            name = name,
+            sqliteOpenHelperFactory = openFactory,
+            migrationContainer = container,
+            callbacks = null,
+            allowMainThreadQueries = true,
+            journalMode = RoomDatabase.JournalMode.TRUNCATE,
+            queryExecutor = ArchTaskExecutor.getIOThreadExecutor(),
+            transactionExecutor = ArchTaskExecutor.getIOThreadExecutor(),
+            multiInstanceInvalidationServiceIntent = null,
+            requireMigration = true,
+            allowDestructiveMigrationOnDowngrade = false,
+            migrationNotRequiredFrom = emptySet(),
+            copyFromAssetPath = null,
+            copyFromFile = null,
+            copyFromInputStream = null,
+            prepackagedDatabaseCallback = null,
+            typeConverters = emptyList(),
+            autoMigrationSpecs = emptyList()
+        )
+        val roomOpenHelper = RoomOpenHelper(
+            configuration = databaseConfiguration,
+            delegate = MigratingDelegate(
+                databaseBundle = schemaBundle.database,
+                // we pass the same hash twice since an old schema does not necessarily have
+                // a legacy hash and we would not even persist it.
+                mVerifyDroppedTables = validateDroppedTables
+            ),
+            identityHash = schemaBundle.database.identityHash,
+            legacyHash = schemaBundle.database.identityHash
+        )
+        return openDatabase(name, roomOpenHelper)
+    }
+
+    /**
+     * Returns a list of [Migration] of a database that has been generated using
+     * [androidx.room.AutoMigration].
+     */
+    private fun getAutoMigrations(userProvidedSpecs: List<AutoMigrationSpec>): List<Migration> {
+        if (databaseClass == null) {
+            return if (userProvidedSpecs.isEmpty()) {
+                // TODO: Detect that there are auto migrations to test when a deprecated
+                //  constructor is used.
+                Log.e(
+                    TAG, "If you have any AutoMigrations in your implementation, you must use " +
+                        "a non-deprecated MigrationTestHelper constructor to provide the " +
+                        "Database class in order to test them. If you do not have any " +
+                        "AutoMigrations to test, you may ignore this warning."
+                )
+                mutableListOf()
+            } else {
+                error(
+                    "You must provide the database class in the " +
+                        "MigrationTestHelper constructor in order to test auto migrations."
+                )
+            }
+        }
+        val db: RoomDatabase = getGeneratedImplementation(
+            databaseClass, "_Impl"
+        )
+        val requiredAutoMigrationSpecs = db.getRequiredAutoMigrationSpecs()
+        return db.getAutoMigrations(
+            createAutoMigrationSpecMap(requiredAutoMigrationSpecs, userProvidedSpecs)
+        )
+    }
+
+    /**
+     * Maps auto migration spec classes to their provided instance.
+     */
+    private fun createAutoMigrationSpecMap(
+        requiredAutoMigrationSpecs: Set<Class<out AutoMigrationSpec>>,
+        userProvidedSpecs: List<AutoMigrationSpec>
+    ): Map<Class<out AutoMigrationSpec>, AutoMigrationSpec> {
+        if (requiredAutoMigrationSpecs.isEmpty()) {
+            return emptyMap()
+        }
+        return buildMap {
+            requiredAutoMigrationSpecs.forEach { spec ->
+                val match = userProvidedSpecs.firstOrNull { provided ->
+                    spec.isAssignableFrom(provided.javaClass)
+                }
+                require(match != null) {
+                    "A required auto migration spec (${spec.canonicalName}) has not been provided."
+                }
+                put(spec, match)
+            }
+        }
+    }
+
+    private fun openDatabase(name: String, roomOpenHelper: RoomOpenHelper): SupportSQLiteDatabase {
+        val config = SupportSQLiteOpenHelper.Configuration.builder(instrumentation.targetContext)
+            .callback(roomOpenHelper)
+            .name(name)
+            .build()
+        val db = openFactory.create(config).writableDatabase
+        managedDatabases.add(WeakReference(db))
+        return db
+    }
+
+    override fun finished(description: Description?) {
+        super.finished(description)
+        managedDatabases.forEach { dbRef ->
+            val db = dbRef.get()
+            if (db != null && db.isOpen) {
+                try {
+                    db.close()
+                } catch (ignored: Throwable) {
+                }
+            }
+        }
+        managedRoomDatabases.forEach { dbRef ->
+            val roomDatabase = dbRef.get()
+            roomDatabase?.close()
+        }
+    }
+
+    /**
+     * Registers a database connection to be automatically closed when the test finishes.
+     *
+     * This only works if `MigrationTestHelper` is registered as a Junit test rule via
+     * [Rule][org.junit.Rule] annotation.
+     *
+     * @param db The database connection that should be closed after the test finishes.
+     */
+    open fun closeWhenFinished(db: SupportSQLiteDatabase) {
+        check(testStarted) {
+            "You cannot register a database to be closed before" +
+                " the test starts. Maybe you forgot to annotate MigrationTestHelper as a" +
+                " test rule? (@Rule)"
+        }
+        managedDatabases.add(WeakReference(db))
+    }
+
+    /**
+     * Registers a database connection to be automatically closed when the test finishes.
+     *
+     * This only works if `MigrationTestHelper` is registered as a Junit test rule via
+     * [Rule][org.junit.Rule] annotation.
+     *
+     * @param db The RoomDatabase instance which holds the database.
+     */
+    open fun closeWhenFinished(db: RoomDatabase) {
+        check(testStarted) {
+            "You cannot register a database to be closed before" +
+                " the test starts. Maybe you forgot to annotate MigrationTestHelper as a" +
+                " test rule? (@Rule)"
+        }
+        managedRoomDatabases.add(WeakReference(db))
+    }
+
+    private fun loadSchema(version: Int): SchemaBundle {
+        return try {
+            loadSchema(instrumentation.context, version)
+        } catch (testAssetsIOExceptions: FileNotFoundException) {
+            Log.w(
+                TAG, "Could not find the schema file in the test assets. Checking the" +
+                    " application assets"
+            )
+            try {
+                loadSchema(instrumentation.targetContext, version)
+            } catch (appAssetsException: FileNotFoundException) {
+                // throw the test assets exception instead
+                throw FileNotFoundException(
+                    "Cannot find the schema file in the assets folder. " +
+                        "Make sure to include the exported json schemas in your test assert " +
+                        "inputs. See " +
+                        "https://developer.android.com/training/data-storage/room/" +
+                        "migrating-db-versions#export-schema for details. Missing file: " +
+                        testAssetsIOExceptions.message
+                )
+            }
+        }
+    }
+
+    private fun loadSchema(context: Context, version: Int): SchemaBundle {
+        val input = context.assets.open("$assetsFolder/$version.json")
+        return deserialize(input)
+    }
+
+    internal class MigratingDelegate(
+        databaseBundle: DatabaseBundle,
+        private val mVerifyDroppedTables: Boolean
+    ) : RoomOpenHelperDelegate(databaseBundle) {
+        override fun createAllTables(database: SupportSQLiteDatabase) {
+            throw UnsupportedOperationException(
+                "Was expecting to migrate but received create." +
+                    "Make sure you have created the database first."
+            )
+        }
+
+        override fun onValidateSchema(
+            db: SupportSQLiteDatabase
+        ): RoomOpenHelper.ValidationResult {
+            val tables = mDatabaseBundle.entitiesByTableName
+            tables.values.forEach { entity ->
+                if (entity is FtsEntityBundle) {
+                    val expected = toFtsTableInfo(entity)
+                    val found = FtsTableInfo.read(db, entity.tableName)
+                    if (expected != found) {
+                        return RoomOpenHelper.ValidationResult(
+                            false,
+                            """
+                                ${expected.name}
+                                Expected: $expected
+                                Found: $found
+                            """.trimIndent()
+                        )
+                    }
+                } else {
+                    val expected = toTableInfo(entity)
+                    val found = TableInfo.read(db, entity.tableName)
+                    if (expected != found) {
+                        return RoomOpenHelper.ValidationResult(
+                            false,
+                            """
+                                ${expected.name}
+                                Expected: $expected
+                                found: $found
+                            """.trimIndent()
+                        )
+                    }
+                }
+            }
+            mDatabaseBundle.views.forEach { view ->
+                val expected = toViewInfo(view)
+                val found = ViewInfo.read(db, view.viewName)
+                if (expected != found) {
+                    return RoomOpenHelper.ValidationResult(
+                        false,
+                            """
+                                ${expected.name}
+                                Expected: $expected
+                                Found: $found
+                            """.trimIndent()
+                    )
+                }
+            }
+            if (mVerifyDroppedTables) {
+                // now ensure tables that should be removed are removed.
+                val expectedTables = buildSet {
+                    tables.values.forEach { entity ->
+                        add(entity.tableName)
+                        if (entity is FtsEntityBundle) {
+                            addAll(entity.shadowTableNames)
+                        }
+                    }
+                }
+                db.query(
+                    "SELECT name FROM sqlite_master WHERE type='table'" +
+                        " AND name NOT IN(?, ?, ?)",
+                    arrayOf(
+                        Room.MASTER_TABLE_NAME, "android_metadata",
+                        "sqlite_sequence"
+                    )
+                ).useCursor { cursor ->
+                    while (cursor.moveToNext()) {
+                        val tableName = cursor.getString(0)
+                        if (!expectedTables.contains(tableName)) {
+                            return RoomOpenHelper.ValidationResult(
+                                false, "Unexpected table $tableName"
+                            )
+                        }
+                    }
+                }
+            }
+            return RoomOpenHelper.ValidationResult(true, null)
+        }
+    }
+
+    internal class CreatingDelegate(
+        databaseBundle: DatabaseBundle
+    ) : RoomOpenHelperDelegate(databaseBundle) {
+        override fun createAllTables(database: SupportSQLiteDatabase) {
+            mDatabaseBundle.buildCreateQueries().forEach { query ->
+                database.execSQL(query)
+            }
+        }
+
+        override fun onValidateSchema(
+            db: SupportSQLiteDatabase
+        ): RoomOpenHelper.ValidationResult {
+            throw UnsupportedOperationException(
+                "This open helper just creates the database but it received a migration request."
+            )
+        }
+    }
+
+    internal abstract class RoomOpenHelperDelegate(
+        val mDatabaseBundle: DatabaseBundle
+    ) : RoomOpenHelper.Delegate(
+            mDatabaseBundle.version
+        ) {
+        override fun dropAllTables(database: SupportSQLiteDatabase) {
+            throw UnsupportedOperationException("cannot drop all tables in the test")
+        }
+
+        override fun onCreate(database: SupportSQLiteDatabase) {}
+        override fun onOpen(database: SupportSQLiteDatabase) {}
+    }
+
+    internal companion object {
+        private const val TAG = "MigrationTestHelper"
+        @JvmStatic
+        internal fun toTableInfo(entityBundle: EntityBundle): TableInfo {
+            return TableInfo(
+                name = entityBundle.tableName,
+                columns = toColumnMap(entityBundle),
+                foreignKeys = toForeignKeys(entityBundle.foreignKeys),
+                indices = toIndices(entityBundle.indices)
+            )
+        }
+
+        @JvmStatic
+        internal fun toFtsTableInfo(ftsEntityBundle: FtsEntityBundle): FtsTableInfo {
+            return FtsTableInfo(
+                name = ftsEntityBundle.tableName,
+                columns = toColumnNamesSet(ftsEntityBundle),
+                createSql = ftsEntityBundle.createSql
+            )
+        }
+
+        @JvmStatic
+        internal fun toViewInfo(viewBundle: DatabaseViewBundle): ViewInfo {
+            return ViewInfo(
+                name = viewBundle.viewName,
+                sql = viewBundle.createView()
+            )
+        }
+
+        @JvmStatic
+        internal fun toIndices(indices: List<IndexBundle>?): Set<TableInfo.Index> {
+            if (indices == null) {
+                return emptySet()
+            }
+            val result = indices.map { bundle ->
+                TableInfo.Index(
+                    name = bundle.name,
+                    unique = bundle.isUnique,
+                    columns = bundle.columnNames!!,
+                    orders = bundle.orders!!
+                )
+            }.toSet()
+            return result
+        }
+
+        @JvmStatic
+        internal fun toForeignKeys(
+            bundles: List<ForeignKeyBundle>?
+        ): Set<TableInfo.ForeignKey> {
+            if (bundles == null) {
+                return emptySet()
+            }
+            val result = bundles.map { bundle ->
+                TableInfo.ForeignKey(
+                    referenceTable = bundle.table,
+                    onDelete = bundle.onDelete,
+                    onUpdate = bundle.onUpdate,
+                    columnNames = bundle.columns,
+                    referenceColumnNames = bundle.referencedColumns
+                )
+            }.toSet()
+            return result
+        }
+
+        @JvmStatic
+        internal fun toColumnNamesSet(entity: EntityBundle): Set<String> {
+            val result = entity.fields.map { field ->
+                field.columnName
+            }.toSet()
+            return result
+        }
+
+        @JvmStatic
+        internal fun toColumnMap(entity: EntityBundle): Map<String, TableInfo.Column> {
+            val result: MutableMap<String, TableInfo.Column> = HashMap()
+            entity.fields.associateBy { bundle ->
+                val column = toColumn(entity, bundle)
+                result[column.name] = column
+            }
+            return result
+        }
+
+        @JvmStatic
+        internal fun toColumn(entity: EntityBundle, field: FieldBundle): TableInfo.Column {
+            return TableInfo.Column(
+                name = field.columnName,
+                type = field.affinity,
+                notNull = field.isNonNull,
+                primaryKeyPosition = findPrimaryKeyPosition(entity, field),
+                defaultValue = field.defaultValue,
+                createdFrom = TableInfo.CREATED_FROM_ENTITY
+            )
+        }
+
+        @JvmStatic
+        internal fun findPrimaryKeyPosition(entity: EntityBundle, field: FieldBundle): Int {
+            return entity.primaryKey.columnNames.indexOfFirst { columnName ->
+                field.columnName.equals(columnName, ignoreCase = true)
+            } + 1 // Shift by 1 to get primary key position
+        }
+    }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index fbc4a7c..331f5c6 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -384,6 +384,7 @@
 includeProject(":benchmark:benchmark-darwin", [BuildType.KMP])
 includeProject(":benchmark:benchmark-darwin-core", [BuildType.KMP])
 includeProject(":benchmark:benchmark-darwin-samples", [BuildType.KMP])
+includeProject(":benchmark:benchmark-darwin-gradle-plugin", [BuildType.KMP])
 includeProject(":benchmark:benchmark-gradle-plugin", "benchmark/gradle-plugin", [BuildType.MAIN])
 includeProject(":benchmark:benchmark-junit4")
 includeProject(":benchmark:benchmark-macro", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt
index a52636c..00257a1 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt
@@ -114,7 +114,7 @@
     override val isExecPerConnectionSQLSupported: Boolean
         get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
 
-    override fun execPerConnectionSQL(sql: String, bindArgs: Array<Any?>?) {
+    override fun execPerConnectionSQL(sql: String, bindArgs: Array<out Any?>?) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             Api30Impl.execPerConnectionSQL(delegate, sql, bindArgs)
         } else {
@@ -135,7 +135,7 @@
         return query(SimpleSQLiteQuery(query))
     }
 
-    override fun query(query: String, bindArgs: Array<Any?>): Cursor {
+    override fun query(query: String, bindArgs: Array<out Any?>): Cursor {
         return query(SimpleSQLiteQuery(query, bindArgs))
     }
 
@@ -182,7 +182,7 @@
         return delegate.insertWithOnConflict(table, null, values, conflictAlgorithm)
     }
 
-    override fun delete(table: String, whereClause: String?, whereArgs: Array<Any?>?): Int {
+    override fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int {
         val query = buildString {
             append("DELETE FROM ")
             append(table)
@@ -201,7 +201,7 @@
         conflictAlgorithm: Int,
         values: ContentValues,
         whereClause: String?,
-        whereArgs: Array<Any?>?
+        whereArgs: Array<out Any?>?
     ): Int {
         // taken from SQLiteDatabase class.
         require(values.size() != 0) { "Empty values" }
@@ -247,7 +247,7 @@
     }
 
     @Throws(SQLException::class)
-    override fun execSQL(sql: String, bindArgs: Array<Any?>) {
+    override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
         delegate.execSQL(sql, bindArgs)
     }
 
@@ -313,7 +313,7 @@
         fun execPerConnectionSQL(
             sQLiteDatabase: SQLiteDatabase,
             sql: String,
-            bindArgs: Array<Any?>?
+            bindArgs: Array<out Any?>?
         ) {
             sQLiteDatabase.execPerConnectionSQL(sql, bindArgs)
         }
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.kt b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.kt
index 2e6a9d9..e3245e2 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.kt
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SimpleSQLiteQuery.kt
@@ -29,7 +29,7 @@
 class SimpleSQLiteQuery(
     private val query: String,
     @Suppress("ArrayReturn") // Due to legacy API
-    private val bindArgs: Array<Any?>?
+    private val bindArgs: Array<out Any?>?
     ) : SupportSQLiteQuery {
 
     /**
@@ -66,7 +66,7 @@
         fun bind(
             statement: SupportSQLiteProgram,
             @Suppress("ArrayReturn") // Due to legacy API
-            bindArgs: Array<Any?>?
+            bindArgs: Array<out Any?>?
         ) {
             if (bindArgs == null) {
                 return
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteCompat.kt b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteCompat.kt
index c0d7fd8..553c19b 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteCompat.kt
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteCompat.kt
@@ -106,7 +106,7 @@
         fun rawQueryWithFactory(
             sQLiteDatabase: SQLiteDatabase,
             sql: String,
-            selectionArgs: Array<String?>,
+            selectionArgs: Array<out String?>,
             editTable: String?,
             cancellationSignal: CancellationSignal,
             cursorFactory: CursorFactory
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt
index dc474eb..bd253f2cc 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt
@@ -234,7 +234,7 @@
     @Suppress("AcronymName") // To keep consistency with framework method name.
     fun execPerConnectionSQL(
         sql: String,
-        @SuppressLint("ArrayReturn") bindArgs: Array<Any?>?
+        @SuppressLint("ArrayReturn") bindArgs: Array<out Any?>?
     ) {
         throw UnsupportedOperationException()
     }
@@ -288,7 +288,7 @@
      * @return A [Cursor] object, which is positioned before the first entry. Note that
      * [Cursor]s are not synchronized, see the documentation for more details.
      */
-    fun query(query: String, bindArgs: Array<Any?>): Cursor
+    fun query(query: String, bindArgs: Array<out Any?>): Cursor
 
     /**
      * Runs the given query on the database.
@@ -354,7 +354,7 @@
      * otherwise. To remove all rows and get a count pass "1" as the
      * whereClause.
      */
-    fun delete(table: String, whereClause: String?, whereArgs: Array<Any?>?): Int
+    fun delete(table: String, whereClause: String?, whereArgs: Array<out Any?>?): Int
 
     /**
      * Convenience method for updating rows in the database.
@@ -381,7 +381,7 @@
         conflictAlgorithm: Int,
         values: ContentValues,
         whereClause: String?,
-        whereArgs: Array<Any?>?
+        whereArgs: Array<out Any?>?
     ): Int
 
     /**
@@ -413,7 +413,7 @@
      * @throws SQLException if the SQL string is invalid
      */
     @Throws(SQLException::class)
-    fun execSQL(sql: String, bindArgs: Array<Any?>)
+    fun execSQL(sql: String, bindArgs: Array<out Any?>)
 
     /**
      * Is true if the database is opened as read only.
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.kt b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.kt
index 044a86f..ed54ec4 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.kt
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteQueryBuilder.kt
@@ -22,9 +22,9 @@
  */
 class SupportSQLiteQueryBuilder private constructor(private val table: String) {
     private var distinct = false
-    private var columns: Array<String>? = null
+    private var columns: Array<out String>? = null
     private var selection: String? = null
-    private var bindArgs: Array<Any?>? = null
+    private var bindArgs: Array<out Any?>? = null
     private var groupBy: String? = null
     private var having: String? = null
     private var orderBy: String? = null
@@ -46,7 +46,7 @@
      *
      * @return this
      */
-    fun columns(columns: Array<String>?): SupportSQLiteQueryBuilder = apply {
+    fun columns(columns: Array<out String>?): SupportSQLiteQueryBuilder = apply {
         this.columns = columns
     }
 
@@ -58,7 +58,10 @@
      *
      * @return this
      */
-    fun selection(selection: String?, bindArgs: Array<Any?>?): SupportSQLiteQueryBuilder = apply {
+    fun selection(
+        selection: String?,
+        bindArgs: Array<out Any?>?
+    ): SupportSQLiteQueryBuilder = apply {
         this.selection = selection
         this.bindArgs = bindArgs
     }
@@ -153,7 +156,7 @@
      * Add the names that are non-null in columns to string, separating
      * them with commas.
      */
-    private fun StringBuilder.appendColumns(columns: Array<String>) {
+    private fun StringBuilder.appendColumns(columns: Array<out String>) {
         val n = columns.size
         for (i in 0 until n) {
             val column = columns[i]
diff --git a/sqlite/sqlite/src/test/java/androidx/sqlite/db/SupportSQLiteQueryBuilderTest.kt b/sqlite/sqlite/src/test/java/androidx/sqlite/db/SupportSQLiteQueryBuilderTest.kt
index cd0e65e..9d9a173 100644
--- a/sqlite/sqlite/src/test/java/androidx/sqlite/db/SupportSQLiteQueryBuilderTest.kt
+++ b/sqlite/sqlite/src/test/java/androidx/sqlite/db/SupportSQLiteQueryBuilderTest.kt
@@ -51,4 +51,17 @@
             .create()
         assertThat(query.sql).isEqualTo("SELECT * FROM Books GROUP BY pages HAVING >100")
     }
+
+    @Test
+    fun subtypes_in_array_selection_does_not_throw_error() {
+        val bindArgs: Array<String?> = arrayOf("USA")
+
+        val query = SupportSQLiteQueryBuilder.builder("Books")
+            .columns(arrayOf("country_published"))
+            .selection("country_published=USA", bindArgs)
+            .create()
+        assertThat(query.sql).isEqualTo(
+            "SELECT country_published FROM Books WHERE country_published=USA"
+        )
+    }
 }
\ No newline at end of file
diff --git a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
index 4f035e8..8f00efe 100644
--- a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
@@ -60,7 +60,6 @@
   public enum ComplicationType {
     method public static androidx.wear.watchface.complications.data.ComplicationType valueOf(String name) throws java.lang.IllegalArgumentException;
     method public static androidx.wear.watchface.complications.data.ComplicationType[] values();
-    enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType DISCRETE_RANGED_VALUE;
     enum_constant public static final androidx.wear.watchface.complications.data.ComplicationType EMPTY;
     enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType GOAL_PROGRESS;
     enum_constant @androidx.wear.watchface.complications.data.ComplicationExperimental public static final androidx.wear.watchface.complications.data.ComplicationType LIST;
@@ -92,41 +91,6 @@
   public final class DataKt {
   }
 
-  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class DiscreteRangedValueComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
-    method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
-    method public int getMax();
-    method public int getMin();
-    method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
-    method public androidx.wear.watchface.complications.data.SmallImage? getSmallImage();
-    method public androidx.wear.watchface.complications.data.ComplicationText? getText();
-    method public androidx.wear.watchface.complications.data.ComplicationText? getTitle();
-    method public int getValue();
-    property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
-    property public final int max;
-    property public final int min;
-    property public final androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage;
-    property public final androidx.wear.watchface.complications.data.SmallImage? smallImage;
-    property public final androidx.wear.watchface.complications.data.ComplicationText? text;
-    property public final androidx.wear.watchface.complications.data.ComplicationText? title;
-    property public final int value;
-    field public static final int PLACEHOLDER;
-    field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
-  }
-
-  public static final class DiscreteRangedValueComplicationData.Builder {
-    ctor public DiscreteRangedValueComplicationData.Builder(int value, int min, int max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData build();
-    method public final T setDataSource(android.content.ComponentName? dataSource);
-    method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final T setDisplayPolicy(int displayPolicy);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage);
-    method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final T setPersistencePolicy(int persistencePolicy);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData.Builder setTapAction(android.app.PendingIntent? tapAction);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title);
-    method public androidx.wear.watchface.complications.data.DiscreteRangedValueComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
-  }
-
   public final class EmptyComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     ctor public EmptyComplicationData();
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
@@ -361,6 +325,7 @@
     method public androidx.wear.watchface.complications.data.ComplicationText? getText();
     method public androidx.wear.watchface.complications.data.ComplicationText? getTitle();
     method public float getValue();
+    method @androidx.wear.watchface.complications.data.ComplicationExperimental public int getValueType();
     property @androidx.wear.watchface.complications.data.ComplicationExperimental public final androidx.wear.watchface.complications.data.ColorRamp? colorRamp;
     property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
     property public final float max;
@@ -370,6 +335,7 @@
     property public final androidx.wear.watchface.complications.data.ComplicationText? text;
     property public final androidx.wear.watchface.complications.data.ComplicationText? title;
     property public final float value;
+    property @androidx.wear.watchface.complications.data.ComplicationExperimental public final int valueType;
     field public static final float PLACEHOLDER;
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
   }
@@ -387,6 +353,13 @@
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setText(androidx.wear.watchface.complications.data.ComplicationText? text);
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setTitle(androidx.wear.watchface.complications.data.ComplicationText? title);
     method public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
+    method @androidx.wear.watchface.complications.data.ComplicationExperimental public androidx.wear.watchface.complications.data.RangedValueComplicationData.Builder setValueType(int valueType);
+  }
+
+  @androidx.wear.watchface.complications.data.ComplicationExperimental public final class RangedValueTypes {
+    field public static final androidx.wear.watchface.complications.data.RangedValueTypes INSTANCE;
+    field public static final int SCORE = 1; // 0x1
+    field public static final int UNDEFINED = 0; // 0x0
   }
 
   public final class ShortTextComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
@@ -535,17 +508,21 @@
 
   @androidx.wear.watchface.complications.data.ComplicationExperimental public final class WeightedElementsComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
+    method public int getElementBackgroundColor();
     method public java.util.List<androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Element> getElements();
     method public androidx.wear.watchface.complications.data.MonochromaticImage? getMonochromaticImage();
     method public androidx.wear.watchface.complications.data.SmallImage? getSmallImage();
     method public androidx.wear.watchface.complications.data.ComplicationText? getText();
     method public androidx.wear.watchface.complications.data.ComplicationText? getTitle();
     property public final androidx.wear.watchface.complications.data.ComplicationText? contentDescription;
+    property public final int elementBackgroundColor;
     property public final java.util.List<androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Element> elements;
     property public final androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage;
     property public final androidx.wear.watchface.complications.data.SmallImage? smallImage;
     property public final androidx.wear.watchface.complications.data.ComplicationText? text;
     property public final androidx.wear.watchface.complications.data.ComplicationText? title;
+    field public static final androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Companion Companion;
+    field public static final int MAX_NUM_ELEMENTS = 20; // 0x14
     field public static final java.util.List<androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Element> PLACEHOLDER;
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
   }
@@ -555,6 +532,7 @@
     method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData build();
     method public final T setDataSource(android.content.ComponentName? dataSource);
     method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final T setDisplayPolicy(int displayPolicy);
+    method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setElementBackgroundColor(@ColorInt int elementBackgroundColor);
     method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setMonochromaticImage(androidx.wear.watchface.complications.data.MonochromaticImage? monochromaticImage);
     method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final T setPersistencePolicy(int persistencePolicy);
     method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setSmallImage(androidx.wear.watchface.complications.data.SmallImage? smallImage);
@@ -564,6 +542,9 @@
     method public androidx.wear.watchface.complications.data.WeightedElementsComplicationData.Builder setValidTimeRange(androidx.wear.watchface.complications.data.TimeRange? validTimeRange);
   }
 
+  public static final class WeightedElementsComplicationData.Companion {
+  }
+
   public static final class WeightedElementsComplicationData.Element {
     ctor public WeightedElementsComplicationData.Element(@FloatRange(from=0.0, fromInclusive=false) float weight, @ColorInt int color);
     method public int getColor();
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
index 5f2fa3d..65924c0 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.java
@@ -246,20 +246,6 @@
     public static final int TYPE_GOAL_PROGRESS = 13;
 
     /**
-     * Type used for complications including a discrete integer value within a range, such as a
-     * 3/6 daily cups of water drunk. The value may be accompanied by an icon and/or short text
-     * and title.
-     *
-     * <p>The <i>value</i>, <i>min value</i>, and <i>max value</i> fields are required for this
-     * type, and the value within the range is expected to always be displayed.
-     *
-     * <p>The <i>icon</i> (and <i>burnInProtectionIcon</i>), <i>short title</i>, and <i>short
-     * text</i> fields are optional for this type, but at least one must be defined. The watch face
-     * may choose which of these fields to display, if any.
-     */
-    public static final int TYPE_DISCRETE_RANGED_VALUE = 14;
-
-    /**
      * Type used for complications to display a series of weighted values e.g. in a pie chart. The
      * weighted values may be accompanied by an icon and/or short text and title.
      *
@@ -270,7 +256,7 @@
      * text</i> fields are optional for this type, but at least one must be defined. The watch face
      * may choose which of these fields to display, if any.
      */
-    public static final int TYPE_WEIGHTED_ELEMENTS = 15;
+    public static final int TYPE_WEIGHTED_ELEMENTS = 14;
 
     /** @hide */
     @IntDef({IMAGE_STYLE_PHOTO, IMAGE_STYLE_ICON})
@@ -300,15 +286,13 @@
     private static final String FIELD_COLOR_RAMP_INTERPOLATED = "COLOR_RAMP_INTERPOLATED";
     private static final String FIELD_DATA_SOURCE = "FIELD_DATA_SOURCE";
     private static final String FIELD_DISPLAY_POLICY = "DISPLAY_POLICY";
+    private static final String FIELD_ELEMENT_BACKGROUND_COLOR = "ELEMENT_BACKGROUND_COLOR";
     private static final String FIELD_ELEMENT_COLORS = "ELEMENT_COLORS";
     private static final String FIELD_ELEMENT_WEIGHTS = "ELEMENT_WEIGHTS";
     private static final String FIELD_END_TIME = "END_TIME";
     private static final String FIELD_ICON = "ICON";
     private static final String FIELD_ICON_BURN_IN_PROTECTION = "ICON_BURN_IN_PROTECTION";
     private static final String FIELD_IMAGE_STYLE = "IMAGE_STYLE";
-    private static final String FIELD_INT_MAX_VALUE = "INT_MAX_VALUE";
-    private static final String FIELD_INT_MIN_VALUE = "INT_MIN_VALUE";
-    private static final String FIELD_INT_VALUE = "INT_VALUE";
     private static final String FIELD_LARGE_IMAGE = "LARGE_IMAGE";
     private static final String FIELD_LIST_ENTRIES = "LIST_ENTRIES";
     private static final String FIELD_LIST_ENTRY_TYPE = "LIST_ENTRY_TYPE";
@@ -337,6 +321,7 @@
     private static final String FIELD_TIMELINE_ENTRIES = "TIMELINE";
     private static final String FIELD_TIMELINE_ENTRY_TYPE = "TIMELINE_ENTRY_TYPE";
     private static final String FIELD_VALUE = "VALUE";
+    private static final String FIELD_VALUE_TYPE = "VALUE_TYPE";
 
     // Originally it was planned to support both content and image content descriptions.
     private static final String FIELD_CONTENT_DESCRIPTION = "IMAGE_CONTENT_DESCRIPTION";
@@ -362,8 +347,11 @@
             },
             {FIELD_LIST_ENTRIES}, // TYPE_LIST
             {FIELD_VALUE, FIELD_TARGET_VALUE}, // GOAL_PROGRESS
-            {FIELD_INT_VALUE, FIELD_INT_MIN_VALUE, FIELD_INT_MAX_VALUE}, // DISCRETE_RANGED_VALUE
-            {FIELD_ELEMENT_WEIGHTS, FIELD_ELEMENT_COLORS} // TYPE_WEIGHTED_ELEMENTS
+            {  // TYPE_WEIGHTED_ELEMENTS
+                    FIELD_ELEMENT_WEIGHTS,
+                    FIELD_ELEMENT_COLORS,
+                    FIELD_ELEMENT_BACKGROUND_COLOR
+            }
     };
 
     // Used for validation. OPTIONAL_FIELDS[i] is an array containing all the fields which are
@@ -412,7 +400,8 @@
                     FIELD_COLOR_RAMP,
                     FIELD_COLOR_RAMP_INTERPOLATED,
                     FIELD_PERSISTENCE_POLICY,
-                    FIELD_DISPLAY_POLICY
+                    FIELD_DISPLAY_POLICY,
+                    FIELD_VALUE_TYPE
             }, // RANGED_VALUE
             {
                     FIELD_TAP_ACTION,
@@ -468,6 +457,7 @@
                     FIELD_SMALL_IMAGE_BURN_IN_PROTECTION,
                     FIELD_TAP_ACTION,
                     FIELD_VALUE,
+                    FIELD_VALUE_TYPE,
                     FIELD_DATA_SOURCE,
                     FIELD_PERSISTENCE_POLICY,
                     FIELD_DISPLAY_POLICY
@@ -516,20 +506,6 @@
                     FIELD_DATA_SOURCE,
                     FIELD_PERSISTENCE_POLICY,
                     FIELD_DISPLAY_POLICY
-            }, // DISCRETE_RANGED_VALUE
-            {
-                    FIELD_SHORT_TEXT,
-                    FIELD_SHORT_TITLE,
-                    FIELD_ICON,
-                    FIELD_ICON_BURN_IN_PROTECTION,
-                    FIELD_SMALL_IMAGE,
-                    FIELD_SMALL_IMAGE_BURN_IN_PROTECTION,
-                    FIELD_IMAGE_STYLE,
-                    FIELD_TAP_ACTION,
-                    FIELD_CONTENT_DESCRIPTION,
-                    FIELD_DATA_SOURCE,
-                    FIELD_PERSISTENCE_POLICY,
-                    FIELD_DISPLAY_POLICY
             }  // TYPE_WEIGHTED_ELEMENTS
     };
 
@@ -572,7 +548,7 @@
 
     @RequiresApi(api = Build.VERSION_CODES.P)
     private static class SerializedForm implements Serializable {
-        private static final int VERSION_NUMBER = 17;
+        private static final int VERSION_NUMBER = 19;
 
         @NonNull
         ComplicationData mComplicationData;
@@ -630,6 +606,9 @@
             if (isFieldValidForType(FIELD_VALUE, type)) {
                 oos.writeFloat(mComplicationData.getRangedValue());
             }
+            if (isFieldValidForType(FIELD_VALUE_TYPE, type)) {
+                oos.writeInt(mComplicationData.getRangedValueType());
+            }
             if (isFieldValidForType(FIELD_MIN_VALUE, type)) {
                 oos.writeFloat(mComplicationData.getRangedMinValue());
             }
@@ -639,15 +618,6 @@
             if (isFieldValidForType(FIELD_TARGET_VALUE, type)) {
                 oos.writeFloat(mComplicationData.getTargetValue());
             }
-            if (isFieldValidForType(FIELD_INT_VALUE, type)) {
-                oos.writeInt(mComplicationData.getDiscreteRangedValue());
-            }
-            if (isFieldValidForType(FIELD_INT_MIN_VALUE, type)) {
-                oos.writeInt(mComplicationData.getDiscreteRangedMinValue());
-            }
-            if (isFieldValidForType(FIELD_INT_MAX_VALUE, type)) {
-                oos.writeInt(mComplicationData.getDiscreteRangedMaxValue());
-            }
             if (isFieldValidForType(FIELD_COLOR_RAMP, type)) {
                 int[] colors = mComplicationData.getColorRamp();
                 if (colors != null) {
@@ -693,6 +663,9 @@
                     oos.writeBoolean(false);
                 }
             }
+            if (isFieldValidForType(FIELD_ELEMENT_BACKGROUND_COLOR, type)) {
+                oos.writeInt(mComplicationData.getElementBackgroundColor());
+            }
             if (isFieldValidForType(FIELD_START_TIME, type)) {
                 oos.writeLong(mComplicationData.getStartDateTimeMillis());
             }
@@ -835,6 +808,9 @@
             if (isFieldValidForType(FIELD_VALUE, type)) {
                 fields.putFloat(FIELD_VALUE, ois.readFloat());
             }
+            if (isFieldValidForType(FIELD_VALUE_TYPE, type)) {
+                fields.putInt(FIELD_VALUE_TYPE, ois.readInt());
+            }
             if (isFieldValidForType(FIELD_MIN_VALUE, type)) {
                 fields.putFloat(FIELD_MIN_VALUE, ois.readFloat());
             }
@@ -844,15 +820,6 @@
             if (isFieldValidForType(FIELD_TARGET_VALUE, type)) {
                 fields.putFloat(FIELD_TARGET_VALUE, ois.readFloat());
             }
-            if (isFieldValidForType(FIELD_INT_VALUE, type)) {
-                fields.putInt(FIELD_INT_VALUE, ois.readInt());
-            }
-            if (isFieldValidForType(FIELD_INT_MIN_VALUE, type)) {
-                fields.putInt(FIELD_INT_MIN_VALUE, ois.readInt());
-            }
-            if (isFieldValidForType(FIELD_INT_MAX_VALUE, type)) {
-                fields.putInt(FIELD_INT_MAX_VALUE, ois.readInt());
-            }
             if (isFieldValidForType(FIELD_COLOR_RAMP, type) && ois.readBoolean()) {
                 int numColors = ois.readInt();
                 int[] colors = new int[numColors];
@@ -880,6 +847,9 @@
                 }
                 fields.putIntArray(FIELD_ELEMENT_COLORS, colors);
             }
+            if (isFieldValidForType(FIELD_ELEMENT_BACKGROUND_COLOR, type)) {
+                fields.putInt(FIELD_ELEMENT_BACKGROUND_COLOR, ois.readInt());
+            }
             if (isFieldValidForType(FIELD_START_TIME, type)) {
                 fields.putLong(FIELD_START_TIME, ois.readLong());
             }
@@ -1184,6 +1154,29 @@
     }
 
     /**
+     * Returns true if the ComplicationData contains a ranged max type. I.e. if
+     * {@link #getRangedValueType} can succeed.
+     */
+    public boolean hasRangedValueType() {
+        try {
+            return isFieldValidForType(FIELD_VALUE_TYPE, mType);
+        } catch (BadParcelableException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the <i>value</i> field for this complication.
+     *
+     * <p>Valid only if the type of this complication data is {@link #TYPE_RANGED_VALUE}.
+     * Otherwise returns zero.
+     */
+    public int getRangedValueType() {
+        checkFieldValidForTypeWithoutThrowingException(FIELD_VALUE_TYPE, mType);
+        return mFields.getInt(FIELD_VALUE_TYPE);
+    }
+
+    /**
      * Returns true if the ComplicationData contains a ranged max value. I.e. if
      * {@link #getRangedMinValue} can succeed.
      */
@@ -1253,75 +1246,6 @@
     }
 
     /**
-     * Returns true if the ComplicationData contains a discrete integer ranged max value. I.e. if
-     * {@link #getDiscreteRangedValue} can succeed.
-     */
-    public boolean hasDiscreteRangedValue() {
-        try {
-            return isFieldValidForType(FIELD_INT_VALUE, mType);
-        } catch (BadParcelableException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Returns the <i>discrete integer value</i> field for this complication.
-     *
-     * <p>Valid only if the type of this complication data is {@link #TYPE_DISCRETE_RANGED_VALUE}.
-     * Otherwise returns zero.
-     */
-    public int getDiscreteRangedValue() {
-        checkFieldValidForTypeWithoutThrowingException(FIELD_INT_VALUE, mType);
-        return mFields.getInt(FIELD_INT_VALUE);
-    }
-
-    /**
-     * Returns true if the ComplicationData contains a discrete integer ranged max value. I.e. if
-     * {@link #getDiscreteRangedMinValue} can succeed.
-     */
-    public boolean hasDiscreteRangedMinValue() {
-        try {
-            return isFieldValidForType(FIELD_INT_MIN_VALUE, mType);
-        } catch (BadParcelableException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Returns the <i>discrete integer min value</i> field for this complication.
-     *
-     * <p>Valid only if the type of this complication data is {@link #TYPE_DISCRETE_RANGED_VALUE}.
-     * Otherwise returns zero.
-     */
-    public int getDiscreteRangedMinValue() {
-        checkFieldValidForTypeWithoutThrowingException(FIELD_INT_MIN_VALUE, mType);
-        return mFields.getInt(FIELD_INT_MIN_VALUE);
-    }
-
-    /**
-     * Returns true if the ComplicationData contains a discrete integer ranged max value. I.e. if
-     * {@link #getDiscreteRangedMaxValue} can succeed.
-     */
-    public boolean hasDiscreteRangedMaxValue() {
-        try {
-            return isFieldValidForType(FIELD_INT_MAX_VALUE, mType);
-        } catch (BadParcelableException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Returns the <i>discrete integer max value</i> field for this complication.
-     *
-     * <p>Valid only if the type of this complication data is {@link #TYPE_DISCRETE_RANGED_VALUE}.
-     * Otherwise returns zero.
-     */
-    public int getDiscreteRangedMaxValue() {
-        checkFieldValidForTypeWithoutThrowingException(FIELD_INT_MAX_VALUE, mType);
-        return mFields.getInt(FIELD_INT_MAX_VALUE);
-    }
-
-    /**
      * Returns the colors for the progress bar.
      *
      * <p>Valid only if the type of this complication data is {@link #TYPE_RANGED_VALUE} or
@@ -1756,6 +1680,18 @@
     }
 
     /**
+     * Returns the background color to use between elements for this complication.
+     *
+     * <p>Valid only if the type of this complication data is {@link #TYPE_WEIGHTED_ELEMENTS}.
+     * Otherwise returns 0.
+     */
+    @ColorInt
+    public int getElementBackgroundColor() {
+        checkFieldValidForTypeWithoutThrowingException(FIELD_ELEMENT_BACKGROUND_COLOR, mType);
+        return mFields.getInt(FIELD_ELEMENT_BACKGROUND_COLOR);
+    }
+
+    /**
      * Returns the placeholder ComplicationData if there is one or `null`.
      */
     @Nullable
@@ -2032,6 +1968,16 @@
         }
 
         /**
+         * Sets the <i>value type</i> field which provides meta data about the value. This is
+         * optional for the {@link #TYPE_RANGED_VALUE} type.
+         */
+        @NonNull
+        public Builder setRangedValueType(int valueType) {
+            putIntField(FIELD_VALUE_TYPE, valueType);
+            return this;
+        }
+
+        /**
          * Sets the <i>min value</i> field. This is required for the {@link #TYPE_RANGED_VALUE}
          * type, and is not valid for any other type. A {@link #TYPE_RANGED_VALUE} complication
          * visually presents a single value, which is usually a percentage. E.g. you have
@@ -2064,57 +2010,6 @@
         }
 
         /**
-         * Sets the <i>discrete integer value</i> field. This is required for the
-         * {@link #TYPE_DISCRETE_RANGED_VALUE} type, and is not valid for any other type. A
-         * {@link #TYPE_DISCRETE_RANGED_VALUE} complication
-         * visually presents a single value. E.g. you have drunk 3 out of today's target of 6 cups
-         * of water.
-         *
-         * <p>Returns this Builder to allow chaining.
-         *
-         * @throws IllegalStateException if this field is not valid for the complication type
-         */
-        @NonNull
-        public Builder setDiscreteRangedValue(int value) {
-            putIntField(FIELD_INT_VALUE, value);
-            return this;
-        }
-
-        /**
-         * Sets the <i>min discrete integer value</i> field. This is required for the
-         * {@link #TYPE_DISCRETE_RANGED_VALUE} type, and is not valid for any other type. A
-         * {@link #TYPE_DISCRETE_RANGED_VALUE} complication visually presents a single value,
-         * which is usually a percentage. E.g. you have completed 70% of today's target of 10000
-         * steps.
-         *
-         * <p>Returns this Builder to allow chaining.
-         *
-         * @throws IllegalStateException if this field is not valid for the complication type
-         */
-        @NonNull
-        public Builder setDiscreteRangedMinValue(int minValue) {
-            putIntField(FIELD_INT_MIN_VALUE, minValue);
-            return this;
-        }
-
-        /**
-         * Sets the <i>max discrete integer value</i> field. This is required for the
-         * {@link #TYPE_DISCRETE_RANGED_VALUE} type, and is not valid for any other type. A
-         * {@link #TYPE_DISCRETE_RANGED_VALUE} complication visually presents a single value,
-         * which is usually a percentage. E.g. you have completed 70% of today's target of 10000
-         * steps.
-         *
-         * <p>Returns this Builder to allow chaining.
-         *
-         * @throws IllegalStateException if this field is not valid for the complication type
-         */
-        @NonNull
-        public Builder setDiscreteRangedMaxValue(int maxValue) {
-            putIntField(FIELD_INT_MAX_VALUE, maxValue);
-            return this;
-        }
-
-        /**
          * Sets the <i>targetValue</i> field. This is required for the {@link #TYPE_GOAL_PROGRESS}
          * type, and is not valid for any other type. A {@link #TYPE_GOAL_PROGRESS} complication
          * visually presents a single value, which is usually a percentage. E.g. you
@@ -2516,6 +2411,17 @@
         }
 
         /**
+         * Sets the background color to use between elements for this complication.
+         *
+         * <p>Returns this Builder to allow chaining.
+         */
+        @NonNull
+        public Builder setElementBackgroundColor(@ColorInt int elementBackgroundColor) {
+            putOrRemoveField(FIELD_ELEMENT_BACKGROUND_COLOR, elementBackgroundColor);
+            return this;
+        }
+
+        /**
          * Constructs and returns {@link ComplicationData} with the provided fields. All required
          * fields must be populated before this method is called.
          *
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index fb451c7..bdf8831 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent
 import android.content.ComponentName
+import android.graphics.Color
 import android.graphics.drawable.Icon
 import android.os.Build
 import android.util.Log
@@ -276,7 +277,6 @@
             is SmallImageComplicationData -> placeholder.contentDescription
             is PhotoImageComplicationData -> placeholder.contentDescription
             is GoalProgressComplicationData -> placeholder.contentDescription
-            is DiscreteRangedValueComplicationData -> placeholder.contentDescription
             is WeightedElementsComplicationData -> placeholder.contentDescription
             else -> null
         }
@@ -904,6 +904,33 @@
 }
 
 /**
+ * Describes the semantic meaning of a [RangedValueComplicationData] if known. The complication
+ * renderer may wish to visually differentiate between the different types, for example rendering a
+ * dot on a line/arc to show the value for a [SCORE].
+ */
+@ComplicationExperimental
+object RangedValueTypes {
+    /** The ranged value's semantic hasn't been defined, but it's most commonly a percentage. */
+    const val UNDEFINED = 0
+
+    /**
+     * The randed value represents a score for something unrelated to the user, e.g. the air quality
+     * index.
+     */
+    const val SCORE = 1
+}
+
+/** @hide */
+@OptIn(ComplicationExperimental::class)
+@IntDef(
+    value = [
+        RangedValueTypes.UNDEFINED,
+        RangedValueTypes.SCORE
+    ]
+)
+public annotation class RangedValueType
+
+/**
  * Type used for complications including a numerical value within a range, such as a percentage.
  * The value may be accompanied by an icon and/or short text and title.
  *
@@ -922,7 +949,7 @@
  * @property value The [Float] value of this complication which is >= [min] and <= [max] or equal to
  * [PLACEHOLDER]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder rather
  * than rendering normally, its suggested to be drawn as a grey arc with a percentage value selected
- * by the renderer.
+ * by the renderer. The semantic meaning of value is described by [valueType].
  * @property min The minimum [Float] value for this complication.
  * @property max The maximum [Float] value for this complication.
  * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch
@@ -966,6 +993,7 @@
     cachedWireComplicationData: WireComplicationData?,
     dataSource: ComponentName?,
     colorRamp: ColorRamp?,
+    @RangedValueType valueType: Int,
     @ComplicationPersistencePolicy persistencePolicy: Int,
     @ComplicationDisplayPolicy displayPolicy: Int
 ) : ComplicationData(
@@ -983,6 +1011,13 @@
     @ComplicationExperimental
     val colorRamp: ColorRamp? = colorRamp
 
+    /** The semantic meaning of [value], see [RangedValueType] for more details. */
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ComplicationExperimental
+    @ComplicationExperimental
+    @RangedValueType
+    val valueType: Int = valueType
+
     /**
      * Builder for [RangedValueComplicationData].
      *
@@ -990,7 +1025,7 @@
      * least one of [monochromaticImage], [smallImage], [text] or [title].
      *
      * @param value The value of the ranged complication which should be in the range
-     * [[min]] .. [[max]]
+     * [[min]] .. [[max]]. The semantic meaning of value can be specified via [setValueType].
      * @param min The minimum value
      * @param max The maximum value. This must be less than [Float.MAX_VALUE].
      * @param contentDescription Localized description for use by screen readers
@@ -1010,6 +1045,9 @@
         private var text: ComplicationText? = null
         @OptIn(ComplicationExperimental::class)
         private var colorRamp: ColorRamp? = null
+        @OptIn(ComplicationExperimental::class)
+        @RangedValueType
+        private var valueType: Int = RangedValueTypes.UNDEFINED
 
         init {
             require(max != Float.MAX_VALUE) {
@@ -1057,6 +1095,15 @@
             this.colorRamp = colorRamp
         }
 
+        /**
+         * Sets the semantic meaning of [value], see [@RangedValueTypes] for detail.s Defaults to
+         * [RangedValueTypes.UNDEFINED] if not set.
+         */
+        @ComplicationExperimental
+        public fun setValueType(@RangedValueType valueType: Int): Builder = apply {
+            this.valueType = valueType
+        }
+
         /** Builds the [RangedValueComplicationData]. */
         @OptIn(ComplicationExperimental::class)
         public override fun build(): RangedValueComplicationData {
@@ -1079,6 +1126,7 @@
                 cachedWireComplicationData,
                 dataSource,
                 colorRamp,
+                valueType,
                 persistencePolicy,
                 displayPolicy
             )
@@ -1118,6 +1166,7 @@
             builder.setColorRamp(it.colors)
             builder.setColorRampIsSmoothShaded(it.interpolated)
         }
+        builder.setRangedValueType(valueType)
     }
 
     @OptIn(ComplicationExperimental::class)
@@ -1128,6 +1177,7 @@
         other as RangedValueComplicationData
 
         if (value != other.value) return false
+        if (valueType != other.valueType) return false
         if (min != other.min) return false
         if (max != other.max) return false
         if (monochromaticImage != other.monochromaticImage) return false
@@ -1149,6 +1199,7 @@
     @OptIn(ComplicationExperimental::class)
     override fun hashCode(): Int {
         var result = value.hashCode()
+        result = 31 * result + valueType
         result = 31 * result + min.hashCode()
         result = 31 * result + max.hashCode()
         result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
@@ -1173,9 +1224,9 @@
         } else {
             value.toString()
         }
-        return "RangedValueComplicationData(value=$valueString, min=$min, max=$max, " +
-            "monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
-            "text=$text, contentDescription=$contentDescription), " +
+        return "RangedValueComplicationData(value=$valueString, valueType=$valueType, min=$min, " +
+            "max=$max, monochromaticImage=$monochromaticImage, smallImage=$smallImage, " +
+            "title=$title, text=$text, contentDescription=$contentDescription), " +
             "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
             "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " +
             "colorRamp=$colorRamp, persistencePolicy=$persistencePolicy, " +
@@ -1233,6 +1284,10 @@
  * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to
  * specify both a [monochromaticImage] and a [smallImage].
  *
+ * If you want to represent a score for something that's not based on the user (e.g. air quality
+ * index) then you should use a [RangedValueComplicationData] and pass [RangedValueTypes.SCORE] into
+ * [RangedValueComplicationData.Builder.setValueType].
+ *
  * @property value The [Float] value of this complication which is >= 0f, this value may be larger
  * than [targetValue]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder
  * rather than rendering normally, its suggested to be drawn as a grey arc with a percentage value
@@ -1389,7 +1444,7 @@
                 dataSource,
                 colorRamp,
                 persistencePolicy,
-                displayPolicy
+                displayPolicy,
             )
         }
     }
@@ -1540,8 +1595,11 @@
  *
  * @property elements The breakdown of the subject into various [Element]s. E.g. the proportion of
  * calories consumed which were carbohydrates, fats etc... If this is equal to [PLACEHOLDER] then
- * the renderer must display this in a visiually distinct way to suggest to the user that it's
- * placeholder data.  E.g. each rendered is rendered in light grey.
+ * the renderer must display this in a visually distinct way to suggest to the user that it's
+ * placeholder data.  E.g. each rendered is rendered in light grey. The maximum valid size of this
+ * list is [MAX_NUM_ELEMENTS].
+ * @property elementBackgroundColor If elements are draw as segments then this is the background
+ * color to use in between them.
  * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch
  * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must
  * treat it as a placeholder rather than rendering normally, its suggested it should be rendered as
@@ -1572,6 +1630,7 @@
 public class WeightedElementsComplicationData
 internal constructor(
     public val elements: List<Element>,
+    @ColorInt public val elementBackgroundColor: Int,
     public val monochromaticImage: MonochromaticImage?,
     public val smallImage: SmallImage?,
     public val title: ComplicationText?,
@@ -1642,7 +1701,8 @@
      *
      * @param elements The breakdown of the subject into various [Element]s. E.g. the proportion of
      * calories consumed which were carbohydrates, fats etc... The [tapAction] must take the user to
-     * an experience where the color key becomes obvious.
+     * an experience where the color key becomes obvious. The maximum valid size of this list is
+     * [MAX_NUM_ELEMENTS].
      * @param contentDescription Localized description for use by screen readers
      */
     @OptIn(ComplicationExperimental::class)
@@ -1650,6 +1710,7 @@
         private val elements: List<Element>,
         private var contentDescription: ComplicationText
     ) : BaseBuilder<Builder, WeightedElementsComplicationData>() {
+        @ColorInt private var elementBackgroundColor: Int = Color.TRANSPARENT
         private var tapAction: PendingIntent? = null
         private var validTimeRange: TimeRange? = null
         private var monochromaticImage: MonochromaticImage? = null
@@ -1657,6 +1718,22 @@
         private var title: ComplicationText? = null
         private var text: ComplicationText? = null
 
+        init {
+            require(elements.size < MAX_NUM_ELEMENTS) {
+                "Found ${elements.size} elements but the maximum is $MAX_NUM_ELEMENTS"
+            }
+        }
+
+        /**
+         * Sets the background color to use between the [elements] if they are drawn segmented.
+         * Defaults to [Color.TRANSPARENT] if not set.
+         */
+        public fun setElementBackgroundColor(
+            @ColorInt elementBackgroundColor: Int
+        ): Builder = apply {
+            this.elementBackgroundColor = elementBackgroundColor
+        }
+
         /** Sets optional pending intent to be invoked when the complication is tapped. */
         public fun setTapAction(tapAction: PendingIntent?): Builder = apply {
             this.tapAction = tapAction
@@ -1698,6 +1775,7 @@
             }
             return WeightedElementsComplicationData(
                 elements,
+                elementBackgroundColor,
                 monochromaticImage,
                 smallImage,
                 title,
@@ -1728,6 +1806,7 @@
     override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
         builder.setElementWeights(elements.map { it.weight }.toFloatArray())
         builder.setElementColors(elements.map { it.color }.toIntArray())
+        builder.setElementBackgroundColor(elementBackgroundColor)
         monochromaticImage?.addToWireComplicationData(builder)
         smallImage?.addToWireComplicationData(builder)
         builder.setShortText(text?.toWireComplicationText())
@@ -1761,6 +1840,7 @@
             elements.joinToString()
         }
         return "WeightedElementsComplicationData(elements=$elementsString, " +
+            "elementBackgroundColor=$elementBackgroundColor, " +
             "monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
             "text=$text, contentDescription=$contentDescription), " +
             "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
@@ -1779,6 +1859,7 @@
         other as WeightedElementsComplicationData
 
         if (elements != other.elements) return false
+        if (elementBackgroundColor != other.elementBackgroundColor) return false
         if (monochromaticImage != other.monochromaticImage) return false
         if (smallImage != other.smallImage) return false
         if (title != other.title) return false
@@ -1792,6 +1873,7 @@
 
     override fun hashCode(): Int {
         var result = elements.hashCode()
+        result = 31 * result + elementBackgroundColor
         result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
         result = 31 * result + (smallImage?.hashCode() ?: 0)
         result = 31 * result + (title?.hashCode() ?: 0)
@@ -1804,7 +1886,6 @@
         return result
     }
 
-    /** @hide */
     public companion object {
         /** The [ComplicationType] corresponding to objects of this type. */
         @OptIn(ComplicationExperimental::class)
@@ -1821,294 +1902,12 @@
          */
         @JvmField
         public val PLACEHOLDER = emptyList<Element>()
-    }
-}
-
-/**
- * Type used for complications including a discrete integer value within a range. E.g. 3 out of 6
- * daily cups of water drunk. The value may be accompanied by an icon and/or short text and title.
- *
- * The [value], [min], and [max] fields are required for this type and the value within the
- * range is expected to always be displayed.
- *
- * The icon, title, and text fields are optional and the watch face may choose which of these
- * fields to display, if any.
- *
- * Unlike [RangedValueComplicationData], DiscreteRangedValueComplicationData doesn't specify a color
- * ramp, this is because the ranged value is expected to be rendered using solid colored segments
- * with watch face selected colors.
- *
- * If a [monochromaticImage] and a [smallImage] are both specified then only one should be
- * displayed. If the complication is drawn with a single color it's recommended to choose
- * [monochromaticImage] and apply a tint. If the complication is rendered with multiple colors it's
- * recommended to choose the [smallImage]. It's best practice for a ComplicationDataSource to
- * specify both a [monochromaticImage] and a [smallImage].
- *
- * @property value The [Int] value of this complication which is >= [min] and <= [max] or equal to
- * [PLACEHOLDER]. If it's equal to [PLACEHOLDER] the renderer must treat it as a placeholder rather
- * than rendering normally, its suggested to be drawn as a grey arc with a percentage value selected
- * by the renderer.
- * @property min The minimum [Int] value for this complication.
- * @property max The maximum [Int] value for this complication.
- * @property monochromaticImage A simple [MonochromaticImage] image that can be tinted by the watch
- * face. If the monochromaticImage is equal to [MonochromaticImage.PLACEHOLDER] the renderer must
- * treat it as a placeholder rather than rendering normally, its suggested it should be rendered as
- * a light grey box.
- * @property title The optional title [ComplicationText]. The length of the title, including
- * any time-dependent values at any valid time, is expected to not exceed seven characters. When
- * using this text, the watch face should be able to display any string of up to seven characters
- * (reducing the text size appropriately if the string is very wide). Although not expected, it is
- * possible that strings of more than seven characters might be seen, in which case they may be
- * truncated. If the title is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it as
- * a placeholder rather than rendering normally, its suggested it should be rendered as a light grey
- * box.
- * @property text The body [ComplicationText] of the complication. The length of the text, including
- * any time-dependent values at any valid time, is expected to not exceed seven characters. When
- * using this text, the watch face should be able to display any string of up to seven characters
- * (reducing the text size appropriately if the string is very wide). Although not expected, it is
- * possible that strings of more than seven characters might be seen, in which case they may be
- * truncated. If the text is equal to [ComplicationText.PLACEHOLDER] the renderer must treat it as a
- * placeholder rather than rendering normally, its suggested it should be rendered as a light grey
- * box.
- * @property contentDescription The content description field for accessibility.
- */
-@ComplicationExperimental
-public class DiscreteRangedValueComplicationData
-internal constructor(
-    public val value: Int,
-    public val min: Int,
-    public val max: Int,
-    public val monochromaticImage: MonochromaticImage?,
-    public val smallImage: SmallImage?,
-    public val title: ComplicationText?,
-    public val text: ComplicationText?,
-    public val contentDescription: ComplicationText?,
-    tapAction: PendingIntent?,
-    validTimeRange: TimeRange?,
-    cachedWireComplicationData: WireComplicationData?,
-    dataSource: ComponentName?,
-    @ComplicationPersistencePolicy persistencePolicy: Int,
-    @ComplicationDisplayPolicy displayPolicy: Int
-) : ComplicationData(
-    TYPE,
-    tapAction = tapAction,
-    cachedWireComplicationData = cachedWireComplicationData,
-    validTimeRange = validTimeRange ?: TimeRange.ALWAYS,
-    dataSource = dataSource,
-    persistencePolicy = persistencePolicy,
-    displayPolicy = displayPolicy
-) {
-    /**
-     * Builder for [DiscreteRangedValueComplicationData].
-     *
-     * You must at a minimum set the [value], [min], [max] and [contentDescription] fields and at
-     * least one of [monochromaticImage], [smallImage], [text] or [title].
-     *
-     * @param value The value of the ranged complication which should be in the range
-     * [[min]] .. [[max]]
-     * @param min The minimum value
-     * @param max The maximum value. This must be less than [Float.MAX_VALUE].
-     * @param contentDescription Localized description for use by screen readers
-     */
-    @OptIn(ComplicationExperimental::class)
-    public class Builder(
-        private val value: Int,
-        private val min: Int,
-        private val max: Int,
-        private var contentDescription: ComplicationText
-    ) : BaseBuilder<Builder, DiscreteRangedValueComplicationData>() {
-        private var tapAction: PendingIntent? = null
-        private var validTimeRange: TimeRange? = null
-        private var monochromaticImage: MonochromaticImage? = null
-        private var smallImage: SmallImage? = null
-        private var title: ComplicationText? = null
-        private var text: ComplicationText? = null
-
-        init {
-            require(max != Int.MAX_VALUE) {
-                "Int.MAX_VALUE is reserved and can't be used for max"
-            }
-        }
-
-        /** Sets optional pending intent to be invoked when the complication is tapped. */
-        public fun setTapAction(tapAction: PendingIntent?): Builder = apply {
-            this.tapAction = tapAction
-        }
-
-        /** Sets optional time range during which the complication has to be shown. */
-        @Suppress("MissingGetterMatchingBuilder") // b/174052810
-        public fun setValidTimeRange(validTimeRange: TimeRange?): Builder = apply {
-            this.validTimeRange = validTimeRange
-        }
-
-        /** Sets optional icon associated with the complication data. */
-        public fun setMonochromaticImage(monochromaticImage: MonochromaticImage?): Builder = apply {
-            this.monochromaticImage = monochromaticImage
-        }
-
-        /** Sets optional image associated with the complication data. */
-        public fun setSmallImage(smallImage: SmallImage?): Builder = apply {
-            this.smallImage = smallImage
-        }
-
-        /** Sets optional title associated with the complication data. */
-        public fun setTitle(title: ComplicationText?): Builder = apply {
-            this.title = title
-        }
-
-        /** Sets optional text associated with the complication data. */
-        public fun setText(text: ComplicationText?): Builder = apply {
-            this.text = text
-        }
-
-        /** Builds the [DiscreteRangedValueComplicationData]. */
-        @OptIn(ComplicationExperimental::class)
-        public override fun build(): DiscreteRangedValueComplicationData {
-            require(
-                monochromaticImage != null || smallImage != null || text != null || title != null
-            ) {
-                "At least one of monochromaticImage, smallImage, text or title must be set"
-            }
-            return DiscreteRangedValueComplicationData(
-                value,
-                min,
-                max,
-                monochromaticImage,
-                smallImage,
-                title,
-                text,
-                contentDescription,
-                tapAction,
-                validTimeRange,
-                cachedWireComplicationData,
-                dataSource,
-                persistencePolicy,
-                displayPolicy
-            )
-        }
-    }
-
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public override fun asWireComplicationData(): WireComplicationData {
-        cachedWireComplicationData?.let {
-            return it
-        }
-        return createWireComplicationDataBuilder().apply {
-            fillWireComplicationDataBuilder(this)
-        }.build().also { cachedWireComplicationData = it }
-    }
-
-    override fun fillWireComplicationDataBuilder(builder: WireComplicationDataBuilder) {
-        builder.setDiscreteRangedValue(value)
-        builder.setDiscreteRangedMinValue(min)
-        builder.setDiscreteRangedMaxValue(max)
-        monochromaticImage?.addToWireComplicationData(builder)
-        smallImage?.addToWireComplicationData(builder)
-        builder.setShortText(text?.toWireComplicationText())
-        builder.setShortTitle(title?.toWireComplicationText())
-        builder.setTapAction(tapAction)
-        builder.setContentDescription(
-            when (contentDescription) {
-                ComplicationText.EMPTY -> null
-                else -> contentDescription?.toWireComplicationText()
-            }
-        )
-        setValidTimeRange(validTimeRange, builder)
-        builder.setTapActionLostDueToSerialization(tapActionLostDueToSerialization)
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (javaClass != other?.javaClass) return false
-
-        other as DiscreteRangedValueComplicationData
-
-        if (value != other.value) return false
-        if (min != other.min) return false
-        if (max != other.max) return false
-        if (monochromaticImage != other.monochromaticImage) return false
-        if (smallImage != other.smallImage) return false
-        if (title != other.title) return false
-        if (text != other.text) return false
-        if (contentDescription != other.contentDescription) return false
-        if (tapActionLostDueToSerialization != other.tapActionLostDueToSerialization) return false
-        if (tapAction != other.tapAction) return false
-        if (validTimeRange != other.validTimeRange) return false
-        if (dataSource != other.dataSource) return false
-        if (persistencePolicy != other.persistencePolicy) return false
-        if (displayPolicy != other.displayPolicy) return false
-
-        return true
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    override fun hashCode(): Int {
-        var result = value.hashCode()
-        result = 31 * result + min.hashCode()
-        result = 31 * result + max.hashCode()
-        result = 31 * result + (monochromaticImage?.hashCode() ?: 0)
-        result = 31 * result + (smallImage?.hashCode() ?: 0)
-        result = 31 * result + (title?.hashCode() ?: 0)
-        result = 31 * result + (text?.hashCode() ?: 0)
-        result = 31 * result + (contentDescription?.hashCode() ?: 0)
-        result = 31 * result + tapActionLostDueToSerialization.hashCode()
-        result = 31 * result + (tapAction?.hashCode() ?: 0)
-        result = 31 * result + validTimeRange.hashCode()
-        result = 31 * result + dataSource.hashCode()
-        result = 31 * result + persistencePolicy.hashCode()
-        result = 31 * result + persistencePolicy.hashCode()
-        result = 31 * result + displayPolicy.hashCode()
-
-        return result
-    }
-
-    override fun toString(): String {
-        val valueString = if (WireComplicationData.shouldRedact()) {
-            "REDACTED"
-        } else {
-            value.toString()
-        }
-        return "DiscreteRangedValueComplicationData(value=$valueString, min=$min, max=$max, " +
-            "monochromaticImage=$monochromaticImage, smallImage=$smallImage, title=$title, " +
-            "text=$text, contentDescription=$contentDescription), " +
-            "tapActionLostDueToSerialization=$tapActionLostDueToSerialization, " +
-            "tapAction=$tapAction, validTimeRange=$validTimeRange, dataSource=$dataSource, " +
-            "persistencePolicy=$persistencePolicy, displayPolicy=$displayPolicy)"
-    }
-
-    override fun hasPlaceholderFields() = value == PLACEHOLDER || text?.isPlaceholder() == true ||
-        title?.isPlaceholder() == true || monochromaticImage?.isPlaceholder() == true ||
-        smallImage?.isPlaceholder() == true
-
-    override fun getNextChangeInstant(afterInstant: Instant): Instant {
-        val titleChangeInstant = title?.getNextChangeTime(afterInstant) ?: Instant.MAX
-        val textChangeInstant = text?.getNextChangeTime(afterInstant) ?: Instant.MAX
-        return if (textChangeInstant.isBefore(titleChangeInstant)) {
-            textChangeInstant
-        } else {
-            titleChangeInstant
-        }
-    }
-
-    /** @hide */
-    public companion object {
-        /** The [ComplicationType] corresponding to objects of this type. */
-        @OptIn(ComplicationExperimental::class)
-        @JvmField
-        public val TYPE: ComplicationType = ComplicationType.DISCRETE_RANGED_VALUE
 
         /**
-         * Used to signal the range should be rendered as a placeholder. It's suggested that a
-         * placeholder ranged value be drawn as a grey arc with a percentage value selected by the
-         * renderer.
-         *
-         * Note a placeholder may only be used in the context of
-         * [NoDataComplicationData.placeholder].
-         */
-        @JvmField
-        public val PLACEHOLDER = Int.MAX_VALUE
+         * The maximum size for [elements]. Complications are small and if we have a very large
+         * number of elements we likely won't be able to render them properly because the individual
+         * elements will be too small on screen. */
+        public const val MAX_NUM_ELEMENTS = 20
     }
 }
 
@@ -3211,6 +3010,7 @@
                     }
                     setPersistencePolicy(persistencePolicyCopy)
                     setDisplayPolicy(displayPolicyCopy)
+                    setValueType(rangedValueType)
                 }.build()
 
             MonochromaticImageComplicationData.TYPE.toWireComplicationType() ->
@@ -3301,24 +3101,6 @@
                     setDisplayPolicy(displayPolicyCopy)
                 }.build()
 
-            DiscreteRangedValueComplicationData.TYPE.toWireComplicationType() ->
-                DiscreteRangedValueComplicationData.Builder(
-                    value = discreteRangedValue,
-                    min = discreteRangedMinValue,
-                    max = discreteRangedMaxValue,
-                    contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
-                ).apply {
-                    setTapAction(tapAction)
-                    setValidTimeRange(parseTimeRange())
-                    setMonochromaticImage(parseIconPlaceholderAware())
-                    setSmallImage(parseSmallImagePlaceholderAware())
-                    setTitle(shortTitle?.toApiComplicationTextPlaceholderAware())
-                    setText(shortText?.toApiComplicationTextPlaceholderAware())
-                    setDataSource(dataSourceCopy)
-                    setPersistencePolicy(persistencePolicyCopy)
-                    setDisplayPolicy(displayPolicyCopy)
-                }.build()
-
             WeightedElementsComplicationData.TYPE.toWireComplicationType() ->
                 WeightedElementsComplicationData.Builder(
                     elements = if (elementWeights!!.isEmpty()) {
@@ -3335,6 +3117,7 @@
                     },
                     contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
                 ).apply {
+                    setElementBackgroundColor(elementBackgroundColor)
                     setTapAction(tapAction)
                     setValidTimeRange(parseTimeRange())
                     setMonochromaticImage(parseIconPlaceholderAware())
@@ -3436,6 +3219,7 @@
                     setDataSource(dataSourceCopy)
                     setPersistencePolicy(persistencePolicyCopy)
                     setDisplayPolicy(displayPolicyCopy)
+                    setValueType(rangedValueType)
                 }.build()
 
             MonochromaticImageComplicationData.TYPE.toWireComplicationType() ->
@@ -3544,26 +3328,6 @@
                     setDisplayPolicy(displayPolicyCopy)
                 }.build()
 
-            DiscreteRangedValueComplicationData.TYPE.toWireComplicationType() ->
-                DiscreteRangedValueComplicationData.Builder(
-                    value = discreteRangedValue,
-                    min = discreteRangedMinValue,
-                    max = discreteRangedMaxValue,
-                    contentDescription = contentDescription?.toApiComplicationText()
-                        ?: ComplicationText.EMPTY
-                ).apply {
-                    setTapAction(tapAction)
-                    setValidTimeRange(parseTimeRange())
-                    setMonochromaticImage(parseIcon())
-                    setSmallImage(parseSmallImage())
-                    setTitle(shortTitle?.toApiComplicationText())
-                    setText(shortText?.toApiComplicationText())
-                    setCachedWireComplicationData(wireComplicationData)
-                    setDataSource(dataSourceCopy)
-                    setPersistencePolicy(persistencePolicyCopy)
-                    setDisplayPolicy(displayPolicyCopy)
-                }.build()
-
             WeightedElementsComplicationData.TYPE.toWireComplicationType() -> {
                 val elementWeights = this.elementWeights!!
                 val elementColors = this.elementColors!!
@@ -3576,6 +3340,7 @@
                     }.toList(),
                     contentDescription?.toApiComplicationText() ?: ComplicationText.EMPTY
                 ).apply {
+                    setElementBackgroundColor(elementBackgroundColor)
                     setTapAction(tapAction)
                     setValidTimeRange(parseTimeRange())
                     setMonochromaticImage(parseIcon())
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt
index 905b056a..e64be10 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Type.kt
@@ -42,8 +42,6 @@
     @ComplicationExperimental
     GOAL_PROGRESS(WireComplicationData.TYPE_GOAL_PROGRESS),
     @ComplicationExperimental
-    DISCRETE_RANGED_VALUE(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE),
-    @ComplicationExperimental
     WEIGHTED_ELEMENTS(WireComplicationData.TYPE_WEIGHTED_ELEMENTS),
 
     @ComplicationExperimental
@@ -86,7 +84,6 @@
                 NO_PERMISSION.wireType -> NO_PERMISSION
                 PROTO_LAYOUT.wireType -> PROTO_LAYOUT
                 GOAL_PROGRESS.wireType -> GOAL_PROGRESS
-                DISCRETE_RANGED_VALUE.wireType -> DISCRETE_RANGED_VALUE
                 WEIGHTED_ELEMENTS.wireType -> WEIGHTED_ELEMENTS
                 LIST.wireType -> LIST
                 else -> EMPTY
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
index 25c7f64..29fc589 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
@@ -412,6 +412,7 @@
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                     .setRangedValue(95f)
+                    .setRangedValueType(RangedValueTypes.UNDEFINED)
                     .setRangedMinValue(0f)
                     .setRangedMaxValue(100f)
                     .setShortTitle(WireComplicationText.plainText("battery"))
@@ -452,7 +453,7 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "RangedValueComplicationData(value=95.0, min=0.0, max=100.0, " +
+            "RangedValueComplicationData(value=95.0, valueType=0, min=0.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
                 "mSurroundingText=battery, mTimeDependentText=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
@@ -477,6 +478,7 @@
             .setMonochromaticImage(monochromaticImage)
             .setSmallImage(smallImage)
             .setDataSource(dataSourceA)
+            .setValueType(RangedValueTypes.SCORE)
             .build()
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
@@ -492,6 +494,7 @@
                     .setDataSource(dataSourceA)
                     .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
                     .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
+                    .setRangedValueType(RangedValueTypes.SCORE)
                     .build()
             )
         testRoundTripConversions(data)
@@ -517,6 +520,7 @@
             .setMonochromaticImage(monochromaticImage2)
             .setSmallImage(smallImage2)
             .setDataSource(dataSourceA)
+            .setValueType(RangedValueTypes.SCORE)
             .build()
 
         val data3 = RangedValueComplicationData.Builder(
@@ -525,6 +529,7 @@
         )
             .setTitle("battery2".complicationText)
             .setDataSource(dataSourceB)
+            .setValueType(RangedValueTypes.SCORE)
             .build()
 
         assertThat(data).isEqualTo(data2)
@@ -532,7 +537,7 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "RangedValueComplicationData(value=95.0, min=0.0, max=100.0, " +
+            "RangedValueComplicationData(value=95.0, valueType=1, min=0.0, max=100.0, " +
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
                 "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
                 "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=battery," +
@@ -619,7 +624,7 @@
     @RequiresApi(Build.VERSION_CODES.P)
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun goalProgressComplicationData_with_ColorRamp_andiImages() {
+    public fun goalProgressComplicationData_with_ColorRamp_and_Images() {
         val data = GoalProgressComplicationData.Builder(
             value = 1200f, targetValue = 10000f,
             contentDescription = "content description".complicationText
@@ -703,153 +708,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun discreteRangedValueComplicationData() {
-        val data = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = "content description".complicationText
-        )
-            .setTitle("battery".complicationText)
-            .setDataSource(dataSourceA)
-            .build()
-        ParcelableSubject.assertThat(data.asWireComplicationData())
-            .hasSameSerializationAs(
-                WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                    .setDiscreteRangedValue(95)
-                    .setDiscreteRangedMinValue(0)
-                    .setDiscreteRangedMaxValue(100)
-                    .setShortTitle(WireComplicationText.plainText("battery"))
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
-                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                    .build()
-            )
-        testRoundTripConversions(data)
-        val deserialized = serializeAndDeserialize(data) as DiscreteRangedValueComplicationData
-        assertThat(deserialized.max).isEqualTo(100)
-        assertThat(deserialized.min).isEqualTo(0)
-        assertThat(deserialized.value).isEqualTo(95)
-        assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
-            .isEqualTo("content description")
-        assertThat(deserialized.title!!.getTextAt(resources, Instant.EPOCH))
-            .isEqualTo("battery")
-
-        val data2 = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = "content description".complicationText
-        )
-            .setTitle("battery".complicationText)
-            .setDataSource(dataSourceA)
-            .build()
-
-        val data3 = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = "content description2".complicationText
-        )
-            .setTitle("battery2".complicationText)
-            .setDataSource(dataSourceB)
-            .build()
-
-        assertThat(data).isEqualTo(data2)
-        assertThat(data).isNotEqualTo(data3)
-        assertThat(data.hashCode()).isEqualTo(data2.hashCode())
-        assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
-        assertThat(data.toString()).isEqualTo(
-            "DiscreteRangedValueComplicationData(value=95, min=0, max=100, " +
-                "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=battery, mTimeDependentText=null}, text=null, " +
-                "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
-                "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
-                "-1000000000-01-01T00:00:00Z, endDateTimeMillis=" +
-                "+1000000000-12-31T23:59:59.999999999Z), " +
-                "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
-        )
-    }
-
-    @RequiresApi(Build.VERSION_CODES.P)
-    @OptIn(ComplicationExperimental::class)
-    @Test
-    public fun discreteRangedValueComplicationData_withImages() {
-        val data = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = "content description".complicationText
-        )
-            .setTitle("battery".complicationText)
-            .setMonochromaticImage(monochromaticImage)
-            .setSmallImage(smallImage)
-            .setDataSource(dataSourceA)
-            .build()
-        ParcelableSubject.assertThat(data.asWireComplicationData())
-            .hasSameSerializationAs(
-                WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                    .setDiscreteRangedValue(95)
-                    .setDiscreteRangedMinValue(0)
-                    .setDiscreteRangedMaxValue(100)
-                    .setIcon(monochromaticImageIcon)
-                    .setSmallImage(smallImageIcon)
-                    .setSmallImageStyle(WireComplicationData.IMAGE_STYLE_PHOTO)
-                    .setShortTitle(WireComplicationText.plainText("battery"))
-                    .setContentDescription(WireComplicationText.plainText("content description"))
-                    .setDataSource(dataSourceA)
-                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                    .build()
-            )
-        testRoundTripConversions(data)
-        val deserialized = serializeAndDeserialize(data) as DiscreteRangedValueComplicationData
-        assertThat(deserialized.max).isEqualTo(100)
-        assertThat(deserialized.min).isEqualTo(0)
-        assertThat(deserialized.value).isEqualTo(95)
-        assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
-            .isEqualTo("content description")
-        assertThat(deserialized.title!!.getTextAt(resources, Instant.EPOCH))
-            .isEqualTo("battery")
-        assertThat(deserialized.monochromaticImage!!.image.uri.toString())
-            .isEqualTo("someuri")
-        assertThat(deserialized.smallImage!!.image.uri.toString())
-            .isEqualTo("someuri2")
-        assertThat(deserialized.smallImage!!.type).isEqualTo(SmallImageType.PHOTO)
-
-        val data2 = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = "content description".complicationText
-        )
-            .setMonochromaticImage(monochromaticImage2)
-            .setSmallImage(smallImage2)
-            .setTitle("battery".complicationText)
-            .setDataSource(dataSourceA)
-            .build()
-
-        val data3 = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = "content description2".complicationText
-        )
-            .setTitle("battery2".complicationText)
-            .setDataSource(dataSourceB)
-            .build()
-
-        assertThat(data).isEqualTo(data2)
-        assertThat(data).isNotEqualTo(data3)
-        assertThat(data.hashCode()).isEqualTo(data2.hashCode())
-        assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
-        assertThat(data.toString()).isEqualTo(
-            "DiscreteRangedValueComplicationData(value=95, min=0, max=100, " +
-                "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
-                "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
-                "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=battery," +
-                " mTimeDependentText=null}, text=null," +
-                " contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
-                "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
-                "-1000000000-01-01T00:00:00Z, endDateTimeMillis=" +
-                "+1000000000-12-31T23:59:59.999999999Z), " +
-                "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
-        )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun rangedValueComplicationData_with_ColorRamp() {
         val data = RangedValueComplicationData.Builder(
             value = 95f, min = 0f, max = 100f,
@@ -863,6 +721,7 @@
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                     .setRangedValue(95f)
+                    .setRangedValueType(RangedValueTypes.UNDEFINED)
                     .setRangedMinValue(0f)
                     .setRangedMaxValue(100f)
                     .setShortTitle(WireComplicationText.plainText("battery"))
@@ -907,7 +766,7 @@
         assertThat(data.hashCode()).isEqualTo(data2.hashCode())
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
-            "RangedValueComplicationData(value=95.0, min=0.0, max=100.0, " +
+            "RangedValueComplicationData(value=95.0, valueType=0, min=0.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
                 "mSurroundingText=battery, mTimeDependentText=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
@@ -931,6 +790,7 @@
             ),
             contentDescription = "content description".complicationText
         )
+            .setElementBackgroundColor(Color.GRAY)
             .setTitle("calories".complicationText)
             .setDataSource(dataSourceA)
             .build()
@@ -939,6 +799,7 @@
                 WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                     .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                     .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                    .setElementBackgroundColor(Color.GRAY)
                     .setShortTitle(WireComplicationText.plainText("calories"))
                     .setContentDescription(WireComplicationText.plainText("content description"))
                     .setDataSource(dataSourceA)
@@ -968,6 +829,7 @@
             ),
             contentDescription = "content description".complicationText
         )
+            .setElementBackgroundColor(Color.GRAY)
             .setTitle("calories".complicationText)
             .setDataSource(dataSourceA)
             .build()
@@ -991,8 +853,9 @@
         assertThat(data.toString()).isEqualTo(
             "WeightedElementsComplicationData(elements=Element(color=-65536, weight=0.5)," +
                 " Element(color=-16711936, weight=1.0), Element(color=-16776961, weight=2.0), " +
-                "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=calories, mTimeDependentText=null}, text=null, " +
+                "elementBackgroundColor=-7829368, monochromaticImage=null, smallImage=null, " +
+                "title=ComplicationText{mSurroundingText=calories, mTimeDependentText=null}, " +
+                "text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
                 "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
                 "tapAction=null, validTimeRange=TimeRange(" +
@@ -1024,6 +887,7 @@
                 WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                     .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                     .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                    .setElementBackgroundColor(Color.TRANSPARENT)
                     .setShortTitle(WireComplicationText.plainText("calories"))
                     .setIcon(monochromaticImageIcon)
                     .setSmallImage(smallImageIcon)
@@ -1086,13 +950,14 @@
         assertThat(data.toString()).isEqualTo(
             "WeightedElementsComplicationData(elements=Element(color=-65536, weight=0.5)," +
                 " Element(color=-16711936, weight=1.0), Element(color=-16776961, weight=2.0), " +
-                "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
-                "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
-                "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=" +
-                "calories, mTimeDependentText=null}, text=null, " +
-                "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
-                "tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
+                "elementBackgroundColor=0, monochromaticImage=MonochromaticImage(" +
+                "image=Icon(typ=URI uri=someuri), ambientImage=null), " +
+                "smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), type=PHOTO, " +
+                "ambientImage=null), title=ComplicationText{mSurroundingText=calories, " +
+                "mTimeDependentText=null}, text=null, contentDescription=ComplicationText{" +
+                "mSurroundingText=content description, mTimeDependentText=null}), " +
+                "tapActionLostDueToSerialization=false, tapAction=null, " +
+                "validTimeRange=TimeRange(startDateTimeMillis=" +
                 "-1000000000-01-01T00:00:00Z, endDateTimeMillis=" +
                 "+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
@@ -1408,6 +1273,7 @@
                                 .build(),
                             WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                                 .setRangedValue(95f)
+                                .setRangedValueType(RangedValueTypes.UNDEFINED)
                                 .setRangedMinValue(0f)
                                 .setRangedMaxValue(100f)
                                 .setShortText(WireComplicationText.plainText("battery"))
@@ -1487,10 +1353,10 @@
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), dataSource=null, " +
                 "persistencePolicy=0, displayPolicy=0), RangedValueComplicationData(value=95.0, " +
-                "min=0.0, max=100.0, monochromaticImage=null, smallImage=null, title=null, " +
-                "text=ComplicationText{mSurroundingText=battery, mTimeDependentText=null}, " +
-                "contentDescription=ComplicationText{mSurroundingText=, " +
-                "mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
+                "valueType=0, min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
+                "title=null, text=ComplicationText{mSurroundingText=battery, " +
+                "mTimeDependentText=null}, contentDescription=ComplicationText{mSurroundingText=," +
+                " mTimeDependentText=null}), tapActionLostDueToSerialization=false, " +
                 "tapAction=null, validTimeRange=TimeRange(" +
                 "startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), dataSource=null, " +
@@ -1667,6 +1533,7 @@
                     .setPlaceholder(
                         WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                             .setRangedValue(RangedValueComplicationData.PLACEHOLDER)
+                            .setRangedValueType(RangedValueTypes.UNDEFINED)
                             .setRangedMinValue(0f)
                             .setRangedMaxValue(100f)
                             .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
@@ -1715,7 +1582,7 @@
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=RangedValueComplicationData(" +
-                "value=3.4028235E38, min=0.0, max=100.0, monochromaticImage=null, " +
+                "value=3.4028235E38, valueType=0, min=0.0, max=100.0, monochromaticImage=null, " +
                 "smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null}, " +
                 "contentDescription=ComplicationText{mSurroundingText=" +
@@ -1819,90 +1686,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun noDataComplicationData_discreteRangedValue() {
-        val data = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = DiscreteRangedValueComplicationData.PLACEHOLDER,
-                min = 0,
-                max = 100,
-                "content description".complicationText
-            )
-                .setText(ComplicationText.PLACEHOLDER)
-                .setDataSource(dataSourceA)
-                .build()
-        )
-        ParcelableSubject.assertThat(data.asWireComplicationData())
-            .hasSameSerializationAs(
-                WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholder(
-                        WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                            .setDiscreteRangedValue(DiscreteRangedValueComplicationData.PLACEHOLDER)
-                            .setDiscreteRangedMinValue(0)
-                            .setDiscreteRangedMaxValue(100)
-                            .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
-                            .setContentDescription(
-                                WireComplicationText.plainText("content description")
-                            )
-                            .setDataSource(dataSourceA)
-                            .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                            .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                            .build()
-                    )
-                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                    .build()
-            )
-        testRoundTripConversions(data)
-        val deserialized = serializeAndDeserialize(data) as NoDataComplicationData
-        assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
-            .isEqualTo("content description")
-
-        val data2 = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = DiscreteRangedValueComplicationData.PLACEHOLDER,
-                min = 0,
-                max = 100,
-                "content description".complicationText
-            )
-                .setText(ComplicationText.PLACEHOLDER)
-                .setDataSource(dataSourceA)
-                .build()
-        )
-        val data3 = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = DiscreteRangedValueComplicationData.PLACEHOLDER,
-                min = 0,
-                max = 100,
-                "content description".complicationText
-            )
-                .setTitle(ComplicationText.PLACEHOLDER)
-                .setText(ComplicationText.PLACEHOLDER)
-                .build()
-        )
-
-        assertThat(data).isEqualTo(data2)
-        assertThat(data).isNotEqualTo(data3)
-        assertThat(data.hashCode()).isEqualTo(data2.hashCode())
-        assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
-        assertThat(data.toString()).isEqualTo(
-            "NoDataComplicationData(placeholder=DiscreteRangedValueComplicationData(" +
-                "value=2147483647, min=0, max=100, monochromaticImage=null, smallImage=null, " +
-                "title=null, text=ComplicationText{mSurroundingText=__placeholder__, " +
-                "mTimeDependentText=null}, contentDescription=ComplicationText{mSurroundingText=" +
-                "content description, mTimeDependentText=null}), " +
-                "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=TimeRange(" +
-                "startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
-                "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
-                "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, " +
-                "displayPolicy=0), tapActionLostDueToSerialization=false, tapAction=null, " +
-                "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
-                "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), persistencePolicy=0, " +
-                "displayPolicy=0)"
-        )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun noDataComplicationData_weightedElements() {
         val data = NoDataComplicationData(
             WeightedElementsComplicationData.Builder(
@@ -1914,6 +1697,7 @@
                 contentDescription = "content description".complicationText
             )
                 .setTitle("calories".complicationText)
+                .setElementBackgroundColor(Color.GRAY)
                 .setDataSource(dataSourceA)
                 .build()
         )
@@ -1924,6 +1708,7 @@
                         WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                             .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                             .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                            .setElementBackgroundColor(Color.GRAY)
                             .setShortTitle(WireComplicationText.plainText("calories"))
                             .setContentDescription(
                                 WireComplicationText.plainText("content description")
@@ -1952,6 +1737,7 @@
                 contentDescription = "content description".complicationText
             )
                 .setTitle("calories".complicationText)
+                .setElementBackgroundColor(Color.GRAY)
                 .setDataSource(dataSourceA)
                 .build()
         )
@@ -1975,7 +1761,8 @@
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=WeightedElementsComplicationData(" +
                 "elements=Element(color=-65536, weight=0.5), Element(color=-16711936, " +
-                "weight=1.0), Element(color=-16776961, weight=2.0), monochromaticImage=null, " +
+                "weight=1.0), Element(color=-16776961, weight=2.0), " +
+                "elementBackgroundColor=-7829368, monochromaticImage=null, " +
                 "smallImage=null, title=ComplicationText{mSurroundingText=calories, " +
                 "mTimeDependentText=null}, text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null}), " +
@@ -2003,6 +1790,7 @@
                 .setText(ComplicationText.PLACEHOLDER)
                 .setDataSource(dataSourceA)
                 .setColorRamp(ColorRamp(intArrayOf(Color.RED, Color.GREEN, Color.BLUE), true))
+                .setValueType(RangedValueTypes.SCORE)
                 .build()
         )
         ParcelableSubject.assertThat(data.asWireComplicationData())
@@ -2011,6 +1799,7 @@
                     .setPlaceholder(
                         WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                             .setRangedValue(RangedValueComplicationData.PLACEHOLDER)
+                            .setRangedValueType(RangedValueTypes.SCORE)
                             .setRangedMinValue(0f)
                             .setRangedMaxValue(100f)
                             .setShortText(ComplicationText.PLACEHOLDER.toWireComplicationText())
@@ -2043,6 +1832,7 @@
                 .setText(ComplicationText.PLACEHOLDER)
                 .setDataSource(dataSourceA)
                 .setColorRamp(ColorRamp(intArrayOf(Color.RED, Color.GREEN, Color.BLUE), true))
+                .setValueType(RangedValueTypes.SCORE)
                 .build()
         )
         val data3 = NoDataComplicationData(
@@ -2062,7 +1852,7 @@
         assertThat(data.hashCode()).isNotEqualTo(data3.hashCode())
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=RangedValueComplicationData(" +
-                "value=3.4028235E38, min=0.0, max=100.0, monochromaticImage=null, " +
+                "value=3.4028235E38, valueType=1, min=0.0, max=100.0, monochromaticImage=null, " +
                 "smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null}, " +
                 "contentDescription=ComplicationText{mSurroundingText=" +
@@ -2411,28 +2201,12 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun discreteRangedValueComplicationData() {
-        assertRoundtrip(
-            WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                .setDiscreteRangedValue(95)
-                .setDiscreteRangedMinValue(0)
-                .setDiscreteRangedMaxValue(100)
-                .setShortTitle(WireComplicationText.plainText("battery"))
-                .setContentDescription(WireComplicationText.plainText("content description"))
-                .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                .build(),
-            ComplicationType.DISCRETE_RANGED_VALUE
-        )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun weightedElementsComplicationData() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                 .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                 .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                .setElementBackgroundColor(Color.DKGRAY)
                 .setShortTitle(WireComplicationText.plainText("calories"))
                 .setContentDescription(WireComplicationText.plainText("content description"))
                 .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
@@ -2603,33 +2377,7 @@
                             WireComplicationText.plainText("content description")
                         )
                         .setRangedValue(75f)
-                        .setRangedMinValue(0f)
-                        .setRangedMaxValue(100f)
-                        .setShortTitle(WireComplicationText.plainText("battery"))
-                        .setIcon(icon)
-                        .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                        .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                        .build()
-                )
-                .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                .build(),
-            ComplicationType.NO_DATA
-        )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
-    public fun noDataComplicationData_rangedValue_drawSegmented() {
-        val icon = Icon.createWithContentUri("someuri")
-        assertRoundtrip(
-            WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholder(
-                    WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
-                        .setContentDescription(
-                            WireComplicationText.plainText("content description")
-                        )
-                        .setRangedValue(75f)
+                        .setRangedValueType(RangedValueTypes.UNDEFINED)
                         .setRangedMinValue(0f)
                         .setRangedMaxValue(100f)
                         .setShortTitle(WireComplicationText.plainText("battery"))
@@ -2675,33 +2423,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun noDataComplicationData_discreteRangedValue() {
-        val icon = Icon.createWithContentUri("someuri")
-        assertRoundtrip(
-            WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                .setPlaceholder(
-                    WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                        .setContentDescription(
-                            WireComplicationText.plainText("content description")
-                        )
-                        .setDiscreteRangedValue(75)
-                        .setDiscreteRangedMinValue(0)
-                        .setDiscreteRangedMaxValue(100)
-                        .setShortTitle(WireComplicationText.plainText("battery"))
-                        .setIcon(icon)
-                        .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                        .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                        .build()
-                )
-                .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                .build(),
-            ComplicationType.NO_DATA
-        )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun noDataComplicationData_weightedElementsComplicationData() {
         assertRoundtrip(
             WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
@@ -2709,6 +2430,7 @@
                     WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                         .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                         .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                        .setElementBackgroundColor(Color.DKGRAY)
                         .setShortTitle(WireComplicationText.plainText("calories"))
                         .setContentDescription(
                             WireComplicationText.plainText("content description")
@@ -2891,20 +2613,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun discreteRangedValueComplicationData() {
-        assertThat(
-            DiscreteRangedValueComplicationData.Builder(
-                value = 95, min = 0, max = 100,
-                contentDescription = ComplicationText.EMPTY
-            )
-                .setText("battery".complicationText)
-                .setTapAction(mPendingIntent)
-                .build().asWireComplicationData().tapAction
-        ).isEqualTo(mPendingIntent)
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun weightedElementsComplicationData() {
         assertThat(
             WeightedElementsComplicationData.Builder(
@@ -3065,20 +2773,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun discreteRangedValueComplicationData() {
-        assertThat(
-            DiscreteRangedValueComplicationData.Builder(
-                value = 95, min = 0, max = 100,
-                contentDescription = ComplicationText.EMPTY
-            )
-                .setText("battery".complicationText)
-                .setTapAction(mPendingIntent)
-                .build().asWireComplicationData().toApiComplicationData().tapAction
-        ).isEqualTo(mPendingIntent)
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun weightedElementsComplicationData() {
         assertThat(
             WeightedElementsComplicationData.Builder(
@@ -3243,6 +2937,7 @@
             .hasSameSerializationAs(
                 WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                     .setRangedValue(95f)
+                    .setRangedValueType(RangedValueTypes.UNDEFINED)
                     .setRangedMinValue(0f)
                     .setRangedMaxValue(100f)
                     .setShortText(WireComplicationText.plainText("battery"))
@@ -3284,31 +2979,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun discreteRangedValueComplicationData() {
-        val data = DiscreteRangedValueComplicationData.Builder(
-            value = 95, min = 0, max = 100,
-            contentDescription = ComplicationText.EMPTY
-        )
-            .setText("battery".complicationText)
-            .setValidTimeRange(TimeRange.between(testStartInstant, testEndDateInstant))
-            .build()
-        ParcelableSubject.assertThat(data.asWireComplicationData())
-            .hasSameSerializationAs(
-                WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                    .setDiscreteRangedValue(95)
-                    .setDiscreteRangedMinValue(0)
-                    .setDiscreteRangedMaxValue(100)
-                    .setShortText(WireComplicationText.plainText("battery"))
-                    .setStartDateTimeMillis(testStartInstant.toEpochMilli())
-                    .setEndDateTimeMillis(testEndDateInstant.toEpochMilli())
-                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                    .build()
-            )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun weightedElementsComplicationData() {
         val data = WeightedElementsComplicationData.Builder(
             listOf(
@@ -3326,6 +2996,7 @@
                 WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                     .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                     .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                    .setElementBackgroundColor(Color.TRANSPARENT)
                     .setShortTitle(WireComplicationText.plainText("calories"))
                     .setContentDescription(WireComplicationText.plainText("content description"))
                     .setStartDateTimeMillis(testStartInstant.toEpochMilli())
@@ -3456,6 +3127,7 @@
 
         val wireRangedValue = WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
             .setRangedValue(95f)
+            .setRangedValueType(RangedValueTypes.UNDEFINED)
             .setRangedMinValue(0f)
             .setRangedMaxValue(100f)
             .setShortText(WireComplicationText.plainText("battery"))
@@ -3541,6 +3213,7 @@
                     .setPlaceholder(
                         WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
                             .setRangedValue(95f)
+                            .setRangedValueType(RangedValueTypes.UNDEFINED)
                             .setRangedMinValue(0f)
                             .setRangedMaxValue(100f)
                             .setShortText(WireComplicationText.plainText("battery"))
@@ -3591,38 +3264,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    public fun noDataComplicationData_discreteRangedValue() {
-        val data = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = 95,
-                min = 0,
-                max = 100,
-                ComplicationText.EMPTY
-            )
-                .setText("battery".complicationText)
-                .build()
-        )
-        ParcelableSubject.assertThat(data.asWireComplicationData())
-            .hasSameSerializationAs(
-                WireComplicationDataBuilder(WireComplicationData.TYPE_NO_DATA)
-                    .setPlaceholder(
-                        WireComplicationDataBuilder(WireComplicationData.TYPE_DISCRETE_RANGED_VALUE)
-                            .setDiscreteRangedValue(95)
-                            .setDiscreteRangedMinValue(0)
-                            .setDiscreteRangedMaxValue(100)
-                            .setShortText(WireComplicationText.plainText("battery"))
-                            .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                            .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                            .build()
-                    )
-                    .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
-                    .setDisplayPolicy(ComplicationDisplayPolicies.ALWAYS_DISPLAY)
-                    .build()
-            )
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     public fun noDataComplicationData_weightedElements() {
         val data = NoDataComplicationData(
             WeightedElementsComplicationData.Builder(
@@ -3643,6 +3284,7 @@
                         WireComplicationDataBuilder(WireComplicationData.TYPE_WEIGHTED_ELEMENTS)
                             .setElementWeights(floatArrayOf(0.5f, 1f, 2f))
                             .setElementColors(intArrayOf(Color.RED, Color.GREEN, Color.BLUE))
+                            .setElementBackgroundColor(Color.TRANSPARENT)
                             .setShortTitle(WireComplicationText.plainText("calories"))
                             .setContentDescription(
                                 WireComplicationText.plainText("content description")
@@ -3792,6 +3434,7 @@
 
         val wireRangedValue = WireComplicationDataBuilder(WireComplicationData.TYPE_RANGED_VALUE)
             .setRangedValue(95f)
+            .setRangedValueType(RangedValueTypes.UNDEFINED)
             .setRangedMinValue(0f)
             .setRangedMaxValue(100f)
             .setShortText(WireComplicationText.plainText("battery"))
@@ -3893,7 +3536,7 @@
             .build()
 
         assertThat(data.toString()).isEqualTo("RangedValueComplicationData(value=REDACTED, " +
-            "min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
+            "valueType=0, min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
             "title=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
             "text=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}, " +
             "contentDescription=ComplicationText{mSurroundingText=REDACTED, " +
@@ -3932,34 +3575,6 @@
         )
     }
 
-    @OptIn(ComplicationExperimental::class)
-    @Test
-    fun discreteRangedValue() {
-        val data = DiscreteRangedValueComplicationData.Builder(
-            50,
-            0,
-            100,
-            "content description".complicationText
-        )
-            .setText("text".complicationText)
-            .setTitle("title".complicationText)
-            .build()
-
-        assertThat(data.toString()).isEqualTo(
-            "DiscreteRangedValueComplicationData(value=REDACTED, " +
-            "min=0, max=100, monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-            "mSurroundingText=REDACTED, mTimeDependentText=null}, text=ComplicationText{" +
-            "mSurroundingText=REDACTED, mTimeDependentText=null}, contentDescription=" +
-            "ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null}), " +
-            "tapActionLostDueToSerialization=false, tapAction=null, " +
-            "validTimeRange=TimeRange(REDACTED), dataSource=null, persistencePolicy=0, " +
-            "displayPolicy=0)"
-        )
-        assertThat(data.asWireComplicationData().toString()).isEqualTo(
-            "ComplicationData{mType=14, mFields=REDACTED}"
-        )
-    }
-
     @Test
     fun placeholder() {
         val data = NoDataComplicationData(
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/PlaceholderTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/PlaceholderTest.kt
index 1f16233..dbedd54 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/PlaceholderTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/PlaceholderTest.kt
@@ -273,66 +273,6 @@
 
     @OptIn(ComplicationExperimental::class)
     @Test
-    fun placeholder_discreteRangedValue() {
-        val placeholderRangedValue = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = DiscreteRangedValueComplicationData.PLACEHOLDER,
-                min = 1,
-                max = 10,
-                contentDescription
-            )
-                .setText(ComplicationText.PLACEHOLDER)
-                .setTitle(ComplicationText.PLACEHOLDER)
-                .setMonochromaticImage(MonochromaticImage.PLACEHOLDER)
-                .setSmallImage(SmallImage.PLACEHOLDER)
-                .build()
-        ).toWireFormatRoundTrip().placeholder as DiscreteRangedValueComplicationData
-
-        assertThat(placeholderRangedValue.value)
-            .isEqualTo(DiscreteRangedValueComplicationData.PLACEHOLDER)
-        assertThat(placeholderRangedValue.text).isEqualTo(ComplicationText.PLACEHOLDER)
-        assertThat(placeholderRangedValue.title).isEqualTo(ComplicationText.PLACEHOLDER)
-        assertThat(placeholderRangedValue.monochromaticImage)
-            .isEqualTo(MonochromaticImage.PLACEHOLDER)
-        assertThat(placeholderRangedValue.smallImage).isEqualTo(SmallImage.PLACEHOLDER)
-        assertThat(placeholderRangedValue.contentDescription!!.getTextAt(resources, Instant.EPOCH))
-            .isEqualTo("description")
-        assertThat(placeholderRangedValue.hasPlaceholderFields()).isTrue()
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
-    fun normal_discreteRangedValue() {
-        val placeholderRangedValue = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = 7,
-                min = 1,
-                max = 10,
-                contentDescription
-            )
-                .setText(text)
-                .setTitle(title)
-                .setMonochromaticImage(monochromaticImage)
-                .setSmallImage(smallImage)
-                .build()
-        ).toWireFormatRoundTrip().placeholder as DiscreteRangedValueComplicationData
-
-        assertThat(placeholderRangedValue.text!!.getTextAt(resources, Instant.EPOCH)).isEqualTo(
-            text.getTextAt(resources, Instant.EPOCH)
-        )
-        assertThat(placeholderRangedValue.title!!.getTextAt(resources, Instant.EPOCH)).isEqualTo(
-            title.getTextAt(resources, Instant.EPOCH)
-        )
-        assertThat(placeholderRangedValue.monochromaticImage).isEqualTo(monochromaticImage)
-        assertThat(placeholderRangedValue.smallImage).isEqualTo(smallImage)
-        assertThat(placeholderRangedValue.value).isEqualTo(7)
-        assertThat(placeholderRangedValue.min).isEqualTo(1)
-        assertThat(placeholderRangedValue.max).isEqualTo(10)
-        assertThat(placeholderRangedValue.hasPlaceholderFields()).isFalse()
-    }
-
-    @OptIn(ComplicationExperimental::class)
-    @Test
     fun placeholder_weightedElements() {
         val placeholderWeightedElements = NoDataComplicationData(
             WeightedElementsComplicationData.Builder(
@@ -393,26 +333,6 @@
         assertThat(weightedElements.hasPlaceholderFields()).isFalse()
     }
 
-    @OptIn(ComplicationExperimental::class)
-    @Test
-    fun titleAbsent_discreteRangedValue() {
-        val placeholderRangedValue = NoDataComplicationData(
-            DiscreteRangedValueComplicationData.Builder(
-                value = DiscreteRangedValueComplicationData.PLACEHOLDER,
-                min = 1,
-                max = 10,
-                contentDescription
-            )
-                .setText(ComplicationText.PLACEHOLDER)
-                .build()
-        ).toWireFormatRoundTrip().placeholder as DiscreteRangedValueComplicationData
-
-        assertThat(placeholderRangedValue.text).isEqualTo(ComplicationText.PLACEHOLDER)
-        assertThat(placeholderRangedValue.title).isNull()
-        assertThat(placeholderRangedValue.monochromaticImage).isNull()
-        assertThat(placeholderRangedValue.smallImage).isNull()
-    }
-
     @Test
     fun placeholder_monochromaticImage() {
         val placeholderMonochromaticImage = NoDataComplicationData(
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
index 85b23b9..dde6316 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/ExampleWindowInitializer.kt
@@ -18,14 +18,12 @@
 
 import android.content.Context
 import androidx.startup.Initializer
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.SplitController
 import androidx.window.sample.R
 
 /**
  * Initializes SplitController with a set of statically defined rules.
  */
-@OptIn(ExperimentalWindowApi::class)
 class ExampleWindowInitializer : Initializer<SplitController> {
     override fun create(context: Context): SplitController {
         SplitController.initialize(context, R.xml.main_split_config)
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
index 5810e48..05ee9a1 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityBase.java
@@ -33,7 +33,6 @@
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.util.Consumer;
-import androidx.window.core.ExperimentalWindowApi;
 import androidx.window.embedding.ActivityFilter;
 import androidx.window.embedding.ActivityRule;
 import androidx.window.embedding.EmbeddingRule;
@@ -49,13 +48,10 @@
 import java.util.List;
 import java.util.Set;
 
-import kotlin.OptIn;
-
 /**
  * Sample showcase of split activity rules. Allows the user to select some split configuration
  * options with checkboxes and launch activities with those options applied.
  */
-@OptIn(markerClass = ExperimentalWindowApi.class)
 public class SplitActivityBase extends AppCompatActivity
         implements CompoundButton.OnCheckedChangeListener {
 
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
index 0c367c4..5ca237a 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityList.kt
@@ -24,13 +24,11 @@
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.ContextCompat
 import androidx.core.util.Consumer
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.SplitController
 import androidx.window.embedding.SplitInfo
 import androidx.window.sample.R
 import androidx.window.sample.embedding.SplitActivityDetail.Companion.EXTRA_SELECTED_ITEM
 
-@OptIn(ExperimentalWindowApi::class)
 open class SplitActivityList : AppCompatActivity() {
     lateinit var splitController: SplitController
     val splitChangeListener = SplitStateChangeListener()
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
index 67680f1..acb6565 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitActivityTrampoline.kt
@@ -18,7 +18,6 @@
 
 import android.content.Intent
 import android.os.Bundle
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.ActivityFilter
 import androidx.window.embedding.SplitController
 import androidx.window.embedding.SplitPlaceholderRule
@@ -27,7 +26,6 @@
 /**
  * Example trampoline activity that launches a split and finishes itself.
  */
-@ExperimentalWindowApi
 class SplitActivityTrampoline : SplitActivityBase() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
diff --git a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
index 2b278a0..e499df3 100644
--- a/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
+++ b/window/window-samples/src/main/java/androidx/window/sample/embedding/SplitPipActivityBase.kt
@@ -27,7 +27,6 @@
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.content.ContextCompat
 import androidx.core.util.Consumer
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.ActivityFilter
 import androidx.window.embedding.EmbeddingRule
 import androidx.window.embedding.SplitController
@@ -46,7 +45,6 @@
  * split and PiP configuration options with checkboxes and launch activities with those options
  * applied.
  */
-@OptIn(ExperimentalWindowApi::class)
 abstract class SplitPipActivityBase : AppCompatActivity(), CompoundButton.OnCheckedChangeListener,
     View.OnClickListener, RadioGroup.OnCheckedChangeListener {
 
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index aff36d9..e3dc726 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -8,6 +8,142 @@
 
 }
 
+package androidx.window.embedding {
+
+  public final class ActivityFilter {
+    ctor public ActivityFilter(android.content.ComponentName componentName, String? intentAction);
+    method public boolean matchesActivity(android.app.Activity activity);
+    method public <T extends android.app.Activity> boolean matchesClassName(Class<T> clazz);
+    method public <T extends android.app.Activity> boolean matchesClassNameOrWildCard(Class<T>? clazz);
+    method public boolean matchesIntent(android.content.Intent intent);
+  }
+
+  public final class ActivityRule extends androidx.window.embedding.EmbeddingRule {
+    method public boolean getAlwaysExpand();
+    method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+    property public final boolean alwaysExpand;
+    property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+  }
+
+  public static final class ActivityRule.Builder {
+    ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
+    method public androidx.window.embedding.ActivityRule build();
+    method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+  }
+
+  public final class ActivityStack {
+    ctor public ActivityStack(java.util.List<? extends android.app.Activity> activities, optional boolean isEmpty);
+    method public operator boolean contains(android.app.Activity activity);
+    method public boolean isEmpty();
+  }
+
+  public abstract class EmbeddingRule {
+  }
+
+  public final class SplitController {
+    method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+    method public void clearRegisteredRules();
+    method public static androidx.window.embedding.SplitController getInstance();
+    method public java.util.Set<androidx.window.embedding.EmbeddingRule> getSplitRules();
+    method public static void initialize(android.content.Context context, int staticRuleResourceId);
+    method public boolean isActivityEmbedded(android.app.Activity activity);
+    method public boolean isSplitSupported();
+    method public void registerRule(androidx.window.embedding.EmbeddingRule rule);
+    method public void removeSplitListener(androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+    method public void unregisterRule(androidx.window.embedding.EmbeddingRule rule);
+    field public static final androidx.window.embedding.SplitController.Companion Companion;
+  }
+
+  public static final class SplitController.Companion {
+    method public androidx.window.embedding.SplitController getInstance();
+    method public void initialize(android.content.Context context, int staticRuleResourceId);
+  }
+
+  public final class SplitInfo {
+    method public operator boolean contains(android.app.Activity activity);
+    method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
+    method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
+    method public float getSplitRatio();
+    property public final androidx.window.embedding.ActivityStack primaryActivityStack;
+    property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
+    property public final float splitRatio;
+  }
+
+  public final class SplitPairFilter {
+    ctor public SplitPairFilter(android.content.ComponentName primaryActivityName, android.content.ComponentName secondaryActivityName, String? secondaryActivityIntentAction);
+    method public android.content.ComponentName getPrimaryActivityName();
+    method public String? getSecondaryActivityIntentAction();
+    method public android.content.ComponentName getSecondaryActivityName();
+    method public boolean matchesActivityIntentPair(android.app.Activity primaryActivity, android.content.Intent secondaryActivityIntent);
+    method public boolean matchesActivityPair(android.app.Activity primaryActivity, android.app.Activity secondaryActivity);
+    property public final android.content.ComponentName primaryActivityName;
+    property public final String? secondaryActivityIntentAction;
+    property public final android.content.ComponentName secondaryActivityName;
+  }
+
+  public final class SplitPairRule extends androidx.window.embedding.SplitRule {
+    ctor @Deprecated public SplitPairRule(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, optional int finishPrimaryWithSecondary, optional int finishSecondaryWithPrimary, optional boolean clearTop, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth, optional @FloatRange(from=0.0, to=1.0) float splitRatio, optional int layoutDir);
+    method public boolean getClearTop();
+    method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
+    method public int getFinishPrimaryWithSecondary();
+    method public int getFinishSecondaryWithPrimary();
+    property public final boolean clearTop;
+    property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
+    property public final int finishPrimaryWithSecondary;
+    property public final int finishSecondaryWithPrimary;
+  }
+
+  public static final class SplitPairRule.Builder {
+    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    method public androidx.window.embedding.SplitPairRule build();
+    method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
+    method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
+    method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
+    method public androidx.window.embedding.SplitPairRule.Builder setLayoutDir(int layoutDir);
+    method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+  }
+
+  public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
+    ctor @Deprecated public SplitPlaceholderRule(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, boolean isSticky, optional int finishPrimaryWithPlaceholder, optional @IntRange(from=0L) int minWidth, optional @IntRange(from=0L) int minSmallestWidth, optional @FloatRange(from=0.0, to=1.0) float splitRatio, optional int layoutDirection);
+    method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+    method public int getFinishPrimaryWithPlaceholder();
+    method public android.content.Intent getPlaceholderIntent();
+    method public boolean isSticky();
+    property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+    property public final int finishPrimaryWithPlaceholder;
+    property public final boolean isSticky;
+    property public final android.content.Intent placeholderIntent;
+  }
+
+  public static final class SplitPlaceholderRule.Builder {
+    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    method public androidx.window.embedding.SplitPlaceholderRule build();
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDir(int layoutDir);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+  }
+
+  public class SplitRule extends androidx.window.embedding.EmbeddingRule {
+    method public final int getLayoutDirection();
+    method public final int getMinSmallestWidth();
+    method public final int getMinWidth();
+    method public final float getSplitRatio();
+    property public final int layoutDirection;
+    property public final int minSmallestWidth;
+    property public final int minWidth;
+    property public final float splitRatio;
+    field public static final androidx.window.embedding.SplitRule.Companion Companion;
+    field public static final int FINISH_ADJACENT = 2; // 0x2
+    field public static final int FINISH_ALWAYS = 1; // 0x1
+    field public static final int FINISH_NEVER = 0; // 0x0
+  }
+
+  public static final class SplitRule.Companion {
+  }
+
+}
+
 package androidx.window.layout {
 
   public interface DisplayFeature {
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index 6b7eeb8..7c76c1d 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -17,7 +17,7 @@
 
 package androidx.window.embedding {
 
-  @androidx.window.core.ExperimentalWindowApi public final class ActivityFilter {
+  public final class ActivityFilter {
     ctor public ActivityFilter(android.content.ComponentName componentName, String? intentAction);
     method public boolean matchesActivity(android.app.Activity activity);
     method public <T extends android.app.Activity> boolean matchesClassName(Class<T> clazz);
@@ -25,7 +25,7 @@
     method public boolean matchesIntent(android.content.Intent intent);
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class ActivityRule extends androidx.window.embedding.EmbeddingRule {
+  public final class ActivityRule extends androidx.window.embedding.EmbeddingRule {
     method public boolean getAlwaysExpand();
     method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
     property public final boolean alwaysExpand;
@@ -38,16 +38,16 @@
     method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class ActivityStack {
+  public final class ActivityStack {
     ctor public ActivityStack(java.util.List<? extends android.app.Activity> activities, optional boolean isEmpty);
     method public operator boolean contains(android.app.Activity activity);
     method public boolean isEmpty();
   }
 
-  @androidx.window.core.ExperimentalWindowApi public abstract class EmbeddingRule {
+  public abstract class EmbeddingRule {
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class SplitController {
+  public final class SplitController {
     method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
     method public void clearRegisteredRules();
     method public static androidx.window.embedding.SplitController getInstance();
@@ -66,7 +66,7 @@
     method public void initialize(android.content.Context context, int staticRuleResourceId);
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class SplitInfo {
+  public final class SplitInfo {
     method public operator boolean contains(android.app.Activity activity);
     method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
     method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
@@ -76,7 +76,7 @@
     property public final float splitRatio;
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class SplitPairFilter {
+  public final class SplitPairFilter {
     ctor public SplitPairFilter(android.content.ComponentName primaryActivityName, android.content.ComponentName secondaryActivityName, String? secondaryActivityIntentAction);
     method public android.content.ComponentName getPrimaryActivityName();
     method public String? getSecondaryActivityIntentAction();
@@ -88,7 +88,7 @@
     property public final android.content.ComponentName secondaryActivityName;
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class SplitPairRule extends androidx.window.embedding.SplitRule {
+  public final class SplitPairRule extends androidx.window.embedding.SplitRule {
     ctor @Deprecated public SplitPairRule(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, optional int finishPrimaryWithSecondary, optional int finishSecondaryWithPrimary, optional boolean clearTop, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth, optional @FloatRange(from=0.0, to=1.0) float splitRatio, optional int layoutDir);
     method public boolean getClearTop();
     method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
@@ -110,7 +110,7 @@
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
   }
 
-  @androidx.window.core.ExperimentalWindowApi public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
+  public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
     ctor @Deprecated public SplitPlaceholderRule(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, boolean isSticky, optional int finishPrimaryWithPlaceholder, optional @IntRange(from=0L) int minWidth, optional @IntRange(from=0L) int minSmallestWidth, optional @FloatRange(from=0.0, to=1.0) float splitRatio, optional int layoutDirection);
     method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
     method public int getFinishPrimaryWithPlaceholder();
@@ -131,7 +131,7 @@
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
   }
 
-  @androidx.window.core.ExperimentalWindowApi public class SplitRule extends androidx.window.embedding.EmbeddingRule {
+  public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
     method public final int getMinSmallestWidth();
     method public final int getMinWidth();
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index aff36d9..e3dc726 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -8,6 +8,142 @@
 
 }
 
+package androidx.window.embedding {
+
+  public final class ActivityFilter {
+    ctor public ActivityFilter(android.content.ComponentName componentName, String? intentAction);
+    method public boolean matchesActivity(android.app.Activity activity);
+    method public <T extends android.app.Activity> boolean matchesClassName(Class<T> clazz);
+    method public <T extends android.app.Activity> boolean matchesClassNameOrWildCard(Class<T>? clazz);
+    method public boolean matchesIntent(android.content.Intent intent);
+  }
+
+  public final class ActivityRule extends androidx.window.embedding.EmbeddingRule {
+    method public boolean getAlwaysExpand();
+    method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+    property public final boolean alwaysExpand;
+    property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+  }
+
+  public static final class ActivityRule.Builder {
+    ctor public ActivityRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters);
+    method public androidx.window.embedding.ActivityRule build();
+    method public androidx.window.embedding.ActivityRule.Builder setAlwaysExpand(boolean alwaysExpand);
+  }
+
+  public final class ActivityStack {
+    ctor public ActivityStack(java.util.List<? extends android.app.Activity> activities, optional boolean isEmpty);
+    method public operator boolean contains(android.app.Activity activity);
+    method public boolean isEmpty();
+  }
+
+  public abstract class EmbeddingRule {
+  }
+
+  public final class SplitController {
+    method public void addSplitListener(android.app.Activity activity, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+    method public void clearRegisteredRules();
+    method public static androidx.window.embedding.SplitController getInstance();
+    method public java.util.Set<androidx.window.embedding.EmbeddingRule> getSplitRules();
+    method public static void initialize(android.content.Context context, int staticRuleResourceId);
+    method public boolean isActivityEmbedded(android.app.Activity activity);
+    method public boolean isSplitSupported();
+    method public void registerRule(androidx.window.embedding.EmbeddingRule rule);
+    method public void removeSplitListener(androidx.core.util.Consumer<java.util.List<androidx.window.embedding.SplitInfo>> consumer);
+    method public void unregisterRule(androidx.window.embedding.EmbeddingRule rule);
+    field public static final androidx.window.embedding.SplitController.Companion Companion;
+  }
+
+  public static final class SplitController.Companion {
+    method public androidx.window.embedding.SplitController getInstance();
+    method public void initialize(android.content.Context context, int staticRuleResourceId);
+  }
+
+  public final class SplitInfo {
+    method public operator boolean contains(android.app.Activity activity);
+    method public androidx.window.embedding.ActivityStack getPrimaryActivityStack();
+    method public androidx.window.embedding.ActivityStack getSecondaryActivityStack();
+    method public float getSplitRatio();
+    property public final androidx.window.embedding.ActivityStack primaryActivityStack;
+    property public final androidx.window.embedding.ActivityStack secondaryActivityStack;
+    property public final float splitRatio;
+  }
+
+  public final class SplitPairFilter {
+    ctor public SplitPairFilter(android.content.ComponentName primaryActivityName, android.content.ComponentName secondaryActivityName, String? secondaryActivityIntentAction);
+    method public android.content.ComponentName getPrimaryActivityName();
+    method public String? getSecondaryActivityIntentAction();
+    method public android.content.ComponentName getSecondaryActivityName();
+    method public boolean matchesActivityIntentPair(android.app.Activity primaryActivity, android.content.Intent secondaryActivityIntent);
+    method public boolean matchesActivityPair(android.app.Activity primaryActivity, android.app.Activity secondaryActivity);
+    property public final android.content.ComponentName primaryActivityName;
+    property public final String? secondaryActivityIntentAction;
+    property public final android.content.ComponentName secondaryActivityName;
+  }
+
+  public final class SplitPairRule extends androidx.window.embedding.SplitRule {
+    ctor @Deprecated public SplitPairRule(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, optional int finishPrimaryWithSecondary, optional int finishSecondaryWithPrimary, optional boolean clearTop, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth, optional @FloatRange(from=0.0, to=1.0) float splitRatio, optional int layoutDir);
+    method public boolean getClearTop();
+    method public java.util.Set<androidx.window.embedding.SplitPairFilter> getFilters();
+    method public int getFinishPrimaryWithSecondary();
+    method public int getFinishSecondaryWithPrimary();
+    property public final boolean clearTop;
+    property public final java.util.Set<androidx.window.embedding.SplitPairFilter> filters;
+    property public final int finishPrimaryWithSecondary;
+    property public final int finishSecondaryWithPrimary;
+  }
+
+  public static final class SplitPairRule.Builder {
+    ctor public SplitPairRule.Builder(java.util.Set<androidx.window.embedding.SplitPairFilter> filters, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    method public androidx.window.embedding.SplitPairRule build();
+    method public androidx.window.embedding.SplitPairRule.Builder setClearTop(boolean clearTop);
+    method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
+    method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
+    method public androidx.window.embedding.SplitPairRule.Builder setLayoutDir(int layoutDir);
+    method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+  }
+
+  public final class SplitPlaceholderRule extends androidx.window.embedding.SplitRule {
+    ctor @Deprecated public SplitPlaceholderRule(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, boolean isSticky, optional int finishPrimaryWithPlaceholder, optional @IntRange(from=0L) int minWidth, optional @IntRange(from=0L) int minSmallestWidth, optional @FloatRange(from=0.0, to=1.0) float splitRatio, optional int layoutDirection);
+    method public java.util.Set<androidx.window.embedding.ActivityFilter> getFilters();
+    method public int getFinishPrimaryWithPlaceholder();
+    method public android.content.Intent getPlaceholderIntent();
+    method public boolean isSticky();
+    property public final java.util.Set<androidx.window.embedding.ActivityFilter> filters;
+    property public final int finishPrimaryWithPlaceholder;
+    property public final boolean isSticky;
+    property public final android.content.Intent placeholderIntent;
+  }
+
+  public static final class SplitPlaceholderRule.Builder {
+    ctor public SplitPlaceholderRule.Builder(java.util.Set<androidx.window.embedding.ActivityFilter> filters, android.content.Intent placeholderIntent, @IntRange(from=0L) int minWidth, @IntRange(from=0L) int minSmallestWidth);
+    method public androidx.window.embedding.SplitPlaceholderRule build();
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDir(int layoutDir);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setSticky(boolean isSticky);
+  }
+
+  public class SplitRule extends androidx.window.embedding.EmbeddingRule {
+    method public final int getLayoutDirection();
+    method public final int getMinSmallestWidth();
+    method public final int getMinWidth();
+    method public final float getSplitRatio();
+    property public final int layoutDirection;
+    property public final int minSmallestWidth;
+    property public final int minWidth;
+    property public final float splitRatio;
+    field public static final androidx.window.embedding.SplitRule.Companion Companion;
+    field public static final int FINISH_ADJACENT = 2; // 0x2
+    field public static final int FINISH_ALWAYS = 1; // 0x1
+    field public static final int FINISH_NEVER = 0; // 0x0
+  }
+
+  public static final class SplitRule.Companion {
+  }
+
+}
+
 package androidx.window.layout {
 
   public interface DisplayFeature {
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/ActivityFilterTest.kt b/window/window/src/androidTest/java/androidx/window/embedding/ActivityFilterTest.kt
index 3aa1026..1d9d74c 100644
--- a/window/window/src/androidTest/java/androidx/window/embedding/ActivityFilterTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/embedding/ActivityFilterTest.kt
@@ -19,7 +19,6 @@
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Intent
-import androidx.window.core.ExperimentalWindowApi
 import com.google.common.truth.Truth.assertWithMessage
 import com.nhaarman.mockitokotlin2.doReturn
 import com.nhaarman.mockitokotlin2.mock
@@ -31,7 +30,6 @@
 /**
  * Integration test for [ActivityFilter] to test construction from [ComponentName].
  */
-@OptIn(ExperimentalWindowApi::class)
 class ActivityFilterTest {
 
     private val intent = Intent()
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
index 7de3ec5..07b2d1e 100644
--- a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
+++ b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
@@ -23,7 +23,6 @@
 import android.graphics.Rect
 import android.util.LayoutDirection
 import androidx.test.core.app.ApplicationProvider
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.SplitRule.Companion.FINISH_ADJACENT
 import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
 import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
@@ -40,7 +39,6 @@
  * @see SplitRule
  * @see ActivityRule
  */
-@OptIn(ExperimentalWindowApi::class)
 class EmbeddingRuleConstructionTests {
     /**
      * Verifies that default params are set correctly when reading {@link SplitPairRule} from XML.
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/SplitPairFilterTest.kt b/window/window/src/androidTest/java/androidx/window/embedding/SplitPairFilterTest.kt
index 0ac37b0..408102b 100644
--- a/window/window/src/androidTest/java/androidx/window/embedding/SplitPairFilterTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/embedding/SplitPairFilterTest.kt
@@ -19,13 +19,11 @@
 import android.app.Activity
 import android.content.ComponentName
 import android.content.Intent
-import androidx.window.core.ExperimentalWindowApi
 import com.google.common.truth.Truth.assertWithMessage
 import com.nhaarman.mockitokotlin2.doReturn
 import com.nhaarman.mockitokotlin2.mock
 import org.junit.Test
 
-@OptIn(ExperimentalWindowApi::class)
 class SplitPairFilterTest {
     private val component1 = ComponentName("a.b.c", "a.b.c.TestActivity")
     private val component2 = ComponentName("d.e.f", "d.e.f.TestActivity")
diff --git a/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt b/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt
index e7ca275..afef875 100644
--- a/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt
+++ b/window/window/src/androidTest/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackendTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.window.layout.adapter.extensions
 
+import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
+import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
+import java.util.function.Consumer as JavaConsumer
 import android.annotation.SuppressLint
 import android.app.Activity
 import android.graphics.Rect
@@ -28,25 +31,23 @@
 import androidx.window.extensions.layout.FoldingFeature.STATE_FLAT
 import androidx.window.extensions.layout.FoldingFeature.TYPE_HINGE
 import androidx.window.extensions.layout.WindowLayoutComponent
+import androidx.window.layout.WindowLayoutInfo
+import androidx.window.layout.WindowMetricsCalculatorCompat
 import androidx.window.layout.adapter.extensions.ExtensionsWindowLayoutInfoAdapter.translate
 import com.nhaarman.mockitokotlin2.any
 import com.nhaarman.mockitokotlin2.argumentCaptor
 import com.nhaarman.mockitokotlin2.eq
 import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
 import com.nhaarman.mockitokotlin2.whenever
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
-import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
-import java.util.function.Consumer as JavaConsumer
-import androidx.window.layout.WindowLayoutInfo
-import androidx.window.layout.WindowMetricsCalculatorCompat
-import com.nhaarman.mockitokotlin2.times
 
 class ExtensionWindowLayoutInfoBackendTest {
 
@@ -149,6 +150,28 @@
             val consumerCaptor = argumentCaptor<JavaConsumer<OEMWindowLayoutInfo>>()
             verify(component).addWindowLayoutInfoListener(eq(activity), consumerCaptor.capture())
             verify(component).removeWindowLayoutInfoListener(consumerCaptor.firstValue)
+            assertFalse(backend.hasRegisteredListeners())
+        }
+    }
+
+    @Test
+    public fun testExtensionWindowBackend_removesMultipleCallback() {
+        val component = mock<WindowLayoutComponent>()
+
+        val backend = ExtensionWindowLayoutInfoBackend(component, consumerAdapter)
+
+        activityScenario.scenario.onActivity { activity ->
+            val consumer = TestConsumer<WindowLayoutInfo>()
+            val consumer2 = TestConsumer<WindowLayoutInfo>()
+            backend.registerLayoutChangeCallback(activity, Runnable::run, consumer)
+            backend.registerLayoutChangeCallback(activity, Runnable::run, consumer2)
+            backend.unregisterLayoutChangeCallback(consumer)
+            backend.unregisterLayoutChangeCallback(consumer2)
+
+            val consumerCaptor = argumentCaptor<JavaConsumer<OEMWindowLayoutInfo>>()
+            verify(component).addWindowLayoutInfoListener(eq(activity), consumerCaptor.capture())
+            verify(component).removeWindowLayoutInfoListener(consumerCaptor.firstValue)
+            assertFalse(backend.hasRegisteredListeners())
         }
     }
 
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityFilter.kt b/window/window/src/main/java/androidx/window/embedding/ActivityFilter.kt
index 9014a3d..54b6ebd 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityFilter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityFilter.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import android.util.Log
 import androidx.window.core.ActivityComponentInfo
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.MatcherUtils.isActivityOrIntentMatching
 import androidx.window.embedding.MatcherUtils.isIntentMatching
 import androidx.window.embedding.MatcherUtils.sDebugMatchers
@@ -31,7 +30,6 @@
  * end or instead of the package name, and a wildcard symbol in the end or instead of the class
  * name.
  */
-@ExperimentalWindowApi
 class ActivityFilter internal constructor(
     /**
      * Component name in the intent for the activity. Must be non-empty. Can contain a single
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt b/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt
index a76c5e6..45c928e 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityRule.kt
@@ -16,13 +16,10 @@
 
 package androidx.window.embedding
 
-import androidx.window.core.ExperimentalWindowApi
-
 /**
  * Layout configuration rules for individual activities with split layouts. Take precedence over
  * [SplitPairRule].
  */
-@ExperimentalWindowApi
 class ActivityRule internal constructor(
     /**
      * Filters used to choose when to apply this rule. The rule may be used if any one of the
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt b/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt
index 6ed10a5..676e78d 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityStack.kt
@@ -16,13 +16,11 @@
 package androidx.window.embedding
 
 import android.app.Activity
-import androidx.window.core.ExperimentalWindowApi
 
 /**
  * A container that holds a stack of activities, overlapping and bound to the same rectangle on the
  * screen.
  */
-@ExperimentalWindowApi
 class ActivityStack(
     /**
      * The [Activity] list in this application's process that belongs to this ActivityStack.
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
index 4116482..a845609 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
@@ -28,14 +28,12 @@
 import androidx.window.extensions.embedding.SplitPairRule.Builder as SplitPairRuleBuilder
 import androidx.window.extensions.embedding.SplitPlaceholderRule as OEMSplitPlaceholderRule
 import androidx.window.extensions.embedding.SplitPlaceholderRule.Builder as SplitPlaceholderRuleBuilder
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.core.PredicateAdapter
 import androidx.window.extensions.WindowExtensionsProvider
 
 /**
  * Adapter class that translates data classes between Extension and Jetpack interfaces.
  */
-@ExperimentalWindowApi
 internal class EmbeddingAdapter(
     private val predicateAdapter: PredicateAdapter
 ) {
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
index 9e420d5..cdb1f91 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
@@ -18,11 +18,9 @@
 
 import android.app.Activity
 import androidx.core.util.Consumer
-import androidx.window.core.ExperimentalWindowApi
 import java.util.concurrent.Executor
 
 // TODO(b/191164045): Move to window-testing or adapt for testing otherwise.
-@ExperimentalWindowApi
 internal interface EmbeddingBackend {
     fun setSplitRules(rules: Set<EmbeddingRule>)
 
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
index 5e5293e..85993d6 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
@@ -16,21 +16,19 @@
 
 package androidx.window.embedding
 
+import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
 import android.app.Activity
 import android.util.Log
 import androidx.window.core.ConsumerAdapter
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
 import androidx.window.extensions.WindowExtensionsProvider
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
 import java.lang.reflect.Proxy
-import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
 
 /**
  * Adapter implementation for different historical versions of activity embedding OEM interface in
  * [ActivityEmbeddingComponent]. Only supports the single current version in this implementation.
  */
-@ExperimentalWindowApi
 internal class EmbeddingCompat constructor(
     private val embeddingExtension: ActivityEmbeddingComponent,
     private val adapter: EmbeddingAdapter,
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
index 33f8f7c..7c0c08c 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
@@ -17,14 +17,12 @@
 package androidx.window.embedding
 
 import android.app.Activity
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
 
 /**
  * Adapter interface for different historical versions of activity embedding OEM interface in
  * [ActivityEmbeddingComponent].
  */
-@ExperimentalWindowApi
 internal interface EmbeddingInterfaceCompat {
 
     fun setSplitRules(rules: Set<EmbeddingRule>)
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt
index 6347b3a..e3ff7dd 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingRule.kt
@@ -16,11 +16,8 @@
 
 package androidx.window.embedding
 
-import androidx.window.core.ExperimentalWindowApi
-
 /**
  * Base abstract class for activity embedding presentation rules, such as [SplitPairRule] and
  * [ActivityRule]. Allows grouping different rule types together when updating.
  */
-@ExperimentalWindowApi
-abstract class EmbeddingRule internal constructor()
\ No newline at end of file
+abstract class EmbeddingRule internal constructor()
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index fe412cf..5c2d3c1 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.VisibleForTesting
 import androidx.core.util.Consumer
 import androidx.window.core.ConsumerAdapter
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.core.ExtensionsUtil
 import androidx.window.core.PredicateAdapter
 import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
@@ -32,7 +31,6 @@
 import java.util.concurrent.locks.ReentrantLock
 import kotlin.concurrent.withLock
 
-@ExperimentalWindowApi
 internal class ExtensionEmbeddingBackend @VisibleForTesting constructor(
     @field:VisibleForTesting @field:GuardedBy(
         "globalLock"
diff --git a/window/window/src/main/java/androidx/window/embedding/MatcherUtils.kt b/window/window/src/main/java/androidx/window/embedding/MatcherUtils.kt
index 71cace1..eb61516 100644
--- a/window/window/src/main/java/androidx/window/embedding/MatcherUtils.kt
+++ b/window/window/src/main/java/androidx/window/embedding/MatcherUtils.kt
@@ -20,12 +20,10 @@
 import android.content.Intent
 import android.util.Log
 import androidx.window.core.ActivityComponentInfo
-import androidx.window.core.ExperimentalWindowApi
 
 /**
  * Internal utils used for matching activities with embedding rules.
  */
-@ExperimentalWindowApi
 internal object MatcherUtils {
     /** Checks component match allowing wildcard patterns. */
     internal fun areComponentsMatching(
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitController.kt b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
index cf5aa4a..5d45231 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
@@ -19,7 +19,7 @@
 import android.app.Activity
 import android.content.Context
 import androidx.core.util.Consumer
-import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.SplitController.Companion
 import java.util.concurrent.Executor
 import java.util.concurrent.locks.ReentrantLock
 import kotlin.concurrent.withLock
@@ -36,7 +36,6 @@
  * [androidx.startup.Initializer] and [Companion.initialize]. See Jetpack App Startup reference
  * for more information.
  */
-@ExperimentalWindowApi
 class SplitController private constructor() {
     private val embeddingBackend: EmbeddingBackend = ExtensionEmbeddingBackend.getInstance()
     private var staticSplitRules: Set<EmbeddingRule> = emptySet()
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt b/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt
index b113a62..7606d64 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitInfo.kt
@@ -17,10 +17,8 @@
 package androidx.window.embedding
 
 import android.app.Activity
-import androidx.window.core.ExperimentalWindowApi
 
 /** Describes a split pair of two containers with activities. */
-@ExperimentalWindowApi
 class SplitInfo internal constructor(
     val primaryActivityStack: ActivityStack,
     val secondaryActivityStack: ActivityStack,
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt
index 4e09377..95e1625 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt
@@ -20,7 +20,6 @@
 import android.content.Intent
 import android.util.Log
 import androidx.window.core.ActivityComponentInfo
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.MatcherUtils.areComponentsMatching
 import androidx.window.embedding.MatcherUtils.isIntentMatching
 import androidx.window.embedding.MatcherUtils.sDebugMatchers
@@ -30,7 +29,6 @@
  * Filter used to find if a pair of activities should be put in a split. Applied to the base /
  * primary activity and an intent starting a secondary activity.
  */
-@ExperimentalWindowApi
 class SplitPairFilter(
     /**
      * Component name of the primary activity in the split. Must be non-empty. Can contain a single
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
index 49e6681..de62416 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
@@ -21,7 +21,6 @@
 import androidx.annotation.IntRange
 import androidx.core.util.Preconditions.checkArgument
 import androidx.core.util.Preconditions.checkArgumentNonnegative
-import androidx.window.core.ExperimentalWindowApi
 
 /**
  * Split configuration rules for activity pairs. Define when activities that were launched on top of
@@ -31,7 +30,6 @@
  * belong to the same application and are running in the same process. The rules are  always
  * applied only to activities that will be started  after the rules were set.
  */
-@ExperimentalWindowApi
 class SplitPairRule : SplitRule {
 
     /**
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
index c12b7b7..ae4b432 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
@@ -23,12 +23,10 @@
 import androidx.annotation.IntRange
 import androidx.core.util.Preconditions.checkArgument
 import androidx.core.util.Preconditions.checkArgumentNonnegative
-import androidx.window.core.ExperimentalWindowApi
 
 /**
  * Configuration rules for split placeholders.
  */
-@ExperimentalWindowApi
 class SplitPlaceholderRule : SplitRule {
 
     /**
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
index 50916c7..0bda0a0 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
@@ -25,7 +25,6 @@
 import androidx.annotation.IntDef
 import androidx.annotation.IntRange
 import androidx.annotation.RequiresApi
-import androidx.window.core.ExperimentalWindowApi
 import kotlin.math.min
 
 /**
@@ -36,7 +35,6 @@
  * belong to the same application and are running in the same process. The rules are always
  * applied only to activities that will be started  after the rules were set.
  */
-@ExperimentalWindowApi
 open class SplitRule internal constructor(
     /**
      * The smallest value of width of the parent window when the split should be used, in pixels.
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt b/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt
index 8e086c9..b9b8ea1 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRuleParser.kt
@@ -24,7 +24,6 @@
 import android.util.LayoutDirection
 
 import androidx.window.R
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
 import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
 
@@ -33,7 +32,6 @@
 /**
  * Parses the static split rules defined in XML.
  */
-@ExperimentalWindowApi
 internal class SplitRuleParser {
 
     internal fun parseSplitRules(context: Context, staticRuleResourceId: Int): Set<EmbeddingRule>? {
diff --git a/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt b/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt
index beebba0..2c304b4 100644
--- a/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt
+++ b/window/window/src/main/java/androidx/window/layout/adapter/extensions/ExtensionWindowLayoutInfoBackend.kt
@@ -19,6 +19,7 @@
 import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
 import android.app.Activity
 import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
 import androidx.core.util.Consumer
 import androidx.window.core.ConsumerAdapter
 import androidx.window.extensions.layout.WindowLayoutComponent
@@ -93,15 +94,24 @@
             val activity = listenerToActivity[callback] ?: return
             val multicastListener = activityToListeners[activity] ?: return
             multicastListener.removeListener(callback)
+            listenerToActivity.remove(callback)
             if (multicastListener.isEmpty()) {
                 consumerToToken.remove(multicastListener)?.dispose()
-                listenerToActivity.remove(callback)
                 activityToListeners.remove(activity)
             }
         }
     }
 
     /**
+     * Returns {@code true} there is any registered listener information, {@code false} otherwise.
+     */
+    @VisibleForTesting
+    fun hasRegisteredListeners(): Boolean {
+        return !(activityToListeners.isEmpty() && listenerToActivity.isEmpty() &&
+            consumerToToken.isEmpty())
+    }
+
+    /**
      * A class that implements [Consumer] by aggregating multiple instances of [Consumer]
      * and multicasting each value that is consumed. [MulticastConsumer] also replays the last known
      * value whenever a new consumer registers.
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityFilterTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityFilterTest.kt
index c54e6f3..6d06d87 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityFilterTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityFilterTest.kt
@@ -18,7 +18,6 @@
 
 import android.app.Activity
 import androidx.window.core.ActivityComponentInfo
-import androidx.window.core.ExperimentalWindowApi
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -27,7 +26,6 @@
 /**
  * The unit tests for [ActivityFilter] that will test construction.
  */
-@OptIn(ExperimentalWindowApi::class)
 class ActivityFilterTest {
 
     @Test(expected = IllegalArgumentException::class)
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt
index beb20e3..aa266a3 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityRuleTest.kt
@@ -17,13 +17,11 @@
 package androidx.window.embedding
 
 import androidx.window.core.ActivityComponentInfo
-import androidx.window.core.ExperimentalWindowApi
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
 
-@OptIn(ExperimentalWindowApi::class)
 class ActivityRuleTest {
 
     @Test
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityStackTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityStackTest.kt
index 2ef71b1..1af074c 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityStackTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityStackTest.kt
@@ -17,13 +17,11 @@
 package androidx.window.embedding
 
 import android.app.Activity
-import androidx.window.core.ExperimentalWindowApi
-import org.mockito.kotlin.mock
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Test
+import org.mockito.kotlin.mock
 
-@OptIn(ExperimentalWindowApi::class)
 class ActivityStackTest {
 
     @Test
diff --git a/window/window/src/test/java/androidx/window/embedding/MatcherUtilsTest.kt b/window/window/src/test/java/androidx/window/embedding/MatcherUtilsTest.kt
index 325bcf4..085a9d0 100644
--- a/window/window/src/test/java/androidx/window/embedding/MatcherUtilsTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/MatcherUtilsTest.kt
@@ -17,7 +17,6 @@
 package androidx.window.embedding
 
 import androidx.window.core.ActivityComponentInfo
-import androidx.window.core.ExperimentalWindowApi
 import androidx.window.embedding.MatcherUtils.areComponentsMatching
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -26,7 +25,6 @@
 /**
  * Unit tests for [MatcherUtils].
  */
-@OptIn(ExperimentalWindowApi::class)
 class MatcherUtilsTest {
 
     @Test
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
index e642d25..8f15b16 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitInfoTest.kt
@@ -17,13 +17,11 @@
 package androidx.window.embedding
 
 import android.app.Activity
-import androidx.window.core.ExperimentalWindowApi
-import org.mockito.kotlin.mock
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Test
+import org.mockito.kotlin.mock
 
-@OptIn(ExperimentalWindowApi::class)
 class SplitInfoTest {
 
     @Test
diff --git a/work/work-runtime/build.gradle b/work/work-runtime/build.gradle
index ca651ca..83a47ba 100644
--- a/work/work-runtime/build.gradle
+++ b/work/work-runtime/build.gradle
@@ -80,6 +80,7 @@
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy) // DexMaker has its own MockMaker
     androidTestImplementation(project(":internal-testutils-runtime"))
     testImplementation(libs.junit)
+    testImplementation(libs.truth)
     lintPublish(project(":work:work-runtime-lint"))
 }
 
diff --git a/work/work-runtime/src/main/java/androidx/work/InputMerger.java b/work/work-runtime/src/main/java/androidx/work/InputMerger.java
index a59805c..695e865 100644
--- a/work/work-runtime/src/main/java/androidx/work/InputMerger.java
+++ b/work/work-runtime/src/main/java/androidx/work/InputMerger.java
@@ -62,7 +62,7 @@
     public static InputMerger fromClassName(@NonNull String className) {
         try {
             Class<?> clazz = Class.forName(className);
-            return (InputMerger) clazz.newInstance();
+            return (InputMerger) clazz.getDeclaredConstructor().newInstance();
         } catch (Exception e) {
             Logger.get().error(TAG, "Trouble instantiating + " + className, e);
         }
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkInfo.java b/work/work-runtime/src/main/java/androidx/work/WorkInfo.java
index 19a9821..7560af3 100644
--- a/work/work-runtime/src/main/java/androidx/work/WorkInfo.java
+++ b/work/work-runtime/src/main/java/androidx/work/WorkInfo.java
@@ -145,6 +145,7 @@
         WorkInfo workInfo = (WorkInfo) o;
 
         if (mRunAttemptCount != workInfo.mRunAttemptCount) return false;
+        if (mGeneration != workInfo.mGeneration) return false;
         if (!mId.equals(workInfo.mId)) return false;
         if (mState != workInfo.mState) return false;
         if (!mOutputData.equals(workInfo.mOutputData)) return false;
@@ -160,6 +161,7 @@
         result = 31 * result + mTags.hashCode();
         result = 31 * result + mProgress.hashCode();
         result = 31 * result + mRunAttemptCount;
+        result = 31 * result + mGeneration;
         return result;
     }
 
diff --git a/work/work-runtime/src/test/java/androidx/work/WorkInfoTest.kt b/work/work-runtime/src/test/java/androidx/work/WorkInfoTest.kt
new file mode 100644
index 0000000..c45eb03
--- /dev/null
+++ b/work/work-runtime/src/test/java/androidx/work/WorkInfoTest.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.work
+
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class WorkInfoTest {
+
+    @Test
+    fun testEqualityWithGeneration() {
+        val id = UUID.randomUUID()
+        val info1 = WorkInfo(id, WorkInfo.State.RUNNING, Data.EMPTY, listOf("a"), Data.EMPTY, 0, 1)
+        val info2 = WorkInfo(id, WorkInfo.State.RUNNING, Data.EMPTY, listOf("a"), Data.EMPTY, 0, 4)
+        assertThat(info1 == info2).isFalse()
+    }
+}
\ No newline at end of file