Merge "Rename some string resources to prevent name collision" into androidx-main
diff --git a/activity/integration-tests/testapp/build.gradle b/activity/integration-tests/testapp/build.gradle
index 7799f8e..f7680a9c 100644
--- a/activity/integration-tests/testapp/build.gradle
+++ b/activity/integration-tests/testapp/build.gradle
@@ -29,8 +29,7 @@
 
 dependencies {
     implementation(project(":activity:activity-ktx"))
-    implementation(projectOrArtifact(":lifecycle:lifecycle-runtime-ktx"))
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-common"))
+    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
     implementation("androidx.appcompat:appcompat:1.6.0")
     implementation("androidx.core:core-splashscreen:1.0.0")
     androidTestImplementation(libs.kotlinStdlib)
diff --git a/appactions/interaction/integration-tests/testapp/build.gradle b/appactions/interaction/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..eb817cc
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.application")
+    id("kotlin-android")
+}
+
+android {
+    defaultConfig {
+        applicationId "androidx.appactions.interaction.testapp"
+        minSdkVersion 26
+    }
+    namespace "androidx.appactions.interaction.testapp"
+}
+
+dependencies {
+    implementation(project(":appactions:interaction:interaction-service"))
+    implementation("androidx.core:core-ktx:1.7.0")
+    implementation("androidx.appcompat:appcompat:1.6.1")
+    implementation("com.google.android.material:material:1.6.0")
+    implementation("androidx.constraintlayout:constraintlayout:2.0.1")
+}
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml b/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0ad13f3
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+  <application
+      android:allowBackup="true"
+      android:dataExtractionRules="@xml/data_extraction_rules"
+      android:fullBackupContent="@xml/backup_rules"
+      android:icon="@mipmap/ic_launcher"
+      android:label="@string/app_name"
+      android:roundIcon="@mipmap/ic_launcher_round"
+      android:supportsRtl="true"
+      android:theme="@style/Theme.AppInteractionTest"
+      tools:targetApi="31">
+    <activity
+        android:name=".MainActivity"
+        android:exported="true">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+
+      <meta-data
+          android:name="android.app.lib_name"
+          android:value=""/>
+    </activity>
+  </application>
+
+</manifest>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt
new file mode 100644
index 0000000..9f21059
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/java/androidx/appactions/interaction/testapp/MainActivity.kt
@@ -0,0 +1,60 @@
+package androidx.appactions.interaction.testapp
+
+import android.os.Bundle
+import android.os.CountDownTimer
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatButton
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+
+class MainActivity : AppCompatActivity() {
+  private var duration = 59
+  private var hasRunningTimer = false
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    setContentView(R.layout.activity_main)
+
+    val hours: TextView = findViewById(R.id.hour)
+    val mins: TextView = findViewById(R.id.minute)
+    val seconds: TextView = findViewById(R.id.second)
+    val startButton: AppCompatButton = findViewById(R.id.startButton)
+
+    startButton.setOnClickListener {
+      if (!hasRunningTimer) {
+        hasRunningTimer = true
+        object : CountDownTimer((duration * 1000).toLong(), 1000) {
+          override fun onTick(millisUntilFinished: Long) {
+            runOnUiThread {
+              val time = String.format(
+                Locale.getDefault(),
+                "%02d:%02d:%02d",
+                TimeUnit.MILLISECONDS.toHours(millisUntilFinished),
+                TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(
+                  TimeUnit.MILLISECONDS.toHours(millisUntilFinished)
+                ),
+                TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds(
+                  TimeUnit.MILLISECONDS.toHours(millisUntilFinished)
+                )
+              )
+              val hourMinSecArray = time.split(":")
+              hours.text = hourMinSecArray[0]
+              mins.text = hourMinSecArray[1]
+              seconds.text = hourMinSecArray[2]
+            }
+          }
+
+          override fun onFinish() {
+            // Reset timer duration
+            duration = 59
+            hasRunningTimer = false
+          }
+        }.start()
+      } else {
+        Toast.makeText(this@MainActivity, "Timer is already running", Toast.LENGTH_SHORT).show()
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/drawable/ic_launcher_background.xml b/appactions/interaction/integration-tests/testapp/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..2be47c2
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+  <path android:fillColor="#3DDC84"
+      android:pathData="M0,0h108v108h-108z"/>
+  <path android:fillColor="#00000000" android:pathData="M9,0L9,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,0L19,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M29,0L29,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M39,0L39,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M49,0L49,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M59,0L59,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M69,0L69,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M79,0L79,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M89,0L89,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M99,0L99,108"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,9L108,9"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,19L108,19"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,29L108,29"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,39L108,39"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,49L108,49"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,59L108,59"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,69L108,69"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,79L108,79"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,89L108,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M0,99L108,99"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,29L89,29"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,39L89,39"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,49L89,49"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,59L89,59"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,69L89,69"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M19,79L89,79"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M29,19L29,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M39,19L39,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M49,19L49,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M59,19L59,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M69,19L69,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+  <path android:fillColor="#00000000" android:pathData="M79,19L79,89"
+      android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
+</vector>
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/drawable/round_back_black10_10.xml b/appactions/interaction/integration-tests/testapp/src/main/res/drawable/round_back_black10_10.xml
new file mode 100644
index 0000000..0d05958
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/drawable/round_back_black10_10.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <solid android:color="@color/cardview_dark_background"/>
+  <corners android:radius="10dp"/>
+</shape>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml b/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..305819b
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/primary"
+    tools:context=".MainActivity">
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="200dp"
+      android:gravity="center"
+      android:orientation="horizontal">
+    <TextView
+        android:id="@+id/hour"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:background="@drawable/round_back_black10_10"
+        android:text="00"
+        android:textColor="#FFFFFF"
+        android:textStyle="bold"
+        android:textSize="30sp"
+        android:gravity="center"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text=":"
+        android:textColor="#FFFFFF"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        android:textSize="30sp"
+        android:textStyle="bold"/>
+    <TextView
+        android:id="@+id/minute"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:background="@drawable/round_back_black10_10"
+        android:text="00"
+        android:textColor="#FFFFFF"
+        android:textStyle="bold"
+        android:textSize="30sp"
+        android:gravity="center"/>
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text=":"
+        android:textColor="#FFFFFF"
+        android:layout_marginStart="10dp"
+        android:layout_marginEnd="10dp"
+        android:textSize="30sp"
+        android:textStyle="bold"/>
+    <TextView
+        android:id="@+id/second"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:background="@drawable/round_back_black10_10"
+        android:text="00"
+        android:textColor="#FFFFFF"
+        android:textStyle="bold"
+        android:textSize="30sp"
+        android:gravity="center"/>
+  </LinearLayout>
+  <androidx.appcompat.widget.AppCompatButton
+      android:id="@+id/startButton"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:background="@color/secondary"
+      android:text="Start"
+      android:textColor="@color/white"
+      android:textAllCaps="true"
+    android:layout_alignParentBottom="true"
+      android:layout_centerHorizontal="true"
+      android:layout_marginBottom="100dp"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/values-night/themes.xml b/appactions/interaction/integration-tests/testapp/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..cf79c9e
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/values-night/themes.xml
@@ -0,0 +1,17 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+  <!-- Base application theme. -->
+  <style name="Theme.AppInteractionTest"
+      parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+    <!-- Primary brand color. -->
+    <item name="colorPrimary">@color/purple_200</item>
+    <item name="colorPrimaryVariant">@color/purple_700</item>
+    <item name="colorOnPrimary">@color/black</item>
+    <!-- Secondary brand color. -->
+    <item name="colorSecondary">@color/teal_200</item>
+    <item name="colorSecondaryVariant">@color/teal_200</item>
+    <item name="colorOnSecondary">@color/black</item>
+    <!-- Status bar color. -->
+    <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+    <!-- Customize your theme here. -->
+  </style>
+</resources>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/values/colors.xml b/appactions/interaction/integration-tests/testapp/src/main/res/values/colors.xml
new file mode 100644
index 0000000..fc67530
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/values/colors.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="purple_200">#FFBB86FC</color>
+  <color name="purple_500">#FF6200EE</color>
+  <color name="purple_700">#FF3700B3</color>
+  <color name="teal_200">#FF03DAC5</color>
+  <color name="teal_700">#FF018786</color>
+  <color name="black">#FF000000</color>
+  <color name="white">#FFFFFFFF</color>
+
+  <color name="primary">#1E1B1C</color>
+  <color name="secondary">@color/teal_700</color>
+</resources>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml b/appactions/interaction/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
new file mode 100644
index 0000000..37ed16e7
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
@@ -0,0 +1,3 @@
+<resources>
+  <string name="app_name">App Interaction Test</string>
+</resources>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/values/themes.xml b/appactions/interaction/integration-tests/testapp/src/main/res/values/themes.xml
new file mode 100644
index 0000000..22e6872
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/values/themes.xml
@@ -0,0 +1,17 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+  <!-- Base application theme. -->
+  <style name="Theme.AppInteractionTest"
+      parent="Theme.MaterialComponents.DayNight.NoActionBar">
+    <!-- Primary brand color. -->
+    <item name="colorPrimary">@color/purple_500</item>
+    <item name="colorPrimaryVariant">@color/purple_700</item>
+    <item name="colorOnPrimary">@color/white</item>
+    <!-- Secondary brand color. -->
+    <item name="colorSecondary">@color/teal_200</item>
+    <item name="colorSecondaryVariant">@color/teal_700</item>
+    <item name="colorOnSecondary">@color/black</item>
+    <!-- Status bar color. -->
+    <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+    <!-- Customize your theme here. -->
+  </style>
+</resources>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/xml/backup_rules.xml b/appactions/interaction/integration-tests/testapp/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..7a180f4
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+  <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/appactions/interaction/integration-tests/testapp/src/main/res/xml/data_extraction_rules.xml b/appactions/interaction/integration-tests/testapp/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..84910a7
--- /dev/null
+++ b/appactions/interaction/integration-tests/testapp/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+  <cloud-backup>
+    <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+  </cloud-backup>
+  <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt
index 015147a..0f1bccb 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.interaction.capabilities.core.ExecutionCallback
 import androidx.appactions.interaction.capabilities.core.ExecutionResult
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
+import androidx.appactions.interaction.capabilities.core.impl.utils.invokeExternalSuspendBlock
 import androidx.appactions.interaction.proto.AppActionsContext.AppDialogState
 import androidx.appactions.interaction.proto.FulfillmentResponse
 import androidx.appactions.interaction.proto.ParamValue
@@ -75,9 +76,12 @@
             try {
                 mutex.lock(owner = this@SingleTurnCapabilitySession)
                 UiHandleRegistry.registerUiHandle(uiHandle, sessionId)
-                val output = executionCallback.onExecute(arguments)
+                val output = invokeExternalSuspendBlock("onExecute") {
+                    executionCallback.onExecute(arguments)
+                }
                 callback.onSuccess(convertToFulfillmentResponse(output))
             } catch (t: Throwable) {
+                // TODO(b/276354491) add fine-grained error handling
                 callback.onError(ErrorStatusInternal.CANCELLED)
             } finally {
                 UiHandleRegistry.unregisterUiHandle(uiHandle)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
index f48067b..efacbfb4 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
@@ -33,7 +33,6 @@
 import androidx.appactions.interaction.capabilities.core.SearchAction;
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
 import androidx.appactions.interaction.capabilities.core.properties.StringValue;
-import androidx.appactions.interaction.capabilities.core.values.EntityValue;
 import androidx.appactions.interaction.proto.Entity;
 import androidx.appactions.interaction.proto.ParamValue;
 
@@ -187,25 +186,6 @@
     public static final ParamValueConverter<Boolean> BOOLEAN_PARAM_VALUE_CONVERTER =
             ParamValueConverter.of(TypeSpec.BOOL_TYPE_SPEC);
 
-    public static final ParamValueConverter<EntityValue> ENTITY_PARAM_VALUE_CONVERTER =
-            new ParamValueConverter<EntityValue>() {
-                @NonNull
-                @Override
-                public ParamValue toParamValue(EntityValue value) {
-                    throw new IllegalStateException(
-                            "EntityValue should never be sent back to " + "Assistant.");
-                }
-
-                @Override
-                public EntityValue fromParamValue(@NonNull ParamValue paramValue) {
-                    EntityValue.Builder value = EntityValue.newBuilder();
-                    if (paramValue.hasIdentifier()) {
-                        value.setId(paramValue.getIdentifier());
-                    }
-                    value.setValue(paramValue.getStringValue());
-                    return value.build();
-                }
-            };
     public static final ParamValueConverter<String> STRING_PARAM_VALUE_CONVERTER =
             ParamValueConverter.of(TypeSpec.STRING_TYPE_SPEC);
 
@@ -358,19 +338,6 @@
                             String.format("Unknown enum format '%s'.", identifier));
                 }
             };
-    public static final EntityConverter<
-            androidx.appactions.interaction.capabilities.core.properties.Entity>
-            ENTITY_ENTITY_CONVERTER =
-                    (entity) -> {
-                        Entity.Builder builder =
-                                Entity.newBuilder()
-                                        .setName(entity.getName())
-                                        .addAllAlternateNames(entity.getAlternateNames());
-                        if (entity.getId() != null) {
-                            builder.setIdentifier(entity.getId());
-                        }
-                        return builder.build();
-                    };
     public static final EntityConverter<StringValue> STRING_VALUE_ENTITY_CONVERTER =
             (stringValue) ->
                     Entity.newBuilder()
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/exceptions/ExternalException.kt
similarity index 63%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/exceptions/ExternalException.kt
index dcdff02..4147e62 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/exceptions/ExternalException.kt
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package androidx.appactions.interaction.capabilities.core.impl.exceptions
 
-import androidx.annotation.RestrictTo;
+/**
+ * This exception wraps an externally thrown exception.
+ *
+ * For example, an exception occurred in a listener or some method implemented as part
+ * of some Session interface.
+ */
+class ExternalException(message: String, cause: Throwable) : Exception(message, cause)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/GenericResolverInternal.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/GenericResolverInternal.kt
index 34f580b..591d9bb 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/GenericResolverInternal.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/GenericResolverInternal.kt
@@ -20,13 +20,14 @@
 import androidx.appactions.interaction.capabilities.core.EntitySearchResult
 import androidx.appactions.interaction.capabilities.core.InventoryListListener
 import androidx.appactions.interaction.capabilities.core.InventoryListener
+import androidx.appactions.interaction.capabilities.core.SearchAction
 import androidx.appactions.interaction.capabilities.core.ValidationResult
 import androidx.appactions.interaction.capabilities.core.ValueListener
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.SlotTypeConverter
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
 import androidx.appactions.interaction.capabilities.core.impl.task.exceptions.InvalidResolverException
-import androidx.appactions.interaction.capabilities.core.SearchAction
+import androidx.appactions.interaction.capabilities.core.impl.utils.invokeExternalSuspendBlock
 import androidx.appactions.interaction.proto.ParamValue
 
 /**
@@ -44,18 +45,22 @@
     val appEntity: AppEntityListener<ValueTypeT>? = null,
     val appEntityList: AppEntityListListener<ValueTypeT>? = null,
     val inventory: InventoryListener<ValueTypeT>? = null,
-    val inventoryList: InventoryListListener<ValueTypeT>? = null,
+    val inventoryList: InventoryListListener<ValueTypeT>? = null
 ) {
 
     /** Wrapper which should invoke the `lookupAndRender` provided by the developer. */
     @Throws(InvalidResolverException::class)
     suspend fun invokeLookup(
-        searchAction: SearchAction<ValueTypeT>,
+        searchAction: SearchAction<ValueTypeT>
     ): EntitySearchResult<ValueTypeT> {
         return if (appEntity != null) {
-            appEntity.lookupAndRender(searchAction)
+            invokeExternalSuspendBlock("lookupAndRender") {
+                appEntity.lookupAndRender(searchAction)
+            }
         } else if (appEntityList != null) {
-            appEntityList.lookupAndRender(searchAction)
+            invokeExternalSuspendBlock("lookupAndRender") {
+                appEntityList.lookupAndRender(searchAction)
+            }
         } else {
             throw InvalidResolverException("invokeLookup is not supported on this resolver")
         }
@@ -68,9 +73,13 @@
     @Throws(InvalidResolverException::class)
     suspend fun invokeEntityRender(entityIds: List<String>) {
         if (inventory != null) {
-            inventory.renderChoices(entityIds)
+            invokeExternalSuspendBlock("renderChoices") {
+                inventory.renderChoices(entityIds)
+            }
         } else if (inventoryList != null) {
-            inventoryList.renderChoices(entityIds)
+            invokeExternalSuspendBlock("renderChoices") {
+                inventoryList.renderChoices(entityIds)
+            }
         } else {
             throw InvalidResolverException("invokeEntityRender is not supported on this resolver")
         }
@@ -83,22 +92,24 @@
     @Throws(StructConversionException::class)
     suspend fun notifyValueChange(
         paramValues: List<ParamValue>,
-        converter: ParamValueConverter<ValueTypeT>,
+        converter: ParamValueConverter<ValueTypeT>
     ): ValidationResult {
         val singularConverter = SlotTypeConverter.ofSingular(converter)
         val repeatedConverter = SlotTypeConverter.ofRepeated(converter)
-        return when {
-            value != null -> value.onReceived(singularConverter.convert(paramValues))
-            valueList != null -> valueList.onReceived(repeatedConverter.convert(paramValues))
-            appEntity != null ->
-                appEntity.onReceived(singularConverter.convert(paramValues))
-            appEntityList != null ->
-                appEntityList.onReceived(repeatedConverter.convert(paramValues))
-            inventory != null ->
-                inventory.onReceived(singularConverter.convert(paramValues))
-            inventoryList != null ->
-                inventoryList.onReceived(repeatedConverter.convert(paramValues))
-            else -> throw IllegalStateException("unreachable")
+        return invokeExternalSuspendBlock("onReceived") {
+            when {
+                value != null -> value.onReceived(singularConverter.convert(paramValues))
+                valueList != null -> valueList.onReceived(repeatedConverter.convert(paramValues))
+                appEntity != null ->
+                    appEntity.onReceived(singularConverter.convert(paramValues))
+                appEntityList != null ->
+                    appEntityList.onReceived(repeatedConverter.convert(paramValues))
+                inventory != null ->
+                    inventory.onReceived(singularConverter.convert(paramValues))
+                inventoryList != null ->
+                    inventoryList.onReceived(repeatedConverter.convert(paramValues))
+                else -> throw IllegalStateException("unreachable")
+            }
         }
     }
 
@@ -113,14 +124,14 @@
             GenericResolverInternal(appEntity = appEntity)
 
         fun <ValueTypeT> fromAppEntityListListener(
-            appEntityList: AppEntityListListener<ValueTypeT>,
+            appEntityList: AppEntityListListener<ValueTypeT>
         ) = GenericResolverInternal(appEntityList = appEntityList)
 
         fun <ValueTypeT> fromInventoryListener(inventory: InventoryListener<ValueTypeT>) =
             GenericResolverInternal(inventory = inventory)
 
         fun <ValueTypeT> fromInventoryListListener(
-            inventoryList: InventoryListListener<ValueTypeT>,
+            inventoryList: InventoryListListener<ValueTypeT>
         ) = GenericResolverInternal(inventoryList = inventoryList)
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilitySession.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilitySession.kt
index 07ae86c..168ab619 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilitySession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskCapabilitySession.kt
@@ -57,7 +57,6 @@
         }
 
     override fun destroy() {
-        // TODO(b/270751989): cancel current processing request immediately
         this.sessionOrchestrator.terminate()
         scope.cancel()
     }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskOrchestrator.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskOrchestrator.kt
index 190b203..5e3aaf5 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskOrchestrator.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskOrchestrator.kt
@@ -33,6 +33,8 @@
 import androidx.appactions.interaction.capabilities.core.impl.task.exceptions.MissingSearchActionConverterException
 import androidx.appactions.interaction.capabilities.core.impl.utils.CapabilityLogger
 import androidx.appactions.interaction.capabilities.core.impl.utils.LoggerInternal
+import androidx.appactions.interaction.capabilities.core.impl.utils.invokeExternalBlock
+import androidx.appactions.interaction.capabilities.core.impl.utils.invokeExternalSuspendBlock
 import androidx.appactions.interaction.proto.AppActionsContext
 import androidx.appactions.interaction.proto.CurrentValue
 import androidx.appactions.interaction.proto.FulfillmentRequest
@@ -281,7 +283,9 @@
 
     private fun maybeInitializeTask() {
         if (status === Status.UNINITIATED) {
-            externalSession.onCreate(SessionConfig())
+            invokeExternalBlock("onCreate") {
+                externalSession.onCreate(SessionConfig())
+            }
         }
         status = Status.IN_PROGRESS
     }
@@ -293,14 +297,15 @@
      * turn, so the logic should include onEnter, arg validation, and onExit.
      */
     private suspend fun handleSync(argumentsWrapper: ArgumentsWrapper): FulfillmentResult {
-        maybeInitializeTask()
-        clearMissingArgs(argumentsWrapper)
         return try {
+            maybeInitializeTask()
+            clearMissingArgs(argumentsWrapper)
             processFulfillmentValues(argumentsWrapper.paramValues)
             val fulfillmentResponse = maybeConfirmOrExecute()
             LoggerInternal.log(CapabilityLogger.LogLevel.INFO, LOG_TAG, "Task sync success")
             FulfillmentResult(fulfillmentResponse)
         } catch (t: Throwable) {
+            // TODO(b/276354491) implement fine-grained exception handling
             LoggerInternal.log(CapabilityLogger.LogLevel.ERROR, LOG_TAG, "Task sync fail", t)
             FulfillmentResult(ErrorStatusInternal.SYNC_REQUEST_FAILURE)
         }
@@ -317,6 +322,7 @@
             LoggerInternal.log(CapabilityLogger.LogLevel.INFO, LOG_TAG, "Task confirm success")
             FulfillmentResult(fulfillmentResponse)
         } catch (t: Throwable) {
+            // TODO(b/276354491) implement fine-grained exception handling
             LoggerInternal.log(CapabilityLogger.LogLevel.ERROR, LOG_TAG, "Task confirm fail")
             FulfillmentResult(ErrorStatusInternal.CONFIRMATION_REQUEST_FAILURE)
         }
@@ -450,7 +456,9 @@
     private suspend fun getFulfillmentResponseForConfirmation(
         finalArguments: Map<String, List<ParamValue>>,
     ): FulfillmentResponse {
-        val result = taskHandler.onReadyToConfirmListener!!.onReadyToConfirm(finalArguments)
+        val result = invokeExternalSuspendBlock("onReadyToConfirm") {
+            taskHandler.onReadyToConfirmListener!!.onReadyToConfirm(finalArguments)
+        }
         val fulfillmentResponse = FulfillmentResponse.newBuilder()
         convertToConfirmationOutput(result)?.let { fulfillmentResponse.confirmationData = it }
         return fulfillmentResponse.build()
@@ -460,7 +468,9 @@
     private suspend fun getFulfillmentResponseForExecution(
         finalArguments: Map<String, List<ParamValue>>,
     ): FulfillmentResponse {
-        val result = externalSession.onExecute(actionSpec.buildArguments(finalArguments))
+        val result = invokeExternalSuspendBlock("onExecute") {
+            externalSession.onExecute(actionSpec.buildArguments(finalArguments))
+        }
         status = Status.COMPLETED
         val fulfillmentResponse =
             FulfillmentResponse.newBuilder().setStartDictation(result.shouldStartDictation)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/CallbackUtils.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/CallbackUtils.kt
new file mode 100644
index 0000000..8cc223d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/CallbackUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.utils
+
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.ExternalException
+
+/** invoke an externally implemented method, wrapping any exceptions with ExternalException.
+ */
+fun <T> invokeExternalBlock(description: String, block: () -> T): T {
+    try {
+        return block()
+    } catch (t: Throwable) {
+        throw ExternalException("exception occurred during '$description'", t)
+    }
+}
+
+/** invoke an externally implemented suspend method, wrapping any exceptions with
+ * ExternalException.
+ */
+suspend fun <T> invokeExternalSuspendBlock(description: String, block: suspend () -> T): T {
+    try {
+        return block()
+    } catch (t: Throwable) {
+        throw ExternalException("exception occurred during '$description'", t)
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/Entity.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/Entity.kt
deleted file mode 100644
index b1d84c8..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/Entity.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.appactions.interaction.capabilities.core.properties
-
-/**
- * Entities are used defining possible values for [Property].
- */
-class Entity internal constructor(
-    val id: String?,
-    val name: String,
-    val alternateNames: List<String>,
-) {
-    /** Builder class for Entity. */
-    class Builder {
-        private var id: String? = null
-        private var name: String? = null
-        private var alternateNames: List<String> = listOf()
-
-        /** Sets the id of the Entity to be built. */
-        fun setId(id: String) = apply {
-            this.id = id
-        }
-
-        /** Sets the name of the Entity to be built. */
-        fun setName(name: String) = apply {
-            this.name = name
-        }
-
-        /** Sets the list of alternate names of the Entity to be built. */
-
-        fun setAlternateNames(alternateNames: List<String>) = apply {
-            this.alternateNames = alternateNames
-        }
-
-        /** Sets the list of alternate names of the Entity to be built. */
-
-        fun setAlternateNames(vararg alternateNames: String) = setAlternateNames(
-            alternateNames.asList(),
-        )
-
-        /** Builds and returns an Entity. */
-        fun build() = Entity(
-            id,
-            requireNotNull(name) {
-                "setName must be called before build"
-            },
-            alternateNames,
-        )
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/EntityValue.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/EntityValue.java
deleted file mode 100644
index c9dcbcc..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/EntityValue.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.appactions.interaction.capabilities.core.values;
-
-import androidx.annotation.NonNull;
-
-import com.google.auto.value.AutoValue;
-
-import java.util.Optional;
-
-/**
- * Represents an entity value for {@code Capability} which includes a value and optionally an
- * id.
- */
-@AutoValue
-public abstract class EntityValue {
-
-    /** Returns a new Builder to build a EntityValue. */
-    @NonNull
-    public static Builder newBuilder() {
-        return new AutoValue_EntityValue.Builder();
-    }
-
-    /** Returns a EntityValue that has both its id and value set to the given identifier. */
-    @NonNull
-    public static EntityValue ofId(@NonNull String id) {
-        return EntityValue.newBuilder().setId(id).setValue(id).build();
-    }
-
-    /** Returns a EntityValue that has the given value and no id. */
-    @NonNull
-    public static EntityValue ofValue(@NonNull String value) {
-        return EntityValue.newBuilder().setValue(value).build();
-    }
-
-    /** Returns the id of the EntityValue. */
-    @NonNull
-    public abstract Optional<String> getId();
-
-    /** Returns the value of the EntityValue. */
-    @NonNull
-    public abstract String getValue();
-
-    /** Builder for {@link EntityValue}. */
-    @AutoValue.Builder
-    public abstract static class Builder {
-        /** Sets the identifier of the EntityValue to be built. */
-        @NonNull
-        public abstract Builder setId(@NonNull String id);
-
-        /** Sets The value of the EntityValue to be built. */
-        @NonNull
-        public abstract Builder setValue(@NonNull String value);
-
-        /** Builds and returns the EntityValue. */
-        @NonNull
-        public abstract EntityValue build();
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
index b6d8e63..108565d 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
@@ -26,8 +26,8 @@
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
-import androidx.appactions.interaction.capabilities.core.properties.Entity
 import androidx.appactions.interaction.capabilities.core.properties.Property
+import androidx.appactions.interaction.capabilities.core.properties.StringValue
 import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils
 import androidx.appactions.interaction.capabilities.testing.internal.FakeCallbackInternal
 import androidx.appactions.interaction.capabilities.testing.internal.TestingUtils.CB_TIMEOUT
@@ -58,13 +58,13 @@
 
     @Test
     fun appAction_computedProperty() {
-        val mutableEntityList = mutableListOf<Entity>()
+        val mutableEntityList = mutableListOf<StringValue>()
         val capability = SingleTurnCapabilityImpl(
             id = "capabilityId",
             actionSpec = ACTION_SPEC,
             property = Properties.newBuilder()
-                .setRequiredEntityField(
-                    Property.Builder<Entity>().setPossibleValueSupplier(
+                .setRequiredStringField(
+                    Property.Builder<StringValue>().setPossibleValueSupplier(
                         mutableEntityList::toList
                     ).build()
                 )
@@ -73,7 +73,7 @@
                 ExecutionResult.Builder<Output>().build()
             }
         )
-        mutableEntityList.add(Entity.Builder().setName("entity1").build())
+        mutableEntityList.add(StringValue.of("entity1"))
 
         assertThat(capability.appAction).isEqualTo(
             AppAction.newBuilder()
@@ -81,9 +81,10 @@
                 .setName("actions.intent.TEST")
                 .addParams(
                     IntentParameter.newBuilder()
-                        .setName("requiredEntity")
+                        .setName("requiredString")
                         .addPossibleEntities(
                             androidx.appactions.interaction.proto.Entity.newBuilder()
+                                .setIdentifier("entity1")
                                 .setName("entity1")
                         )
                 )
@@ -91,20 +92,22 @@
                 .build()
         )
 
-        mutableEntityList.add(Entity.Builder().setName("entity2").build())
+        mutableEntityList.add(StringValue.of("entity2"))
         assertThat(capability.appAction).isEqualTo(
             AppAction.newBuilder()
                 .setIdentifier("capabilityId")
                 .setName("actions.intent.TEST")
                 .addParams(
                     IntentParameter.newBuilder()
-                        .setName("requiredEntity")
+                        .setName("requiredString")
                         .addPossibleEntities(
                             androidx.appactions.interaction.proto.Entity.newBuilder()
+                                .setIdentifier("entity1")
                                 .setName("entity1")
                         )
                         .addPossibleEntities(
                             androidx.appactions.interaction.proto.Entity.newBuilder()
+                                .setIdentifier("entity2")
                                 .setName("entity2")
                         )
                 )
@@ -129,8 +132,8 @@
                 actionSpec = ACTION_SPEC,
                 property =
                 Properties.newBuilder()
-                    .setRequiredEntityField(
-                        Property.Builder<Entity>().build()
+                    .setRequiredStringField(
+                        Property.Builder<StringValue>().build()
                     )
                     .setOptionalStringField(Property.prohibited())
                     .build(),
@@ -187,8 +190,8 @@
                 actionSpec = ACTION_SPEC,
                 property =
                 Properties.newBuilder()
-                    .setRequiredEntityField(
-                        Property.Builder<Entity>().build()
+                    .setRequiredStringField(
+                        Property.Builder<StringValue>().build()
                     )
                     .setOptionalStringField(Property.prohibited())
                     .build(),
@@ -222,8 +225,8 @@
                 actionSpec = ACTION_SPEC,
                 property =
                 Properties.newBuilder()
-                    .setRequiredEntityField(
-                        Property.Builder<Entity>().build()
+                    .setRequiredStringField(
+                        Property.Builder<StringValue>().build()
                     )
                     .build(),
                 executionCallback = executionCallback
@@ -244,8 +247,8 @@
                 actionSpec = ACTION_SPEC,
                 property =
                 Properties.newBuilder()
-                    .setRequiredEntityField(
-                        Property.Builder<Entity>().build()
+                    .setRequiredStringField(
+                        Property.Builder<StringValue>().build()
                     )
                     .build(),
                 executionCallback = executionCallbackAsync.toExecutionCallback()
@@ -266,8 +269,8 @@
         val capability = SingleTurnCapabilityImpl(
             id = "capabilityId",
             actionSpec = ACTION_SPEC,
-            property = Properties.newBuilder().setRequiredEntityField(
-                Property.Builder<Entity>().build()
+            property = Properties.newBuilder().setRequiredStringField(
+                Property.Builder<StringValue>().build()
             ).build(),
             executionCallback = executionCallback
         )
@@ -327,11 +330,11 @@
                 .setArguments(Arguments::class.java, Arguments::newBuilder)
                 .setOutput(Output::class.java)
                 .bindParameter(
-                    "requiredEntity",
-                    Properties::requiredEntityField,
-                    Arguments.Builder::setRequiredEntityField,
-                    TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                    TypeConverters.ENTITY_ENTITY_CONVERTER
+                    "requiredString",
+                    Properties::requiredStringField,
+                    Arguments.Builder::setRequiredStringField,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindOptionalParameter(
                     "optionalString",
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
index 72e9cfc..ea60add 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
@@ -44,7 +44,6 @@
 import androidx.appactions.builtintypes.experimental.types.Timer;
 import androidx.appactions.interaction.capabilities.core.SearchAction;
 import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
-import androidx.appactions.interaction.capabilities.core.values.EntityValue;
 import androidx.appactions.interaction.proto.Entity;
 import androidx.appactions.interaction.proto.ParamValue;
 import androidx.appactions.interaction.protobuf.ListValue;
@@ -192,22 +191,6 @@
     }
 
     @Test
-    public void toEntityValue() throws Exception {
-        List<ParamValue> input =
-                Collections.singletonList(
-                        ParamValue.newBuilder()
-                                .setIdentifier("entity-id")
-                                .setStringValue("string-val")
-                                .build());
-
-        assertThat(
-                SlotTypeConverter.ofSingular(TypeConverters.ENTITY_PARAM_VALUE_CONVERTER)
-                        .convert(input))
-                .isEqualTo(
-                        EntityValue.newBuilder().setId("entity-id").setValue("string-val").build());
-    }
-
-    @Test
     public void toIntegerValue() throws Exception {
         ParamValue paramValue = ParamValue.newBuilder().setNumberValue(5).build();
         List<ParamValue> input = Collections.singletonList(paramValue);
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
index 23cf78f..364bb86 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
@@ -23,11 +23,9 @@
 import androidx.appactions.interaction.capabilities.core.impl.converters.EntityConverter;
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
-import androidx.appactions.interaction.capabilities.core.properties.Entity;
 import androidx.appactions.interaction.capabilities.core.properties.Property;
 import androidx.appactions.interaction.capabilities.core.properties.StringValue;
 import androidx.appactions.interaction.capabilities.core.testing.spec.Output;
-import androidx.appactions.interaction.capabilities.core.values.EntityValue;
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
 import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
 import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput;
@@ -52,24 +50,6 @@
                     .setArguments(Arguments.class, Arguments::newBuilder)
                     .setOutput(Output.class)
                     .bindParameter(
-                            "requiredEntity",
-                            Properties::requiredEntityField,
-                            Arguments.Builder::setRequiredEntityField,
-                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                            TypeConverters.ENTITY_ENTITY_CONVERTER)
-                    .bindOptionalParameter(
-                            "optionalEntity",
-                            Properties::optionalEntityField,
-                            Arguments.Builder::setOptionalEntityField,
-                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                            TypeConverters.ENTITY_ENTITY_CONVERTER)
-                    .bindRepeatedParameter(
-                            "repeatedEntity",
-                            Properties::repeatedEntityField,
-                            Arguments.Builder::setRepeatedEntityField,
-                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                            TypeConverters.ENTITY_ENTITY_CONVERTER)
-                    .bindParameter(
                             "requiredString",
                             Properties::requiredStringField,
                             Arguments.Builder::setRequiredStringField,
@@ -198,16 +178,10 @@
     public void getAppAction_onlyRequiredProperty() {
         Properties property =
                 Properties.create(
-                        new Property.Builder<Entity>()
-                                .setPossibleValues(
-                                        new Entity.Builder()
-                                                .setId("contact_2")
-                                                .setName("Donald")
-                                                .setAlternateNames("Duck")
-                                                .build())
+                        new Property.Builder<StringValue>()
+                                .setPossibleValues(StringValue.of("Donald"))
                                 .setValueMatchRequired(true)
-                                .build(),
-                        new Property.Builder<StringValue>().build());
+                                .build());
 
         assertThat(ACTION_SPEC.convertPropertyToProto(property))
                 .isEqualTo(
@@ -215,17 +189,13 @@
                                 .setName("actions.intent.TEST")
                                 .addParams(
                                         IntentParameter.newBuilder()
-                                                .setName("requiredEntity")
+                                                .setName("requiredString")
+                                                .setEntityMatchRequired(true)
                                                 .addPossibleEntities(
                                                         androidx.appactions.interaction.proto.Entity
                                                                 .newBuilder()
-                                                                .setIdentifier("contact_2")
-                                                                .setName("Donald")
-                                                                .addAlternateNames("Duck")
-                                                                .build())
-                                                .setIsRequired(false)
-                                                .setEntityMatchRequired(true))
-                                .addParams(IntentParameter.newBuilder().setName("requiredString"))
+                                                                .setIdentifier("Donald")
+                                                                .setName("Donald")))
                                 .build());
     }
 
@@ -233,41 +203,11 @@
     public void getAppAction_allProperties() {
         Properties property =
                 Properties.create(
-                        new Property.Builder<Entity>()
-                                .setPossibleValues(
-                                        new Entity.Builder()
-                                                .setId("contact_2")
-                                                .setName("Donald")
-                                                .setAlternateNames("Duck")
-                                                .build())
-                                .build(),
-                        Optional.of(
-                                new Property.Builder<Entity>()
-                                        .setPossibleValues(
-                                                new Entity.Builder()
-                                                        .setId("entity1")
-                                                        .setName("optional possible entity")
-                                                        .build())
-                                        .setRequired(true)
-                                        .build()),
                         Optional.of(
                                 new Property.Builder<TestEnum>()
                                         .setPossibleValues(TestEnum.VALUE_1)
                                         .setRequired(true)
                                         .build()),
-                        Optional.of(
-                                new Property.Builder<Entity>()
-                                        .setPossibleValues(
-                                                new Entity.Builder()
-                                                        .setId("entity1")
-                                                        .setName("repeated entity1")
-                                                        .build(),
-                                                new Entity.Builder()
-                                                        .setId("entity2")
-                                                        .setName("repeated entity2")
-                                                        .build())
-                                        .setRequired(true)
-                                        .build()),
                         new Property.Builder<StringValue>().build(),
                         Optional.of(
                                 new Property.Builder<StringValue>()
@@ -281,46 +221,6 @@
                 .isEqualTo(
                         AppAction.newBuilder()
                                 .setName("actions.intent.TEST")
-                                .addParams(
-                                        IntentParameter.newBuilder()
-                                                .setName("requiredEntity")
-                                                .addPossibleEntities(
-                                                        androidx.appactions.interaction.proto.Entity
-                                                                .newBuilder()
-                                                                .setIdentifier("contact_2")
-                                                                .setName("Donald")
-                                                                .addAlternateNames("Duck")
-                                                                .build())
-                                                .setIsRequired(false)
-                                                .setEntityMatchRequired(false))
-                                .addParams(
-                                        IntentParameter.newBuilder()
-                                                .setName("optionalEntity")
-                                                .addPossibleEntities(
-                                                        androidx.appactions.interaction.proto.Entity
-                                                                .newBuilder()
-                                                                .setIdentifier("entity1")
-                                                                .setName("optional possible entity")
-                                                                .build())
-                                                .setIsRequired(true)
-                                                .setEntityMatchRequired(false))
-                                .addParams(
-                                        IntentParameter.newBuilder()
-                                                .setName("repeatedEntity")
-                                                .addPossibleEntities(
-                                                        androidx.appactions.interaction.proto.Entity
-                                                                .newBuilder()
-                                                                .setIdentifier("entity1")
-                                                                .setName("repeated entity1")
-                                                                .build())
-                                                .addPossibleEntities(
-                                                        androidx.appactions.interaction.proto.Entity
-                                                                .newBuilder()
-                                                                .setIdentifier("entity2")
-                                                                .setName("repeated entity2")
-                                                                .build())
-                                                .setIsRequired(true)
-                                                .setEntityMatchRequired(false))
                                 .addParams(IntentParameter.newBuilder().setName("requiredString"))
                                 .addParams(
                                         IntentParameter.newBuilder()
@@ -416,12 +316,6 @@
             return new AutoValue_ActionSpecTest_Arguments.Builder();
         }
 
-        abstract EntityValue requiredEntityField();
-
-        abstract EntityValue optionalEntityField();
-
-        abstract List<EntityValue> repeatedEntityField();
-
         abstract String requiredStringField();
 
         abstract String optionalStringField();
@@ -431,12 +325,6 @@
         @AutoValue.Builder
         abstract static class Builder implements BuilderOf<Arguments> {
 
-            abstract Builder setRequiredEntityField(EntityValue value);
-
-            abstract Builder setOptionalEntityField(EntityValue value);
-
-            abstract Builder setRepeatedEntityField(List<EntityValue> repeated);
-
             abstract Builder setRequiredStringField(String value);
 
             abstract Builder setOptionalStringField(String value);
@@ -453,44 +341,28 @@
     abstract static class Properties {
 
         static Properties create(
-                Property<Entity> requiredEntityField,
-                Optional<Property<Entity>> optionalEntityField,
                 Optional<Property<TestEnum>> optionalEnumField,
-                Optional<Property<Entity>> repeatedEntityField,
                 Property<StringValue> requiredStringField,
                 Optional<Property<StringValue>> optionalStringField,
                 Optional<Property<StringValue>> repeatedStringField) {
             return new AutoValue_ActionSpecTest_Properties(
-                    requiredEntityField,
-                    optionalEntityField,
                     optionalEnumField,
-                    repeatedEntityField,
                     requiredStringField,
                     optionalStringField,
                     repeatedStringField);
         }
 
         static Properties create(
-                Property<Entity> requiredEntityField,
                 Property<StringValue> requiredStringField) {
             return create(
-                    requiredEntityField,
-                    Optional.empty(),
-                    Optional.empty(),
                     Optional.empty(),
                     requiredStringField,
                     Optional.empty(),
                     Optional.empty());
         }
 
-        abstract Property<Entity> requiredEntityField();
-
-        abstract Optional<Property<Entity>> optionalEntityField();
-
         abstract Optional<Property<TestEnum>> optionalEnumField();
 
-        abstract Optional<Property<Entity>> repeatedEntityField();
-
         abstract Property<StringValue> requiredStringField();
 
         abstract Optional<Property<StringValue>> optionalStringField();
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
index 21022f6..899825b 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
@@ -39,14 +39,14 @@
 import androidx.appactions.interaction.capabilities.core.properties.Property
 import androidx.appactions.interaction.capabilities.core.testing.spec.Arguments
 import androidx.appactions.interaction.capabilities.core.testing.spec.CapabilityStructFill
-import androidx.appactions.interaction.capabilities.core.testing.spec.CapabilityTwoEntityValues
+import androidx.appactions.interaction.capabilities.core.testing.spec.CapabilityTwoStrings
 import androidx.appactions.interaction.capabilities.core.testing.spec.Confirmation
 import androidx.appactions.interaction.capabilities.core.testing.spec.ExecutionSession
 import androidx.appactions.interaction.capabilities.core.testing.spec.Output
 import androidx.appactions.interaction.capabilities.core.testing.spec.TestEnum
 import androidx.appactions.interaction.capabilities.core.testing.spec.Properties
-import androidx.appactions.interaction.capabilities.core.values.EntityValue
 import androidx.appactions.interaction.capabilities.core.SearchAction
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeSpec
 import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils.buildRequestArgs
 import androidx.appactions.interaction.capabilities.testing.internal.ArgumentUtils.buildSearchActionParamValue
 import androidx.appactions.interaction.capabilities.testing.internal.FakeCallbackInternal
@@ -91,12 +91,12 @@
                 }
             },
             sessionBridge = { TaskHandler.Builder<Confirmation>().build() },
-            sessionUpdaterSupplier = ::EmptyTaskUpdater,
+            sessionUpdaterSupplier = ::EmptyTaskUpdater
         )
     private val hostProperties: HostProperties =
         HostProperties.Builder()
             .setMaxHostSizeDp(
-                SizeF(300f, 500f),
+                SizeF(300f, 500f)
             )
             .build()
     private val fakeSessionId = "fakeSessionId"
@@ -109,28 +109,24 @@
                     .setName("actions.intent.TEST")
                     .setIdentifier("id")
                     .addParams(
-                        IntentParameter.newBuilder().setName("required").setIsRequired(true),
+                        IntentParameter.newBuilder().setName("required").setIsRequired(true)
                     )
                     .setTaskInfo(
-                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true),
+                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true)
                     )
-                    .build(),
+                    .build()
             )
     }
 
     @Test
     fun appAction_computedProperty() {
-        val mutableEntityList = mutableListOf<
-            androidx.appactions.interaction.capabilities.core.properties.Entity
-        >()
+        val mutableEntityList = mutableListOf<StringValue>()
         val capability = createCapability<EmptyTaskUpdater>(
             Properties.newBuilder()
-                .setRequiredEntityField(
-                    Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity
-                    >().setPossibleValueSupplier(
-                        mutableEntityList::toList
-                    ).build()
+                .setRequiredStringField(
+                    Property.Builder<StringValue>()
+                        .setPossibleValueSupplier(mutableEntityList::toList)
+                        .build()
                 )
                 .build(),
             sessionFactory =
@@ -141,12 +137,9 @@
                 }
             },
             sessionBridge = { TaskHandler.Builder<Confirmation>().build() },
-            sessionUpdaterSupplier = ::EmptyTaskUpdater,
+            sessionUpdaterSupplier = ::EmptyTaskUpdater
         )
-        mutableEntityList.add(
-            androidx.appactions.interaction.capabilities.core.properties.Entity.Builder()
-                .setName("entity1").build()
-        )
+        mutableEntityList.add(StringValue.of("entity1"))
 
         assertThat(capability.appAction).isEqualTo(
             AppAction.newBuilder()
@@ -155,16 +148,15 @@
                 .addParams(
                     IntentParameter.newBuilder()
                         .setName("required")
-                        .addPossibleEntities(Entity.newBuilder().setName("entity1"))
+                        .addPossibleEntities(
+                            Entity.newBuilder().setIdentifier("entity1").setName("entity1")
+                        )
                 )
                 .setTaskInfo(TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
                 .build()
         )
 
-        mutableEntityList.add(
-            androidx.appactions.interaction.capabilities.core.properties.Entity.Builder()
-                .setName("entity2").build()
-        )
+        mutableEntityList.add(StringValue.of("entity2"))
         assertThat(capability.appAction).isEqualTo(
             AppAction.newBuilder()
                 .setIdentifier("id")
@@ -172,8 +164,12 @@
                 .addParams(
                     IntentParameter.newBuilder()
                         .setName("required")
-                        .addPossibleEntities(Entity.newBuilder().setName("entity1"))
-                        .addPossibleEntities(Entity.newBuilder().setName("entity2"))
+                        .addPossibleEntities(
+                            Entity.newBuilder().setIdentifier("entity1").setName("entity1")
+                        )
+                        .addPossibleEntities(
+                            Entity.newBuilder().setIdentifier("entity2").setName("entity2")
+                        )
                 )
                 .setTaskInfo(TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
                 .build()
@@ -188,7 +184,7 @@
                 SINGLE_REQUIRED_FIELD_PROPERTY,
                 { externalSession },
                 { TaskHandler.Builder<Confirmation>().build() },
-                ::EmptyTaskUpdater,
+                ::EmptyTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
         assertThat(session.uiHandle).isSameInstanceAs(externalSession)
@@ -209,11 +205,11 @@
 
                         override fun onExecuteAsync(arguments: Arguments) =
                             Futures.immediateFuture(
-                                ExecutionResult.Builder<Output>().build(),
+                                ExecutionResult.Builder<Output>().build()
                             )
                     } },
                 sessionBridge = SessionBridge { TaskHandler.Builder<Confirmation>().build() },
-                sessionUpdaterSupplier = ::EmptyTaskUpdater,
+                sessionUpdaterSupplier = ::EmptyTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
 
@@ -221,7 +217,7 @@
         val callback = FakeCallbackInternal()
         session.execute(
             buildRequestArgs(SYNC, "unknownArgName", "foo"),
-            callback,
+            callback
         )
         assertThat(callback.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(onCreateInvocationCount.get()).isEqualTo(1)
@@ -232,23 +228,23 @@
             buildRequestArgs(
                 SYNC,
                 "required",
-                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
+                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()
             ),
-            callback2,
+            callback2
         )
         assertThat(callback2.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(onCreateInvocationCount.get()).isEqualTo(1)
     }
 
     class RequiredTaskUpdater : AbstractTaskUpdater() {
-        fun setRequiredEntityValue(entityValue: EntityValue) {
+        fun setRequiredStringValue(value: String) {
             super.updateParamValues(
                 mapOf(
                     "required" to
                         listOf(
-                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER.toParamValue(entityValue),
-                        ),
-                ),
+                            TypeConverters.STRING_PARAM_VALUE_CONVERTER.toParamValue(value)
+                        )
+                )
             )
         }
     }
@@ -268,7 +264,7 @@
             return CallbackToFutureAdapter.getFuture { newCompleter ->
                 val oldCompleter: Completer<ValidationResult>? =
                     mCompleterRef.getAndSet(
-                        newCompleter,
+                        newCompleter
                     )
                 oldCompleter?.setCancelled()
                 "waiting for setValidationResult"
@@ -290,7 +286,7 @@
             SINGLE_REQUIRED_FIELD_PROPERTY,
             sessionFactory = { _ -> externalSession },
             sessionBridge = SessionBridge { TaskHandler.Builder<Confirmation>().build() },
-            sessionUpdaterSupplier = ::RequiredTaskUpdater,
+            sessionUpdaterSupplier = ::RequiredTaskUpdater
         )
         val session = capability.createSession("mySessionId", hostProperties)
         val callback = FakeCallbackInternal()
@@ -298,13 +294,13 @@
             buildRequestArgs(
                 SYNC,
                 "required",
-                "hello",
+                "hello"
             ),
-            callback,
+            callback
         )
         onExecuteReached.await()
         assertThat(UiHandleRegistry.getSessionIdFromUiHandle(externalSession)).isEqualTo(
-            "mySessionId",
+            "mySessionId"
         )
 
         onExecuteResult.complete(ExecutionResult.Builder<Output>().build())
@@ -323,12 +319,12 @@
                     object : ExecutionSession {
                         override fun onExecuteAsync(arguments: Arguments) =
                             Futures.immediateFuture(
-                                ExecutionResult.Builder<Output>().build(),
+                                ExecutionResult.Builder<Output>().build()
                             )
                     }
                 },
                 sessionBridge = SessionBridge { TaskHandler.Builder<Confirmation>().build() },
-                sessionUpdaterSupplier = ::RequiredTaskUpdater,
+                sessionUpdaterSupplier = ::RequiredTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
 
@@ -338,12 +334,12 @@
                     .setName("actions.intent.TEST")
                     .setIdentifier("id")
                     .addParams(
-                        IntentParameter.newBuilder().setName("required").setIsRequired(true),
+                        IntentParameter.newBuilder().setName("required").setIsRequired(true)
                     )
                     .setTaskInfo(
-                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true),
+                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true)
                     )
-                    .build(),
+                    .build()
             )
 
         // TURN 1 (UNKNOWN).
@@ -355,55 +351,51 @@
 
     @Test
     fun slotFilling_isActive_smokeTest() {
-        val property: CapabilityTwoEntityValues.Properties =
-            CapabilityTwoEntityValues.Properties.newBuilder()
-                .setSlotA(
-                    Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity,
-                        >()
+        val property: CapabilityTwoStrings.Properties =
+            CapabilityTwoStrings.Properties.newBuilder()
+                .setStringSlotA(
+                    Property.Builder<StringValue>()
                         .setRequired(true)
-                        .build(),
+                        .build()
                 )
-                .setSlotB(
-                    Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity,
-                        >()
+                .setStringSlotB(
+                    Property.Builder<StringValue>()
                         .setRequired(true)
-                        .build(),
+                        .build()
                 )
                 .build()
         val sessionFactory:
-            (hostProperties: HostProperties?) -> CapabilityTwoEntityValues.ExecutionSession =
+            (hostProperties: HostProperties?) -> CapabilityTwoStrings.ExecutionSession =
             { _ ->
-                object : CapabilityTwoEntityValues.ExecutionSession {
+                object : CapabilityTwoStrings.ExecutionSession {
                     override suspend fun onExecute(
-                        arguments: CapabilityTwoEntityValues.Arguments,
+                        arguments: CapabilityTwoStrings.Arguments
                     ): ExecutionResult<Void> = ExecutionResult.Builder<Void>().build()
                 }
             }
         val sessionBridge =
-            SessionBridge<CapabilityTwoEntityValues.ExecutionSession, Void> {
+            SessionBridge<CapabilityTwoStrings.ExecutionSession, Void> {
                 TaskHandler.Builder<Void>()
                     .registerValueTaskParam(
-                        "slotA",
-                        AUTO_ACCEPT_ENTITY_VALUE,
-                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
+                        "stringSlotA",
+                        AUTO_ACCEPT_STRING_VALUE,
+                        TypeConverters.STRING_PARAM_VALUE_CONVERTER
                     )
                     .registerValueTaskParam(
-                        "slotB",
-                        AUTO_ACCEPT_ENTITY_VALUE,
-                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
+                        "stringSlotB",
+                        AUTO_ACCEPT_STRING_VALUE,
+                        TypeConverters.STRING_PARAM_VALUE_CONVERTER
                     )
                     .build()
             }
         val capability: Capability =
             TaskCapabilityImpl(
                 "fakeId",
-                CapabilityTwoEntityValues.ACTION_SPEC,
+                CapabilityTwoStrings.ACTION_SPEC,
                 property,
                 sessionFactory,
                 sessionBridge,
-                ::EmptyTaskUpdater,
+                ::EmptyTaskUpdater
             )
 
         val session = capability.createSession(fakeSessionId, hostProperties)
@@ -414,10 +406,10 @@
         session.execute(
             buildRequestArgs(
                 SYNC,
-                "slotA",
-                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
+                "stringSlotA",
+                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()
             ),
-            callback,
+            callback
         )
         assertThat(callback.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(session.isActive).isTrue()
@@ -427,12 +419,12 @@
         session.execute(
             buildRequestArgs(
                 SYNC,
-                "slotA",
-                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
-                "slotB",
-                ParamValue.newBuilder().setIdentifier("bar").setStringValue("bar").build(),
+                "stringSlotA",
+                ParamValue.newBuilder().setStringValue("foo").build(),
+                "stringSlotB",
+                ParamValue.newBuilder().setStringValue("bar").build()
             ),
-            callback2,
+            callback2
         )
         assertThat(callback2.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(session.isActive).isFalse()
@@ -441,7 +433,7 @@
         val callback3 = FakeCallbackInternal()
         session.execute(
             buildRequestArgs(CANCEL),
-            callback3,
+            callback3
         )
         assertThat(callback3.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(session.isActive).isFalse()
@@ -451,29 +443,29 @@
     @kotlin.Throws(Exception::class)
     fun slotFilling_optionalButRejectedParam_onFinishNotInvoked() {
         val onExecuteInvocationCount = AtomicInteger(0)
-        val property: CapabilityTwoEntityValues.Properties =
-            CapabilityTwoEntityValues.Properties.newBuilder()
-                .setSlotA(
+        val property: CapabilityTwoStrings.Properties =
+            CapabilityTwoStrings.Properties.newBuilder()
+                .setStringSlotA(
                     Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity,
+                        StringValue
                         >()
                         .setRequired(true)
-                        .build(),
+                        .build()
                 )
-                .setSlotB(
+                .setStringSlotB(
                     Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity,
+                        StringValue
                         >()
                         .setRequired(false)
-                        .build(),
+                        .build()
                 )
                 .build()
         val sessionFactory:
-            (hostProperties: HostProperties?) -> CapabilityTwoEntityValues.ExecutionSession =
+            (hostProperties: HostProperties?) -> CapabilityTwoStrings.ExecutionSession =
             { _ ->
-                object : CapabilityTwoEntityValues.ExecutionSession {
+                object : CapabilityTwoStrings.ExecutionSession {
                     override suspend fun onExecute(
-                        arguments: CapabilityTwoEntityValues.Arguments,
+                        arguments: CapabilityTwoStrings.Arguments
                     ): ExecutionResult<Void> {
                         onExecuteInvocationCount.incrementAndGet()
                         return ExecutionResult.Builder<Void>().build()
@@ -481,28 +473,28 @@
                 }
             }
         val sessionBridge =
-            SessionBridge<CapabilityTwoEntityValues.ExecutionSession, Void> {
+            SessionBridge<CapabilityTwoStrings.ExecutionSession, Void> {
                 TaskHandler.Builder<Void>()
                     .registerValueTaskParam(
-                        "slotA",
-                        AUTO_ACCEPT_ENTITY_VALUE,
-                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
+                        "stringSlotA",
+                        AUTO_ACCEPT_STRING_VALUE,
+                        TypeConverters.STRING_PARAM_VALUE_CONVERTER
                     )
                     .registerValueTaskParam(
-                        "slotB",
-                        AUTO_REJECT_ENTITY_VALUE,
-                        TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
+                        "stringSlotB",
+                        AUTO_REJECT_STRING_VALUE,
+                        TypeConverters.STRING_PARAM_VALUE_CONVERTER
                     )
                     .build()
             }
         val capability: Capability =
             TaskCapabilityImpl(
                 "fakeId",
-                CapabilityTwoEntityValues.ACTION_SPEC,
+                CapabilityTwoStrings.ACTION_SPEC,
                 property,
                 sessionFactory,
                 sessionBridge,
-                ::EmptyTaskUpdater,
+                ::EmptyTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
 
@@ -511,32 +503,32 @@
         session.execute(
             buildRequestArgs(
                 SYNC,
-                "slotA",
+                "stringSlotA",
                 ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
-                "slotB",
-                ParamValue.newBuilder().setIdentifier("bar").setStringValue("bar").build(),
+                "stringSlotB",
+                ParamValue.newBuilder().setIdentifier("bar").setStringValue("bar").build()
             ),
-            callback,
+            callback
         )
         assertThat(callback.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(onExecuteInvocationCount.get()).isEqualTo(0)
-        assertThat(getCurrentValues("slotA", session.state!!))
+        assertThat(getCurrentValues("stringSlotA", session.state!!))
             .containsExactly(
                 CurrentValue.newBuilder()
                     .setValue(
-                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo"),
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo")
                     )
                     .setStatus(CurrentValue.Status.ACCEPTED)
-                    .build(),
+                    .build()
             )
-        assertThat(getCurrentValues("slotB", session.state!!))
+        assertThat(getCurrentValues("stringSlotB", session.state!!))
             .containsExactly(
                 CurrentValue.newBuilder()
                     .setValue(
-                        ParamValue.newBuilder().setIdentifier("bar").setStringValue("bar"),
+                        ParamValue.newBuilder().setIdentifier("bar").setStringValue("bar")
                     )
                     .setStatus(CurrentValue.Status.REJECTED)
-                    .build(),
+                    .build()
             )
     }
 
@@ -545,18 +537,18 @@
     fun slotFilling_assistantRemovedParam_clearInSdkState() {
         val property: Properties =
             Properties.newBuilder()
-                .setRequiredEntityField(
+                .setRequiredStringField(
                     Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity,
+                        StringValue
                         >()
                         .setRequired(true)
-                        .build(),
+                        .build()
                 )
                 .setEnumField(
                     Property.Builder<TestEnum>()
                         .setPossibleValues(TestEnum.VALUE_1, TestEnum.VALUE_2)
                         .setRequired(true)
-                        .build(),
+                        .build()
                 )
                 .build()
         val capability: Capability =
@@ -564,7 +556,7 @@
                 property,
                 sessionFactory = { _ -> ExecutionSession.DEFAULT },
                 sessionBridge = SessionBridge { TaskHandler.Builder<Confirmation>().build() },
-                sessionUpdaterSupplier = ::EmptyTaskUpdater,
+                sessionUpdaterSupplier = ::EmptyTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
 
@@ -574,19 +566,19 @@
             buildRequestArgs(
                 SYNC,
                 "required",
-                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
+                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()
             ),
-            callback,
+            callback
         )
         assertThat(callback.receiveResponse()).isNotNull()
         assertThat(getCurrentValues("required", session.state!!))
             .containsExactly(
                 CurrentValue.newBuilder()
                     .setValue(
-                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo"),
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo")
                     )
                     .setStatus(CurrentValue.Status.ACCEPTED)
-                    .build(),
+                    .build()
             )
         assertThat(getCurrentValues("optionalEnum", session.state!!)).isEmpty()
 
@@ -594,7 +586,7 @@
         val callback2 = FakeCallbackInternal()
         session.execute(
             buildRequestArgs(SYNC, "optionalEnum", TestEnum.VALUE_2),
-            callback2,
+            callback2
         )
         assertThat(callback2.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(getCurrentValues("required", session.state!!)).isEmpty()
@@ -603,7 +595,7 @@
                 CurrentValue.newBuilder()
                     .setValue(ParamValue.newBuilder().setIdentifier("VALUE_2"))
                     .setStatus(CurrentValue.Status.ACCEPTED)
-                    .build(),
+                    .build()
             )
     }
 
@@ -611,12 +603,6 @@
     @kotlin.Throws(Exception::class)
     @Suppress("DEPRECATION") // TODO(b/269638788) migrate session state to AppDialogState message
     fun disambig_singleParam_disambigEntitiesInContext() {
-        val entityConverter: EntityConverter<EntityValue> = EntityConverter { entityValue ->
-            Entity.newBuilder()
-                .setIdentifier(entityValue.id.get())
-                .setName(entityValue.value)
-                .build()
-        }
         val capability: Capability =
             createCapability(
                 SINGLE_REQUIRED_FIELD_PROPERTY,
@@ -625,22 +611,21 @@
                         override suspend fun onExecute(arguments: Arguments) =
                             ExecutionResult.Builder<Output>().build()
 
-                        override fun getRequiredEntityListener() =
-                            object : AppEntityListener<EntityValue> {
+                        override fun getRequiredStringListener() =
+                            object : AppEntityListener<String> {
                                 override fun lookupAndRenderAsync(
-                                    searchAction: SearchAction<EntityValue>,
-                                ): ListenableFuture<EntitySearchResult<EntityValue>> {
-                                    val result = EntitySearchResult.Builder<EntityValue>()
+                                    searchAction: SearchAction<String>
+                                ): ListenableFuture<EntitySearchResult<String>> {
                                     return Futures.immediateFuture(
-                                        result
-                                            .addPossibleValue(EntityValue.ofId("valid1"))
-                                            .addPossibleValue(EntityValue.ofId("valid2"))
-                                            .build(),
+                                        EntitySearchResult.Builder<String>()
+                                            .addPossibleValue("valid1")
+                                            .addPossibleValue("valid2")
+                                            .build()
                                     )
                                 }
 
                                 override fun onReceivedAsync(
-                                    value: EntityValue,
+                                    value: String
                                 ): ListenableFuture<ValidationResult> {
                                     return Futures.immediateFuture(ValidationResult.newAccepted())
                                 }
@@ -650,19 +635,19 @@
                 sessionBridge =
                 SessionBridge<ExecutionSession, Confirmation> { session ->
                     val builder = TaskHandler.Builder<Confirmation>()
-                    session.getRequiredEntityListener()
-                        ?.let { listener: AppEntityListener<EntityValue> ->
+                    session.getRequiredStringListener()
+                        ?.let { listener: AppEntityListener<String> ->
                             builder.registerAppEntityTaskParam(
                                 "required",
                                 listener,
-                                TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                                entityConverter,
-                                getTrivialSearchActionConverter(),
+                                TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                                EntityConverter.of(TypeSpec.STRING_TYPE_SPEC),
+                                getTrivialSearchActionConverter()
                             )
                         }
                     builder.build()
                 },
-                sessionUpdaterSupplier = ::EmptyTaskUpdater,
+                sessionUpdaterSupplier = ::EmptyTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
 
@@ -670,7 +655,7 @@
         val callback = FakeCallbackInternal()
         session.execute(
             buildRequestArgs(SYNC, "required", buildSearchActionParamValue("invalid")),
-            callback,
+            callback
         )
         assertThat(callback.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(session.state)
@@ -683,34 +668,28 @@
                             .addCurrentValue(
                                 CurrentValue.newBuilder()
                                     .setValue(
-                                        buildSearchActionParamValue("invalid"),
+                                        buildSearchActionParamValue("invalid")
                                     )
                                     .setStatus(
-                                        CurrentValue.Status.DISAMBIG,
+                                        CurrentValue.Status.DISAMBIG
                                     )
                                     .setDisambiguationData(
                                         DisambiguationData.newBuilder()
                                             .addEntities(
                                                 Entity.newBuilder()
-                                                    .setIdentifier(
-                                                        "valid1",
+                                                    .setStringValue(
+                                                        "valid1"
                                                     )
-                                                    .setName(
-                                                        "valid1",
-                                                    ),
                                             )
                                             .addEntities(
                                                 Entity.newBuilder()
-                                                    .setIdentifier(
-                                                        "valid2",
+                                                    .setStringValue(
+                                                        "valid2"
                                                     )
-                                                    .setName(
-                                                        "valid2",
-                                                    ),
-                                            ),
-                                    ),
-                            ),
-                    ).build(),
+                                            )
+                                    )
+                            )
+                    ).build()
             )
 
         // TURN 2.
@@ -719,9 +698,9 @@
             buildRequestArgs(
                 SYNC,
                 "required",
-                ParamValue.newBuilder().setIdentifier("valid2").setStringValue("valid2").build(),
+                ParamValue.newBuilder().setIdentifier("valid2").setStringValue("valid2").build()
             ),
-            callback2,
+            callback2
         )
         assertThat(callback2.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(session.state)
@@ -735,19 +714,17 @@
                                 CurrentValue.newBuilder()
                                     .setValue(
                                         ParamValue.newBuilder()
-                                            .setIdentifier(
-                                                "valid2",
-                                            )
+                                            .setIdentifier("valid2")
                                             .setStringValue(
-                                                "valid2",
-                                            ),
+                                                "valid2"
+                                            )
                                     )
                                     .setStatus(
-                                        CurrentValue.Status.ACCEPTED,
-                                    ),
-                            ),
+                                        CurrentValue.Status.ACCEPTED
+                                    )
+                            )
                     )
-                    .build(),
+                    .build()
             )
     }
 
@@ -776,7 +753,7 @@
             { _ ->
                 object : CapabilityStructFill.ExecutionSession {
                     override suspend fun onExecute(
-                        arguments: CapabilityStructFill.Arguments,
+                        arguments: CapabilityStructFill.Arguments
                     ): ExecutionResult<Void> {
                         val listItem: ListItem = arguments.listItem().orElse(null)
                         val string: String = arguments.anyString().orElse(null)
@@ -788,20 +765,20 @@
                     override fun getListItemListener() =
                         object : AppEntityListener<ListItem> {
                             override fun onReceivedAsync(
-                                value: ListItem,
+                                value: ListItem
                             ): ListenableFuture<ValidationResult> {
                                 onReceivedDeferred.complete(value)
                                 return Futures.immediateFuture(ValidationResult.newAccepted())
                             }
 
                             override fun lookupAndRenderAsync(
-                                searchAction: SearchAction<ListItem>,
+                                searchAction: SearchAction<ListItem>
                             ): ListenableFuture<EntitySearchResult<ListItem>> =
                                 Futures.immediateFuture(
                                     EntitySearchResult.Builder<ListItem>()
                                         .addPossibleValue(item1)
                                         .addPossibleValue(item2)
-                                        .build(),
+                                        .build()
                                 )
                         }
                 }
@@ -814,7 +791,7 @@
                         session.getListItemListener(),
                         ParamValueConverter.of(LIST_ITEM_TYPE_SPEC),
                         EntityConverter.of(LIST_ITEM_TYPE_SPEC)::convert,
-                        getTrivialSearchActionConverter(),
+                        getTrivialSearchActionConverter()
                     )
                     .build()
             }
@@ -826,7 +803,7 @@
                 property,
                 sessionFactory,
                 sessionBridge,
-                ::EmptyTaskUpdater,
+                ::EmptyTaskUpdater
             )
         val session = capability.createSession(fakeSessionId, hostProperties)
 
@@ -834,7 +811,7 @@
         val callback = FakeCallbackInternal()
         session.execute(
             buildRequestArgs(SYNC, "listItem", buildSearchActionParamValue("apple")),
-            callback,
+            callback
         )
         assertThat(callback.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(onReceivedDeferred.isCompleted).isFalse()
@@ -850,28 +827,28 @@
                                 CurrentValue.newBuilder()
                                     .setValue(
                                         buildSearchActionParamValue(
-                                            "apple",
-                                        ),
+                                            "apple"
+                                        )
                                     )
                                     .setStatus(CurrentValue.Status.DISAMBIG)
                                     .setDisambiguationData(
                                         DisambiguationData.newBuilder()
                                             .addEntities(
                                                 EntityConverter.of(LIST_ITEM_TYPE_SPEC)
-                                                    .convert(item1),
+                                                    .convert(item1)
                                             )
                                             .addEntities(
                                                 EntityConverter.of(LIST_ITEM_TYPE_SPEC)
-                                                    .convert(item2),
+                                                    .convert(item2)
                                             )
-                                            .build(),
+                                            .build()
                                     )
-                                    .build(),
+                                    .build()
                             )
-                            .build(),
+                            .build()
                     )
                     .addParams(DialogParameter.newBuilder().setName("string").build())
-                    .build(),
+                    .build()
             )
 
         // second sync request, sending grounded ParamValue with identifier only
@@ -880,9 +857,9 @@
             buildRequestArgs(
                 SYNC,
                 "listItem",
-                ParamValue.newBuilder().setIdentifier("item2").build(),
+                ParamValue.newBuilder().setIdentifier("item2").build()
             ),
-            callback2,
+            callback2
         )
         assertThat(callback2.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(onReceivedDeferred.awaitSync()).isEqualTo(item2)
@@ -896,9 +873,9 @@
                 "listItem",
                 ParamValue.newBuilder().setIdentifier("item2").build(),
                 "string",
-                "unused",
+                "unused"
             ),
-            callback3,
+            callback3
         )
         assertThat(callback3.receiveResponse().fulfillmentResponse).isNotNull()
         assertThat(onExecuteListItemDeferred.awaitSync()).isEqualTo(item2)
@@ -917,9 +894,9 @@
                                 Output.builder()
                                     .setOptionalStringField("bar")
                                     .setRepeatedStringField(
-                                        listOf("bar1", "bar2"),
+                                        listOf("bar1", "bar2")
                                     )
-                                    .build(),
+                                    .build()
                             )
                             .build()
                 }
@@ -934,35 +911,35 @@
                     OutputValue.newBuilder()
                         .setName("optionalStringOutput")
                         .addValues(
-                            ParamValue.newBuilder().setStringValue("bar").build(),
+                            ParamValue.newBuilder().setStringValue("bar").build()
                         )
-                        .build(),
+                        .build()
                 )
                 .addOutputValues(
                     OutputValue.newBuilder()
                         .setName("repeatedStringOutput")
                         .addValues(
-                            ParamValue.newBuilder().setStringValue("bar1").build(),
+                            ParamValue.newBuilder().setStringValue("bar1").build()
                         )
                         .addValues(
-                            ParamValue.newBuilder().setStringValue("bar2").build(),
+                            ParamValue.newBuilder().setStringValue("bar2").build()
                         )
-                        .build(),
+                        .build()
                 )
                 .build()
         session.execute(
             buildRequestArgs(
                 SYNC, /* args...= */
                 "required",
-                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
+                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()
             ),
-            callback,
+            callback
         )
         assertThat(
             callback.receiveResponse()
                 .fulfillmentResponse!!
                 .getExecutionOutput()
-                .getOutputValuesList(),
+                .getOutputValuesList()
         )
             .containsExactlyElementsIn(expectedOutput.getOutputValuesList())
     }
@@ -988,9 +965,9 @@
             buildRequestArgs(
                 SYNC, /* args...= */
                 "required",
-                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
+                ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()
             ),
-            callback,
+            callback
         )
 
         assertThat(callback.receiveResponse().fulfillmentResponse!!.startDictation).isTrue()
@@ -1007,7 +984,7 @@
             Arguments,
             Output,
             Confirmation,
-            ExecutionSession,
+            ExecutionSession
             >(ACTION_SPEC) {
 
         init {
@@ -1025,38 +1002,38 @@
 
     companion object {
 
-        private val AUTO_ACCEPT_ENTITY_VALUE: AppEntityListener<EntityValue> =
-            object : AppEntityListener<EntityValue> {
+        private val AUTO_ACCEPT_STRING_VALUE: AppEntityListener<String> =
+            object : AppEntityListener<String> {
                 override fun lookupAndRenderAsync(
-                    searchAction: SearchAction<EntityValue>,
-                ): ListenableFuture<EntitySearchResult<EntityValue>> {
-                    val result: EntitySearchResult.Builder<EntityValue> =
+                    searchAction: SearchAction<String>
+                ): ListenableFuture<EntitySearchResult<String>> {
+                    val result: EntitySearchResult.Builder<String> =
                         EntitySearchResult.Builder()
                     return Futures.immediateFuture(
-                        result.addPossibleValue(EntityValue.ofId("valid1")).build(),
+                        result.addPossibleValue("valid1").build()
                     )
                 }
 
                 override fun onReceivedAsync(
-                    value: EntityValue,
+                    value: String
                 ): ListenableFuture<ValidationResult> {
                     return Futures.immediateFuture(ValidationResult.newAccepted())
                 }
             }
-        private val AUTO_REJECT_ENTITY_VALUE: AppEntityListener<EntityValue> =
-            object : AppEntityListener<EntityValue> {
+        private val AUTO_REJECT_STRING_VALUE: AppEntityListener<String> =
+            object : AppEntityListener<String> {
                 override fun lookupAndRenderAsync(
-                    searchAction: SearchAction<EntityValue>,
-                ): ListenableFuture<EntitySearchResult<EntityValue>> {
-                    val result: EntitySearchResult.Builder<EntityValue> =
+                    searchAction: SearchAction<String>
+                ): ListenableFuture<EntitySearchResult<String>> {
+                    val result: EntitySearchResult.Builder<String> =
                         EntitySearchResult.Builder()
                     return Futures.immediateFuture(
-                        result.addPossibleValue(EntityValue.ofId("valid1")).build(),
+                        result.addPossibleValue("valid1").build()
                     )
                 }
 
                 override fun onReceivedAsync(
-                    value: EntityValue,
+                    value: String
                 ): ListenableFuture<ValidationResult> {
                     return Futures.immediateFuture(ValidationResult.newRejected())
                 }
@@ -1079,65 +1056,63 @@
             }
         private val ACTION_SPEC: ActionSpec<Properties, Arguments, Output> =
             ActionSpecBuilder.ofCapabilityNamed(
-                CAPABILITY_NAME,
+                CAPABILITY_NAME
             )
                 .setDescriptor(Properties::class.java)
                 .setArguments(Arguments::class.java, Arguments::newBuilder)
                 .setOutput(Output::class.java)
                 .bindParameter(
                     "required",
-                    Properties::requiredEntityField,
-                    Arguments.Builder::setRequiredEntityField,
-                    TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                    TypeConverters.ENTITY_ENTITY_CONVERTER,
+                    Properties::requiredStringField,
+                    Arguments.Builder::setRequiredStringField,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindOptionalParameter(
                     "optional",
                     Properties::optionalStringField,
                     Arguments.Builder::setOptionalStringField,
                     TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindOptionalParameter(
                     "optionalEnum",
                     Properties::enumField,
                     Arguments.Builder::setEnumField,
                     ENUM_CONVERTER,
-                    { Entity.newBuilder().setIdentifier(it.toString()).build() },
+                    { Entity.newBuilder().setIdentifier(it.toString()).build() }
                 )
                 .bindRepeatedParameter(
                     "repeated",
                     Properties::repeatedStringField,
                     Arguments.Builder::setRepeatedStringField,
                     TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
                 )
                 .bindOptionalOutput(
                     "optionalStringOutput",
                     Output::optionalStringField,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue
                 )
                 .bindRepeatedOutput(
                     "repeatedStringOutput",
                     Output::repeatedStringField,
-                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER::toParamValue
                 )
                 .build()
 
         private val SINGLE_REQUIRED_FIELD_PROPERTY: Properties =
             Properties.newBuilder()
-                .setRequiredEntityField(
-                    Property.Builder<
-                        androidx.appactions.interaction.capabilities.core.properties.Entity,
-                        >()
+                .setRequiredStringField(
+                    Property.Builder<StringValue>()
                         .setRequired(true)
-                        .build(),
+                        .build()
                 )
                 .build()
 
         private fun getCurrentValues(
             argName: String,
-            appDialogState: AppDialogState,
+            appDialogState: AppDialogState
         ): List<CurrentValue> {
             return appDialogState
                 .getParamsList()
@@ -1156,14 +1131,14 @@
             property: Properties,
             sessionFactory: (hostProperties: HostProperties?) -> ExecutionSession,
             sessionBridge: SessionBridge<ExecutionSession, Confirmation>,
-            sessionUpdaterSupplier: Supplier<SessionUpdaterT>,
+            sessionUpdaterSupplier: Supplier<SessionUpdaterT>
         ): TaskCapabilityImpl<
             Properties,
             Arguments,
             Output,
             ExecutionSession,
             Confirmation,
-            SessionUpdaterT,
+            SessionUpdaterT
             > {
             return TaskCapabilityImpl(
                 "id",
@@ -1171,7 +1146,7 @@
                 property,
                 sessionFactory,
                 sessionBridge,
-                sessionUpdaterSupplier,
+                sessionUpdaterSupplier
             )
         }
     }
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.java
index 469c5a2..79532a1 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Arguments.java
@@ -18,7 +18,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
-import androidx.appactions.interaction.capabilities.core.values.EntityValue;
 
 import com.google.auto.value.AutoValue;
 
@@ -33,7 +32,7 @@
         return new AutoValue_Arguments.Builder();
     }
 
-    public abstract Optional<EntityValue> requiredEntityField();
+    public abstract Optional<String> requiredStringField();
 
     public abstract Optional<String> optionalStringField();
 
@@ -45,7 +44,7 @@
     @AutoValue.Builder
     public abstract static class Builder implements BuilderOf<Arguments> {
 
-        public abstract Builder setRequiredEntityField(EntityValue value);
+        public abstract Builder setRequiredStringField(String value);
 
         public abstract Builder setOptionalStringField(String value);
 
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
deleted file mode 100644
index 2a4bc3e..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.appactions.interaction.capabilities.core.testing.spec;
-
-import androidx.annotation.NonNull;
-import androidx.appactions.interaction.capabilities.core.BaseExecutionSession;
-import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
-import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
-import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
-import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder;
-import androidx.appactions.interaction.capabilities.core.properties.Entity;
-import androidx.appactions.interaction.capabilities.core.properties.Property;
-import androidx.appactions.interaction.capabilities.core.values.EntityValue;
-
-import com.google.auto.value.AutoValue;
-
-import java.util.Optional;
-
-public final class CapabilityTwoEntityValues {
-
-    private static final String CAPABILITY_NAME = "actions.intent.TEST";
-    public static final ActionSpec<Properties, Arguments, Void> ACTION_SPEC =
-            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-                    .setDescriptor(Properties.class)
-                    .setArguments(Arguments.class, Arguments::newBuilder)
-                    .bindOptionalParameter(
-                            "slotA",
-                            Properties::slotA,
-                            Arguments.Builder::setSlotA,
-                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                            TypeConverters.ENTITY_ENTITY_CONVERTER)
-                    .bindOptionalParameter(
-                            "slotB",
-                            Properties::slotB,
-                            Arguments.Builder::setSlotB,
-                            TypeConverters.ENTITY_PARAM_VALUE_CONVERTER,
-                            TypeConverters.ENTITY_ENTITY_CONVERTER)
-                    .build();
-
-    private CapabilityTwoEntityValues() {}
-
-    /** Two required strings */
-    @AutoValue
-    public abstract static class Arguments {
-        public static Builder newBuilder() {
-            return new AutoValue_CapabilityTwoEntityValues_Arguments.Builder();
-        }
-
-        public abstract Optional<EntityValue> slotA();
-
-        public abstract Optional<EntityValue> slotB();
-
-        /** Builder for the testing Arguments. */
-        @AutoValue.Builder
-        public abstract static class Builder implements BuilderOf<Arguments> {
-
-            public abstract Builder setSlotA(EntityValue value);
-
-            public abstract Builder setSlotB(EntityValue value);
-
-            @Override
-            public abstract Arguments build();
-        }
-    }
-
-    /** Two required strings */
-    @AutoValue
-    public abstract static class Properties {
-        @NonNull
-        public static Builder newBuilder() {
-            return new AutoValue_CapabilityTwoEntityValues_Properties.Builder();
-        }
-
-        public abstract Optional<Property<Entity>> slotA();
-
-        public abstract Optional<Property<Entity>> slotB();
-
-        /** Builder for {@link Property} */
-        @AutoValue.Builder
-        public abstract static class Builder {
-
-            @NonNull
-            public abstract Builder setSlotA(@NonNull Property<Entity> value);
-
-            @NonNull
-            public abstract Builder setSlotB(@NonNull Property<Entity> value);
-
-            @NonNull
-            public abstract Properties build();
-        }
-    }
-
-    public interface ExecutionSession extends BaseExecutionSession<Arguments, Void> {}
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
index 20f692d..9595d9c 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
@@ -17,6 +17,7 @@
 package androidx.appactions.interaction.capabilities.core.testing.spec;
 
 import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.BaseExecutionSession;
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
@@ -103,4 +104,6 @@
             public abstract Properties build();
         }
     }
+
+    public interface ExecutionSession extends BaseExecutionSession<Arguments, Void> {}
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/ExecutionSession.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/ExecutionSession.kt
index dbe44b3..936f2b9 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/ExecutionSession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/ExecutionSession.kt
@@ -20,11 +20,10 @@
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.ExecutionResult
 import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures
-import androidx.appactions.interaction.capabilities.core.values.EntityValue
 
 interface ExecutionSession : BaseExecutionSession<Arguments, Output> {
 
-    fun getRequiredEntityListener(): AppEntityListener<EntityValue>? = null
+    fun getRequiredStringListener(): AppEntityListener<String>? = null
 
     companion object {
         @JvmStatic
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Properties.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Properties.java
index fc9cb05..ee4846d 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Properties.java
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Properties.java
@@ -18,7 +18,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
-import androidx.appactions.interaction.capabilities.core.properties.Entity;
 import androidx.appactions.interaction.capabilities.core.properties.Property;
 import androidx.appactions.interaction.capabilities.core.properties.StringValue;
 
@@ -34,7 +33,7 @@
         return new AutoValue_Properties.Builder();
     }
 
-    public abstract Property<Entity> requiredEntityField();
+    public abstract Property<StringValue> requiredStringField();
 
     public abstract Optional<Property<StringValue>> optionalStringField();
 
@@ -46,7 +45,7 @@
     @AutoValue.Builder
     public abstract static class Builder implements BuilderOf<Properties> {
 
-        public abstract Builder setRequiredEntityField(Property<Entity> property);
+        public abstract Builder setRequiredStringField(Property<StringValue> property);
 
         public abstract Builder setOptionalStringField(Property<StringValue> property);
 
diff --git a/appactions/interaction/interaction-service-proto/build.gradle b/appactions/interaction/interaction-service-proto/build.gradle
index 884a1aa..89a9271 100644
--- a/appactions/interaction/interaction-service-proto/build.gradle
+++ b/appactions/interaction/interaction-service-proto/build.gradle
@@ -69,7 +69,7 @@
         // Add any additional directories specified in the "main" source set to the Java
         // source directories of the main source set.
         ofSourceSet("main").each { task ->
-            sourceSets.main.java.srcDir(task)
+            project.sourceSets.main.java.srcDir(task)
         }
         all().each { task ->
             task.builtins {
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 86dddcc..614c612 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -23,8 +23,8 @@
     api("androidx.fragment:fragment:1.5.4")
     api(project(":appcompat:appcompat-resources"))
     api("androidx.drawerlayout:drawerlayout:1.0.0")
-    implementation(projectOrArtifact(":lifecycle:lifecycle-runtime"))
-    implementation("androidx.lifecycle:lifecycle-viewmodel:2.5.1")
+    implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
     implementation("androidx.profileinstaller:profileinstaller:1.3.0")
     implementation("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
     api("androidx.savedstate:savedstate:1.2.1")
@@ -44,8 +44,8 @@
     androidTestImplementation(libs.espressoCore, excludes.espresso)
     androidTestImplementation(libs.mockitoCore, excludes.bytebuddy) // DexMaker has it's own MockMaker
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy) // DexMaker has it's own MockMaker
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-viewmodel"))
-    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.5.1", {
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1", {
         // Needed to ensure that the same version of lifecycle-runtime-ktx
         // is pulled into main and androidTest configurations. Otherwise,
         // potentially leads to confusing errors about resolution
diff --git a/appcompat/integration-tests/receive-content-testapp/build.gradle b/appcompat/integration-tests/receive-content-testapp/build.gradle
index b365be4..8d74ce9 100644
--- a/appcompat/integration-tests/receive-content-testapp/build.gradle
+++ b/appcompat/integration-tests/receive-content-testapp/build.gradle
@@ -34,7 +34,7 @@
     implementation(projectOrArtifact(":recyclerview:recyclerview"))
     implementation(libs.material)
 
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-common"))
+    androidTestImplementation("androidx.lifecycle:lifecycle-common:2.6.1")
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRules)
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index 7067cde0..cb5582f 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -232,7 +232,12 @@
             .file(BASELINE_PROFILE_FILENAME)
             .get()
             .asFile
-            .writeText(filteredProfileRules.joinToString(System.lineSeparator()))
+            .apply {
+                delete()
+                if (filteredProfileRules.isNotEmpty()) {
+                    writeText(filteredProfileRules.joinToString(System.lineSeparator()))
+                }
+            }
 
         // If this is a library we can stop here and don't manage the startup profiles.
         if (library.get()) {
@@ -243,6 +248,17 @@
         val startupRules = baselineProfileFileCollection.files
             .readLines { FILENAME_MATCHER_STARTUP_PROFILE in it.name }
 
+        if (variantName.isPresent && startupRules.isEmpty()) {
+            logger.warn(
+                """
+                No startup profile rules were generated for the variant `${variantName.get()}`.
+                This is most likely because there are no instrumentation test with baseline profile
+                rule, which specify `includeInStartupProfile = true`. If this is not intentional
+                check that tests for this variant exist in the `baselineProfile` dependency module.
+            """.trimIndent()
+            )
+        }
+
         // Use same sorting without filter for startup profiles.
         val sortedProfileRules = startupRules
             .asSequence()
@@ -258,7 +274,12 @@
             .file(STARTUP_PROFILE_FILENAME)
             .get()
             .asFile
-            .writeText(sortedProfileRules.joinToString(System.lineSeparator()))
+            .apply {
+                delete()
+                if (sortedProfileRules.isNotEmpty()) {
+                    writeText(sortedProfileRules.joinToString(System.lineSeparator()))
+                }
+            }
     }
 
     private fun Pair<RuleType, String>.isInclude(): Boolean = first == RuleType.INCLUDE
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index 69455db..a7e1cdf 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -151,6 +151,32 @@
     }
 
     @Test
+    fun testGenerateTaskWithNoFlavorsForApplicationAndNoStartupProfile() {
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN
+        )
+        projectSetup.producer.setupWithoutFlavors(
+            releaseProfileLines = listOf(
+                Fixtures.CLASS_1_METHOD_1,
+                Fixtures.CLASS_1,
+            ),
+            releaseStartupProfileLines = listOf()
+        )
+
+        gradleRunner
+            .withArguments("generateBaselineProfile", "--stacktrace")
+            .build()
+
+        assertThat(readBaselineProfileFileContent("release"))
+            .containsExactly(
+                Fixtures.CLASS_1,
+                Fixtures.CLASS_1_METHOD_1,
+            )
+
+        assertThat(startupProfileFile("release").exists()).isFalse()
+    }
+
+    @Test
     fun testGenerateTaskWithFlavorsAndDefaultMerge() {
         projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
diff --git a/benchmark/integration-tests/baselineprofile-consumer/src/release/generated/baselineProfiles/expected-startup-prof.txt b/benchmark/integration-tests/baselineprofile-consumer/src/release/generated/baselineProfiles/expected-startup-prof.txt
new file mode 100644
index 0000000..8ac4720
--- /dev/null
+++ b/benchmark/integration-tests/baselineprofile-consumer/src/release/generated/baselineProfiles/expected-startup-prof.txt
@@ -0,0 +1,538 @@
+Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;->$r8$lambda$CNqLK7smWTFjXaIfqGSDUWf8U50(Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;)V
+PLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;->$r8$lambda$G2Q0ZfVkJNYXyLJAm2IZ1xq3Lto(Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;Landroidx/profileinstaller/ProfileVerifier$CompilationStatus;)V
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;-><init>()V
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;->onCreate(Landroid/os/Bundle;)V
+PLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;->onResume$lambda$1$lambda$0(Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;Landroidx/profileinstaller/ProfileVerifier$CompilationStatus;)V
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;->onResume$lambda$1(Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;)V
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;->onResume()V
+PLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity$$ExternalSyntheticLambda0;-><init>(Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;Landroidx/profileinstaller/ProfileVerifier$CompilationStatus;)V
+PLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity$$ExternalSyntheticLambda0;->run()V
+Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity$$ExternalSyntheticLambda1;
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity$$ExternalSyntheticLambda1;-><init>(Landroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity;)V
+HSPLandroidx/benchmark/integration/baselineprofile/consumer/EmptyActivity$$ExternalSyntheticLambda1;->run()V
+Landroidx/concurrent/futures/AbstractResolvableFuture;
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture;-><clinit>()V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture;-><init>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->afterDone()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->clearListeners(Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;)Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->complete(Landroidx/concurrent/futures/AbstractResolvableFuture;)V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture;->get()Ljava/lang/Object;
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->getDoneValue(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->releaseWaiters()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->set(Ljava/lang/Object;)Z
+Landroidx/concurrent/futures/AbstractResolvableFuture$AtomicHelper;
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$AtomicHelper;-><init>()V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$AtomicHelper;-><init>(Landroidx/concurrent/futures/AbstractResolvableFuture$1;)V
+Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Listener;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Listener;-><init>(Ljava/lang/Runnable;Ljava/util/concurrent/Executor;)V
+Landroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;-><init>(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;)V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casListeners(Landroidx/concurrent/futures/AbstractResolvableFuture;Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casValue(Landroidx/concurrent/futures/AbstractResolvableFuture;Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casWaiters(Landroidx/concurrent/futures/AbstractResolvableFuture;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;)Z
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->putNext(Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;)V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->putThread(Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;Ljava/lang/Thread;)V
+Landroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper$$ExternalSyntheticBackportWithForwarding0;
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper$$ExternalSyntheticBackportWithForwarding0;->m(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
+Landroidx/concurrent/futures/AbstractResolvableFuture$SetFuture;
+Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><clinit>()V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><init>()V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><init>(Z)V
+HSPLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;->setNext(Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;)V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;->unpark()V
+Landroidx/concurrent/futures/ResolvableFuture;
+HSPLandroidx/concurrent/futures/ResolvableFuture;-><init>()V
+HSPLandroidx/concurrent/futures/ResolvableFuture;->create()Landroidx/concurrent/futures/ResolvableFuture;
+PLandroidx/concurrent/futures/ResolvableFuture;->set(Ljava/lang/Object;)Z
+Landroidx/constraintlayout/solver/ArrayLinkedVariables;
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
+Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>()V
+HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addSingleError(Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubject(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubjectInVariables(Landroidx/constraintlayout/solver/LinearSystem;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowEquals(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->ensurePositiveConstant()V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->getKey()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->hasKeyVariable()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isEmpty()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isNew(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->pivot(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->reset()V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/SolverVariable;Z)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromSystem(Landroidx/constraintlayout/solver/LinearSystem;)V
+Landroidx/constraintlayout/solver/ArrayRow$ArrayRowVariables;
+Landroidx/constraintlayout/solver/Cache;
+HSPLandroidx/constraintlayout/solver/Cache;-><init>()V
+Landroidx/constraintlayout/solver/LinearSystem;
+HSPLandroidx/constraintlayout/solver/LinearSystem;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;-><init>()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addConstraint(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;I)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addRow(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addSingleError(Landroidx/constraintlayout/solver/ArrayRow;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->computeValues()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(ILjava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createObjectVariable(Ljava/lang/Object;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createRow()Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createSlackVariable()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->enforceBFS(Landroidx/constraintlayout/solver/LinearSystem$Row;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getCache()Landroidx/constraintlayout/solver/Cache;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getMetrics()Landroidx/constraintlayout/solver/Metrics;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Ljava/lang/Object;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->increaseTableSize()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimize()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimizeGoal(Landroidx/constraintlayout/solver/LinearSystem$Row;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/LinearSystem$Row;Z)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->releaseRows()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->reset()V
+Landroidx/constraintlayout/solver/LinearSystem$Row;
+Landroidx/constraintlayout/solver/LinearSystem$ValuesRow;
+HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/Cache;)V
+Landroidx/constraintlayout/solver/Pools$Pool;
+Landroidx/constraintlayout/solver/Pools$SimplePool;
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>(I)V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->acquire()Ljava/lang/Object;
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Ljava/lang/Object;)Z
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->releaseAll([Ljava/lang/Object;I)V
+Landroidx/constraintlayout/solver/PriorityGoalRow;
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addError(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->clear()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate(Landroidx/constraintlayout/solver/LinearSystem;[Z)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->removeGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
+Landroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;Landroidx/constraintlayout/solver/PriorityGoalRow;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;F)Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->init(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->isNegative()Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->reset()V
+Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->addToRow(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->increaseErrorId()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->removeFromRow(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->reset()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setFinalValue(Landroidx/constraintlayout/solver/LinearSystem;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setType(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->updateReferencesWithNewDefinition(Landroidx/constraintlayout/solver/ArrayRow;)V
+Landroidx/constraintlayout/solver/SolverVariable$Type;
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><init>(Ljava/lang/String;I)V
+Landroidx/constraintlayout/solver/SolverVariableValues;
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->add(Landroidx/constraintlayout/solver/SolverVariable;FZ)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addToHashMap(Landroidx/constraintlayout/solver/SolverVariable;I)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->clear()V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->divideByAmount(F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->findEmptySlot()I
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->get(Landroidx/constraintlayout/solver/SolverVariable;)F
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getCurrentSize()I
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariableValue(I)F
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->indexOf(Landroidx/constraintlayout/solver/SolverVariable;)I
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->insertVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->invert()V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->put(Landroidx/constraintlayout/solver/SolverVariable;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->remove(Landroidx/constraintlayout/solver/SolverVariable;Z)F
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->removeFromHashMap(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->use(Landroidx/constraintlayout/solver/ArrayRow;Z)F
+Landroidx/constraintlayout/solver/widgets/Barrier;
+Landroidx/constraintlayout/solver/widgets/ChainHead;
+Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIZ)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getMargin()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getSolverVariable()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getTarget()Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->isConnected()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->reset()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable(Landroidx/constraintlayout/solver/Cache;)V
+Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addAnchors()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addFirst()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;ZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->createObjectVariables(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getAnchor(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getBaselineDistance()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getCompanionWidget()Ljava/lang/Object;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour(I)Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHorizontalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinWidth()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getParent()Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVerticalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVisibility()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getWidth()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getX()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getY()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->immediateConnect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;II)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isChainHead(I)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInHorizontalChain()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInVerticalChain()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->reset()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setBaselineDistance(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setCompanionWidget(Ljava/lang/Object;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setDimensionRatio(Ljava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setFrame(IIII)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHasBaseline(Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setInBarrier(IZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVisibility(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setX(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setY(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->updateFromSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$1;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$1;-><clinit>()V
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getMeasurer()Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getOptimizationLevel()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isHeightMeasuredTooSmall()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isWidthMeasuredTooSmall()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->layout()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->measure(IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->optimizeFor(I)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetChains()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setOptimizationLevel(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setRtl(Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateChildrenFromSolver(Landroidx/constraintlayout/solver/LinearSystem;[Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateHierarchy()V
+Landroidx/constraintlayout/solver/widgets/Guideline;
+Landroidx/constraintlayout/solver/widgets/Helper;
+Landroidx/constraintlayout/solver/widgets/HelperWidget;
+Landroidx/constraintlayout/solver/widgets/Optimizer;
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->checkMatchParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->enabled(II)Z
+Landroidx/constraintlayout/solver/widgets/VirtualLayout;
+Landroidx/constraintlayout/solver/widgets/WidgetContainer;
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->add(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->removeAllChildren()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measure(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measureChildren(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Ljava/lang/String;II)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solverMeasure(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->updateHierarchy(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;-><init>()V
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+Landroidx/constraintlayout/solver/widgets/analyzer/Dependency;
+Landroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><init>(Ljava/lang/String;I)V
+Landroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><init>(Ljava/lang/String;I)V
+Landroidx/constraintlayout/widget/ConstraintHelper;
+Landroidx/constraintlayout/widget/ConstraintLayout;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->access$000(Landroidx/constraintlayout/widget/ConstraintLayout;)Ljava/util/ArrayList;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->applyConstraintsFromLayoutParams(ZLandroid/view/View;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;Landroid/util/SparseArray;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->checkLayoutParams(Landroid/view/ViewGroup$LayoutParams;)Z
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->dispatchDraw(Landroid/graphics/Canvas;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroid/view/ViewGroup$LayoutParams;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getPaddingWidth()I
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getViewWidget(Landroid/view/View;)Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->init(Landroid/util/AttributeSet;II)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->isRtl()Z
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->markHierarchyDirty()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onLayout(ZIIII)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onMeasure(II)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onViewAdded(Landroid/view/View;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->requestLayout()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveMeasuredDimension(IIIIZZ)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;III)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setChildrenConstraints()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setSelfDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIII)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->updateHierarchy()Z
+Landroidx/constraintlayout/widget/ConstraintLayout$1;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$1;-><clinit>()V
+Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->resolveLayoutDirection(I)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->validate()V
+Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;-><clinit>()V
+Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->captureLayoutInfos(IIIIII)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->didMeasures()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->measure(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;)V
+Landroidx/constraintlayout/widget/ConstraintLayoutStates;
+Landroidx/constraintlayout/widget/ConstraintSet;
+Landroidx/constraintlayout/widget/Guideline;
+Landroidx/constraintlayout/widget/Placeholder;
+Landroidx/constraintlayout/widget/R$styleable;
+HSPLandroidx/constraintlayout/widget/R$styleable;-><clinit>()V
+Landroidx/constraintlayout/widget/VirtualLayout;
+Landroidx/core/app/CoreComponentFactory;
+HSPLandroidx/core/app/CoreComponentFactory;-><init>()V
+HSPLandroidx/core/app/CoreComponentFactory;->checkCompatWrapper(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateActivity(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Activity;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateApplication(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/app/Application;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateProvider(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/content/ContentProvider;
+PLandroidx/core/app/CoreComponentFactory;->instantiateReceiver(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/content/BroadcastReceiver;
+Landroidx/core/app/CoreComponentFactory$CompatWrapped;
+Landroidx/core/app/NavUtils$$ExternalSyntheticApiModelOutline0;
+HSPLandroidx/core/app/NavUtils$$ExternalSyntheticApiModelOutline0;->m$1(Landroidx/constraintlayout/widget/ConstraintLayout;)I
+HSPLandroidx/core/app/NavUtils$$ExternalSyntheticApiModelOutline0;->m$2(Landroidx/constraintlayout/widget/ConstraintLayout;)I
+HSPLandroidx/core/app/NavUtils$$ExternalSyntheticApiModelOutline0;->m(Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;)I
+HSPLandroidx/core/app/NavUtils$$ExternalSyntheticApiModelOutline0;->m(Landroidx/constraintlayout/widget/ConstraintLayout;)I
+Landroidx/core/os/TraceCompat$$ExternalSyntheticApiModelOutline0;
+HSPLandroidx/core/os/TraceCompat$$ExternalSyntheticApiModelOutline0;->m()Z
+Landroidx/profileinstaller/Encoding$$ExternalSyntheticApiModelOutline0;
+HSPLandroidx/profileinstaller/Encoding$$ExternalSyntheticApiModelOutline0;->m()Landroid/view/Choreographer;
+HSPLandroidx/profileinstaller/Encoding$$ExternalSyntheticApiModelOutline0;->m(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLandroidx/profileinstaller/Encoding$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/Choreographer;Landroid/view/Choreographer$FrameCallback;)V
+PLandroidx/profileinstaller/ProfileInstallReceiver;-><init>()V
+PLandroidx/profileinstaller/ProfileInstallReceiver;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V
+PLandroidx/profileinstaller/ProfileInstallReceiver;->saveProfile(Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;)V
+PLandroidx/profileinstaller/ProfileInstallReceiver$$ExternalSyntheticLambda0;-><init>()V
+PLandroidx/profileinstaller/ProfileInstallReceiver$ResultDiagnostics;-><init>(Landroidx/profileinstaller/ProfileInstallReceiver;)V
+PLandroidx/profileinstaller/ProfileInstallReceiver$ResultDiagnostics;->onResultReceived(ILjava/lang/Object;)V
+PLandroidx/profileinstaller/ProfileInstaller;-><clinit>()V
+PLandroidx/profileinstaller/ProfileInstaller;->hasAlreadyWrittenProfileForThisInstall(Landroid/content/pm/PackageInfo;Ljava/io/File;Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;)Z
+PLandroidx/profileinstaller/ProfileInstaller;->writeProfile(Landroid/content/Context;)V
+PLandroidx/profileinstaller/ProfileInstaller;->writeProfile(Landroid/content/Context;Ljava/util/concurrent/Executor;Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;)V
+PLandroidx/profileinstaller/ProfileInstaller;->writeProfile(Landroid/content/Context;Ljava/util/concurrent/Executor;Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;Z)V
+PLandroidx/profileinstaller/ProfileInstaller$1;-><init>()V
+PLandroidx/profileinstaller/ProfileInstaller$1;->onResultReceived(ILjava/lang/Object;)V
+PLandroidx/profileinstaller/ProfileInstaller$2;-><init>()V
+PLandroidx/profileinstaller/ProfileInstaller$2;->onResultReceived(ILjava/lang/Object;)V
+Landroidx/profileinstaller/ProfileInstallerInitializer;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;-><init>()V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->create(Landroid/content/Context;)Landroidx/profileinstaller/ProfileInstallerInitializer$Result;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->delayAfterFirstFrame(Landroid/content/Context;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->installAfterDelay(Landroid/content/Context;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->lambda$delayAfterFirstFrame$0$androidx-profileinstaller-ProfileInstallerInitializer(Landroid/content/Context;)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer;->lambda$installAfterDelay$1(Landroid/content/Context;)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer;->lambda$writeInBackground$2(Landroid/content/Context;)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer;->writeInBackground(Landroid/content/Context;)V
+Landroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;-><init>(Landroid/content/Context;)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;->run()V
+Landroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;-><init>(Landroidx/profileinstaller/ProfileInstallerInitializer;Landroid/content/Context;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;->run()V
+PLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda2;-><init>(Landroid/content/Context;)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda2;->run()V
+Landroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;->lambda$postFrameCallback$0(Ljava/lang/Runnable;J)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;->postFrameCallback(Ljava/lang/Runnable;)V
+Landroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl$$ExternalSyntheticLambda2;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl$$ExternalSyntheticLambda2;-><init>(Ljava/lang/Runnable;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl$$ExternalSyntheticLambda2;->doFrame(J)V
+Landroidx/profileinstaller/ProfileInstallerInitializer$Handler28Impl;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Handler28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
+Landroidx/profileinstaller/ProfileInstallerInitializer$Result;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Result;-><init>()V
+Landroidx/profileinstaller/ProfileVerifier;
+HSPLandroidx/profileinstaller/ProfileVerifier;-><clinit>()V
+HSPLandroidx/profileinstaller/ProfileVerifier;->getCompilationStatusAsync()Lcom/google/common/util/concurrent/ListenableFuture;
+PLandroidx/profileinstaller/ProfileVerifier;->getPackageLastUpdateTime(Landroid/content/Context;)J
+PLandroidx/profileinstaller/ProfileVerifier;->setCompilationStatus(IZZ)Landroidx/profileinstaller/ProfileVerifier$CompilationStatus;
+PLandroidx/profileinstaller/ProfileVerifier;->writeProfileVerification(Landroid/content/Context;Z)Landroidx/profileinstaller/ProfileVerifier$CompilationStatus;
+PLandroidx/profileinstaller/ProfileVerifier$Api33Impl;->getPackageInfo(Landroid/content/pm/PackageManager;Landroid/content/Context;)Landroid/content/pm/PackageInfo;
+PLandroidx/profileinstaller/ProfileVerifier$Cache;-><init>(IIJJ)V
+PLandroidx/profileinstaller/ProfileVerifier$Cache;->writeOnFile(Ljava/io/File;)V
+Landroidx/profileinstaller/ProfileVerifier$CompilationStatus;
+PLandroidx/profileinstaller/ProfileVerifier$CompilationStatus;-><init>(IZZ)V
+PLandroidx/profileinstaller/ProfileVerifier$CompilationStatus;->getProfileInstallResultCode()I
+PLandroidx/profileinstaller/ProfileVerifier$CompilationStatus;->hasProfileEnqueuedForCompilation()Z
+PLandroidx/profileinstaller/ProfileVerifier$CompilationStatus;->isCompiledWithProfile()Z
+Landroidx/startup/AppInitializer;
+HSPLandroidx/startup/AppInitializer;-><clinit>()V
+HSPLandroidx/startup/AppInitializer;-><init>(Landroid/content/Context;)V
+HSPLandroidx/startup/AppInitializer;->discoverAndInitialize()V
+HSPLandroidx/startup/AppInitializer;->discoverAndInitialize(Landroid/os/Bundle;)V
+HSPLandroidx/startup/AppInitializer;->doInitialize(Ljava/lang/Class;Ljava/util/Set;)Ljava/lang/Object;
+HSPLandroidx/startup/AppInitializer;->getInstance(Landroid/content/Context;)Landroidx/startup/AppInitializer;
+Landroidx/startup/InitializationProvider;
+HSPLandroidx/startup/InitializationProvider;-><init>()V
+HSPLandroidx/startup/InitializationProvider;->onCreate()Z
+Landroidx/startup/Initializer;
+Landroidx/startup/R$string;
+Landroidx/tracing/Trace;
+HSPLandroidx/tracing/Trace;->beginSection(Ljava/lang/String;)V
+HSPLandroidx/tracing/Trace;->endSection()V
+HSPLandroidx/tracing/Trace;->isEnabled()Z
+Landroidx/tracing/TraceApi18Impl;
+HSPLandroidx/tracing/TraceApi18Impl;->beginSection(Ljava/lang/String;)V
+HSPLandroidx/tracing/TraceApi18Impl;->endSection()V
+Lcom/google/common/util/concurrent/ListenableFuture;
+PLkotlin/Pair;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+PLkotlin/Pair;->component1()Ljava/lang/Object;
+PLkotlin/Pair;->component2()Ljava/lang/Object;
+PLkotlin/Pair;->getFirst()Ljava/lang/Object;
+PLkotlin/Pair;->getSecond()Ljava/lang/Object;
+PLkotlin/TuplesKt;->to(Ljava/lang/Object;Ljava/lang/Object;)Lkotlin/Pair;
+PLkotlin/collections/ArraysKt___ArraysJvmKt;->asList([Ljava/lang/Object;)Ljava/util/List;
+PLkotlin/collections/ArraysUtilJVM;->asList([Ljava/lang/Object;)Ljava/util/List;
+PLkotlin/collections/CollectionsKt__CollectionsKt;->getLastIndex(Ljava/util/List;)I
+PLkotlin/collections/CollectionsKt__CollectionsKt;->optimizeReadOnlyList(Ljava/util/List;)Ljava/util/List;
+PLkotlin/collections/CollectionsKt__IterablesKt;->collectionSizeOrDefault(Ljava/lang/Iterable;I)I
+PLkotlin/collections/CollectionsKt___CollectionsKt;->joinTo$default(Ljava/lang/Iterable;Ljava/lang/Appendable;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;ILjava/lang/CharSequence;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Appendable;
+PLkotlin/collections/CollectionsKt___CollectionsKt;->joinTo(Ljava/lang/Iterable;Ljava/lang/Appendable;Ljava/lang/CharSequence;Ljava/lang/CharSequence;Ljava/lang/CharSequence;ILjava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Ljava/lang/Appendable;
+PLkotlin/collections/CollectionsKt___CollectionsKt;->minOrNull(Ljava/lang/Iterable;)Ljava/lang/Comparable;
+PLkotlin/collections/IntIterator;-><init>()V
+PLkotlin/internal/ProgressionUtilKt;->differenceModulo(III)I
+PLkotlin/internal/ProgressionUtilKt;->getProgressionLastElement(III)I
+PLkotlin/internal/ProgressionUtilKt;->mod(II)I
+Lkotlin/jvm/internal/Intrinsics;
+PLkotlin/jvm/internal/Intrinsics;->checkNotNull(Ljava/lang/Object;Ljava/lang/String;)V
+PLkotlin/jvm/internal/Intrinsics;->checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
+PLkotlin/jvm/internal/Lambda;-><init>(I)V
+PLkotlin/ranges/IntProgression;-><clinit>()V
+PLkotlin/ranges/IntProgression;-><init>(III)V
+PLkotlin/ranges/IntProgression;->getFirst()I
+PLkotlin/ranges/IntProgression;->getLast()I
+PLkotlin/ranges/IntProgression;->getStep()I
+PLkotlin/ranges/IntProgression;->iterator()Ljava/util/Iterator;
+PLkotlin/ranges/IntProgression;->iterator()Lkotlin/collections/IntIterator;
+PLkotlin/ranges/IntProgression$Companion;-><init>()V
+PLkotlin/ranges/IntProgression$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+PLkotlin/ranges/IntProgressionIterator;-><init>(III)V
+PLkotlin/ranges/IntProgressionIterator;->hasNext()Z
+PLkotlin/ranges/IntProgressionIterator;->nextInt()I
+PLkotlin/ranges/IntRange;-><clinit>()V
+PLkotlin/ranges/IntRange;-><init>(II)V
+PLkotlin/ranges/IntRange;->getEndInclusive()Ljava/lang/Integer;
+PLkotlin/ranges/IntRange;->getStart()Ljava/lang/Integer;
+PLkotlin/ranges/IntRange$Companion;-><init>()V
+PLkotlin/ranges/IntRange$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+PLkotlin/ranges/RangesKt___RangesKt;->coerceAtLeast(II)I
+PLkotlin/ranges/RangesKt___RangesKt;->coerceAtMost(II)I
+PLkotlin/ranges/RangesKt___RangesKt;->coerceIn(III)I
+PLkotlin/ranges/RangesKt___RangesKt;->until(II)Lkotlin/ranges/IntRange;
+PLkotlin/sequences/SequencesKt___SequencesKt;->map(Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence;
+PLkotlin/sequences/SequencesKt___SequencesKt;->toCollection(Lkotlin/sequences/Sequence;Ljava/util/Collection;)Ljava/util/Collection;
+PLkotlin/sequences/SequencesKt___SequencesKt;->toList(Lkotlin/sequences/Sequence;)Ljava/util/List;
+PLkotlin/sequences/SequencesKt___SequencesKt;->toMutableList(Lkotlin/sequences/Sequence;)Ljava/util/List;
+PLkotlin/sequences/TransformingSequence;-><init>(Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)V
+PLkotlin/sequences/TransformingSequence;->access$getSequence$p(Lkotlin/sequences/TransformingSequence;)Lkotlin/sequences/Sequence;
+PLkotlin/sequences/TransformingSequence;->access$getTransformer$p(Lkotlin/sequences/TransformingSequence;)Lkotlin/jvm/functions/Function1;
+PLkotlin/sequences/TransformingSequence;->iterator()Ljava/util/Iterator;
+PLkotlin/sequences/TransformingSequence$iterator$1;-><init>(Lkotlin/sequences/TransformingSequence;)V
+PLkotlin/sequences/TransformingSequence$iterator$1;->hasNext()Z
+PLkotlin/sequences/TransformingSequence$iterator$1;->next()Ljava/lang/Object;
+PLkotlin/text/CharsKt__CharJVMKt;->isWhitespace(C)Z
+PLkotlin/text/DelimitedRangesSequence;-><init>(Ljava/lang/CharSequence;IILkotlin/jvm/functions/Function2;)V
+PLkotlin/text/DelimitedRangesSequence;->access$getGetNextMatch$p(Lkotlin/text/DelimitedRangesSequence;)Lkotlin/jvm/functions/Function2;
+PLkotlin/text/DelimitedRangesSequence;->access$getInput$p(Lkotlin/text/DelimitedRangesSequence;)Ljava/lang/CharSequence;
+PLkotlin/text/DelimitedRangesSequence;->access$getLimit$p(Lkotlin/text/DelimitedRangesSequence;)I
+PLkotlin/text/DelimitedRangesSequence;->access$getStartIndex$p(Lkotlin/text/DelimitedRangesSequence;)I
+PLkotlin/text/DelimitedRangesSequence;->iterator()Ljava/util/Iterator;
+PLkotlin/text/DelimitedRangesSequence$iterator$1;-><init>(Lkotlin/text/DelimitedRangesSequence;)V
+PLkotlin/text/DelimitedRangesSequence$iterator$1;->calcNext()V
+PLkotlin/text/DelimitedRangesSequence$iterator$1;->hasNext()Z
+PLkotlin/text/DelimitedRangesSequence$iterator$1;->next()Ljava/lang/Object;
+PLkotlin/text/DelimitedRangesSequence$iterator$1;->next()Lkotlin/ranges/IntRange;
+PLkotlin/text/StringsKt__AppendableKt;->appendElement(Ljava/lang/Appendable;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+PLkotlin/text/StringsKt__IndentKt;->getIndentFunction$StringsKt__IndentKt(Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
+PLkotlin/text/StringsKt__IndentKt;->indentWidth$StringsKt__IndentKt(Ljava/lang/String;)I
+PLkotlin/text/StringsKt__IndentKt;->replaceIndent(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+PLkotlin/text/StringsKt__IndentKt;->trimIndent(Ljava/lang/String;)Ljava/lang/String;
+PLkotlin/text/StringsKt__IndentKt$getIndentFunction$1;-><clinit>()V
+PLkotlin/text/StringsKt__IndentKt$getIndentFunction$1;-><init>()V
+PLkotlin/text/StringsKt__IndentKt$getIndentFunction$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+PLkotlin/text/StringsKt__IndentKt$getIndentFunction$1;->invoke(Ljava/lang/String;)Ljava/lang/String;
+PLkotlin/text/StringsKt__StringsJVMKt;->isBlank(Ljava/lang/CharSequence;)Z
+PLkotlin/text/StringsKt__StringsJVMKt;->regionMatches(Ljava/lang/String;ILjava/lang/String;IIZ)Z
+PLkotlin/text/StringsKt__StringsKt;->access$findAnyOf(Ljava/lang/CharSequence;Ljava/util/Collection;IZZ)Lkotlin/Pair;
+PLkotlin/text/StringsKt__StringsKt;->findAnyOf$StringsKt__StringsKt(Ljava/lang/CharSequence;Ljava/util/Collection;IZZ)Lkotlin/Pair;
+PLkotlin/text/StringsKt__StringsKt;->getIndices(Ljava/lang/CharSequence;)Lkotlin/ranges/IntRange;
+PLkotlin/text/StringsKt__StringsKt;->getLastIndex(Ljava/lang/CharSequence;)I
+PLkotlin/text/StringsKt__StringsKt;->lineSequence(Ljava/lang/CharSequence;)Lkotlin/sequences/Sequence;
+PLkotlin/text/StringsKt__StringsKt;->lines(Ljava/lang/CharSequence;)Ljava/util/List;
+PLkotlin/text/StringsKt__StringsKt;->rangesDelimitedBy$StringsKt__StringsKt$default(Ljava/lang/CharSequence;[Ljava/lang/String;IZIILjava/lang/Object;)Lkotlin/sequences/Sequence;
+PLkotlin/text/StringsKt__StringsKt;->rangesDelimitedBy$StringsKt__StringsKt(Ljava/lang/CharSequence;[Ljava/lang/String;IZI)Lkotlin/sequences/Sequence;
+PLkotlin/text/StringsKt__StringsKt;->requireNonNegativeLimit(I)V
+PLkotlin/text/StringsKt__StringsKt;->splitToSequence$default(Ljava/lang/CharSequence;[Ljava/lang/String;ZIILjava/lang/Object;)Lkotlin/sequences/Sequence;
+PLkotlin/text/StringsKt__StringsKt;->splitToSequence(Ljava/lang/CharSequence;[Ljava/lang/String;ZI)Lkotlin/sequences/Sequence;
+PLkotlin/text/StringsKt__StringsKt;->substring(Ljava/lang/CharSequence;Lkotlin/ranges/IntRange;)Ljava/lang/String;
+PLkotlin/text/StringsKt__StringsKt$rangesDelimitedBy$2;-><init>(Ljava/util/List;Z)V
+PLkotlin/text/StringsKt__StringsKt$rangesDelimitedBy$2;->invoke(Ljava/lang/CharSequence;I)Lkotlin/Pair;
+PLkotlin/text/StringsKt__StringsKt$rangesDelimitedBy$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+PLkotlin/text/StringsKt__StringsKt$splitToSequence$1;-><init>(Ljava/lang/CharSequence;)V
+PLkotlin/text/StringsKt__StringsKt$splitToSequence$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+PLkotlin/text/StringsKt__StringsKt$splitToSequence$1;->invoke(Lkotlin/ranges/IntRange;)Ljava/lang/String;
+PLkotlin/text/StringsKt___StringsKt;->drop(Ljava/lang/String;I)Ljava/lang/String;
\ No newline at end of file
diff --git a/benchmark/integration-tests/baselineprofile-test-utils/utils.gradle b/benchmark/integration-tests/baselineprofile-test-utils/utils.gradle
index b8a42a7..1ec437f 100644
--- a/benchmark/integration-tests/baselineprofile-test-utils/utils.gradle
+++ b/benchmark/integration-tests/baselineprofile-test-utils/utils.gradle
@@ -1,4 +1,3 @@
-import com.android.build.api.artifact.SingleArtifact
 import org.gradle.work.DisableCachingByDefault
 
 import static androidx.baselineprofile.gradle.utils.UtilsKt.camelCase
@@ -21,14 +20,23 @@
     @TaskAction
     void exec() {
 
+        File expectedFile = getExpectedFile().get().asFile
         File actualFile = new File(actualFilePath.get())
+
+        if (!expectedFile.exists() && actualFile.exists()) {
+            throw new GradleException("No profile was expected in ${actualFile.absolutePath}.")
+        }
+        if (expectedFile.exists() && !actualFile.exists()) {
+            throw new GradleException("A profile was expected in ${actualFile.absolutePath}.")
+        }
+
         if (!actualFile.exists()) {
             throw new GradleException(
-                    "A baseline profile was expected in ${actualFile.absolutePath}."
+                    "A profile was expected in ${actualFile.absolutePath}."
             )
         }
 
-        def expectedIter = getExpectedFile().get().asFile.text.lines().iterator()
+        def expectedIter = expectedFile.text.lines().iterator()
         def actualIter = actualFile.text.lines().iterator()
 
         def lineCounter = 0
@@ -43,11 +51,9 @@
         }
 
         if (!diff.isEmpty()) {
-            logger.error("Actual generated baseline profile differs from expected one: \n\t"
+            logger.error("Actual generated profile differs from expected one: \n\t"
                     + diff.join("\n\t"))
-            throw new GradleException(
-                    "Actual generated baseline profile differs from expected one."
-            )
+            throw new GradleException("Actual generated profile differs from expected one.")
         }
 
         // This deletes the actual file since it's a test artifact
@@ -59,14 +65,20 @@
 // present and have the same content.
 def testTaskProviders = []
 
-def registerAssertTask(ArrayList<TaskProvider<Task>> testTaskProviders, String variantName) {
+def registerAssertTask(
+        ArrayList<TaskProvider<Task>> testTaskProviders,
+        String variantName,
+        String taskName,
+        String expectedFilename,
+        String filename
+) {
 
     def expectedBaselineProfileSubDir = "generated/baselineProfiles"
 
     def expectedFile = project
             .layout
             .projectDirectory
-            .file("src/$variantName/$expectedBaselineProfileSubDir/expected-baseline-prof.txt")
+            .file("src/$variantName/$expectedBaselineProfileSubDir/${expectedFilename}.txt")
 
     // If there is no expected file then skip testing this variant.
     if (!expectedFile.asFile.exists()) {
@@ -74,14 +86,14 @@
     }
 
     def taskProvider = project.tasks.register(
-            camelCase("test", variantName, "baselineProfileGeneration"),
+            camelCase("test", variantName, "${taskName}Generation"),
             AssertEqualsAndCleanUpTask
     ) {
         it.expectedFile.set(expectedFile)
         it.actualFilePath.set(project
                 .layout
                 .projectDirectory
-                .file("src/$variantName/$expectedBaselineProfileSubDir/baseline-prof.txt")
+                .file("src/$variantName/$expectedBaselineProfileSubDir/${filename}.txt")
                 .getAsFile()
                 .absolutePath)
 
@@ -95,7 +107,20 @@
 // An assert task is defined per variant
 androidComponents {
     onVariants(selector().all()) { variant ->
-        registerAssertTask(testTaskProviders, variant.name)
+        registerAssertTask(
+                testTaskProviders,
+                variant.name,
+                "baselineProfile",
+                "expected-baseline-prof",
+                "baseline-prof"
+        )
+        registerAssertTask(
+                testTaskProviders,
+                variant.name,
+                "startupProfile",
+                "expected-startup-prof",
+                "startup-prof"
+        )
     }
 }
 
diff --git a/biometric/biometric/src/main/res/values-te/strings.xml b/biometric/biometric/src/main/res/values-te/strings.xml
index 878b295..b10f71e 100644
--- a/biometric/biometric/src/main/res/values-te/strings.xml
+++ b/biometric/biometric/src/main/res/values-te/strings.xml
@@ -23,7 +23,7 @@
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"వేలిముద్రలు నమోదు చేయబడలేదు."</string>
     <string name="fingerprint_error_hw_not_present" msgid="6306988885793029438">"ఈ పరికరంలో వేలిముద్ర సెన్సార్ లేదు"</string>
     <string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"వేలిముద్ర చర్యని వినియోగదారు రద్దు చేశారు."</string>
-    <string name="fingerprint_error_lockout" msgid="7291787166416782245">"చాలా ఎక్కువ ప్రయత్నాలు చేశారు. దయచేసి తర్వాత మళ్లీ ప్రయత్నించండి."</string>
+    <string name="fingerprint_error_lockout" msgid="7291787166416782245">"చాలా ఎక్కువ ప్రయత్నాలు చేశారు. దయచేసి తర్వాత మళ్లీ ట్రై చేయండి."</string>
     <string name="default_error_msg" msgid="4776854077120974966">"తెలియని ఎర్రర్"</string>
     <string name="generic_error_user_canceled" msgid="7309881387583143581">"వినియోగదారు ద్వారా ప్రామాణీకరణ రద్దు చేయబడింది"</string>
     <string name="confirm_device_credential_password" msgid="5912733858573823945">"పాస్‌వర్డ్‌ను ఉపయోగించండి"</string>
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index cdb57e9..5a47765 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -21,6 +21,15 @@
     property public final int timeoutMillis;
   }
 
+  public final class BluetoothAddress {
+    ctor public BluetoothAddress(String address, int addressType);
+    method public String getAddress();
+    method public int getAddressType();
+    method public void setAddressType(int);
+    property public final String address;
+    property public final int addressType;
+  }
+
   public final class BluetoothDevice {
     method @RequiresPermission(anyOf={"android.permission.BLUETOOTH", "android.permission.BLUETOOTH_CONNECT"}) public int getBondState();
     method public java.util.UUID getId();
diff --git a/bluetooth/bluetooth/api/public_plus_experimental_current.txt b/bluetooth/bluetooth/api/public_plus_experimental_current.txt
index cdb57e9..5a47765 100644
--- a/bluetooth/bluetooth/api/public_plus_experimental_current.txt
+++ b/bluetooth/bluetooth/api/public_plus_experimental_current.txt
@@ -21,6 +21,15 @@
     property public final int timeoutMillis;
   }
 
+  public final class BluetoothAddress {
+    ctor public BluetoothAddress(String address, int addressType);
+    method public String getAddress();
+    method public int getAddressType();
+    method public void setAddressType(int);
+    property public final String address;
+    property public final int addressType;
+  }
+
   public final class BluetoothDevice {
     method @RequiresPermission(anyOf={"android.permission.BLUETOOTH", "android.permission.BLUETOOTH_CONNECT"}) public int getBondState();
     method public java.util.UUID getId();
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index cdb57e9..5a47765 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -21,6 +21,15 @@
     property public final int timeoutMillis;
   }
 
+  public final class BluetoothAddress {
+    ctor public BluetoothAddress(String address, int addressType);
+    method public String getAddress();
+    method public int getAddressType();
+    method public void setAddressType(int);
+    property public final String address;
+    property public final int addressType;
+  }
+
   public final class BluetoothDevice {
     method @RequiresPermission(anyOf={"android.permission.BLUETOOTH", "android.permission.BLUETOOTH_CONNECT"}) public int getBondState();
     method public java.util.UUID getId();
diff --git a/bluetooth/bluetooth/build.gradle b/bluetooth/bluetooth/build.gradle
index 0af484d..c455f0b 100644
--- a/bluetooth/bluetooth/build.gradle
+++ b/bluetooth/bluetooth/build.gradle
@@ -36,6 +36,7 @@
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.kotlinCoroutinesTest)
+    androidTestImplementation(libs.kotlinTest)
 }
 
 androidx {
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
new file mode 100644
index 0000000..e2bdae6
--- /dev/null
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.bluetooth
+
+import junit.framework.TestCase.assertEquals
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class BluetoothAddressTest {
+
+    companion object {
+        // TODO(kihongs) Change to actual public address if possible
+        private const val TEST_ADDRESS_PUBLIC = "00:43:A8:23:10:F0"
+        private const val TEST_ADDRESS_RANDOM = "F0:43:A8:23:10:00"
+        private const val TEST_ADDRESS_UNKNOWN = "F0:43:A8:23:10:12"
+    }
+
+    @Test
+    fun constructorWithAddressTypePublic() {
+        val addressType = AddressType.ADDRESS_TYPE_PUBLIC
+
+        val bluetoothAddress = BluetoothAddress(TEST_ADDRESS_PUBLIC, addressType)
+
+        assertEquals(TEST_ADDRESS_PUBLIC, bluetoothAddress.address)
+        assertEquals(addressType, bluetoothAddress.addressType)
+    }
+
+    @Test
+    fun constructorWithAddressTypeRandom() {
+        val addressType = AddressType.ADDRESS_TYPE_RANDOM
+
+        val bluetoothAddress = BluetoothAddress(TEST_ADDRESS_RANDOM, addressType)
+
+        assertEquals(TEST_ADDRESS_RANDOM, bluetoothAddress.address)
+        assertEquals(addressType, bluetoothAddress.addressType)
+    }
+
+    @Test
+    fun constructorWithAddressTypeUnknown() {
+        val addressType = AddressType.ADDRESS_TYPE_UNKNOWN
+
+        val bluetoothAddress = BluetoothAddress(TEST_ADDRESS_UNKNOWN, addressType)
+
+        assertEquals(TEST_ADDRESS_UNKNOWN, bluetoothAddress.address)
+        assertEquals(addressType, bluetoothAddress.addressType)
+    }
+
+    @Test
+    fun constructorWithInvalidAddressType() {
+        val invalidAddressType = -1
+
+        val bluetoothAddress = BluetoothAddress(TEST_ADDRESS_UNKNOWN, invalidAddressType)
+
+        assertEquals(TEST_ADDRESS_UNKNOWN, bluetoothAddress.address)
+        assertEquals(AddressType.ADDRESS_TYPE_UNKNOWN, bluetoothAddress.addressType)
+    }
+
+    @Test
+    fun constructorWithInvalidAddress() {
+        val invalidAddress = "invalidAddress"
+
+        assertFailsWith<IllegalArgumentException> {
+            BluetoothAddress(invalidAddress, AddressType.ADDRESS_TYPE_UNKNOWN)
+        }
+    }
+}
\ No newline at end of file
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
new file mode 100644
index 0000000..b2ae458
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+
+/**
+ * Represents a Bluetooth address for a remote device.
+ *
+ * @property address valid Bluetooth MAC address
+ * @property addressType valid address type
+ *
+ */
+class BluetoothAddress(val address: String, var addressType: Int) {
+    init {
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            throw IllegalArgumentException("$address is not a valid Bluetooth address")
+        }
+
+        if (addressType != AddressType.ADDRESS_TYPE_PUBLIC && addressType !=
+            AddressType.ADDRESS_TYPE_RANDOM) {
+            addressType = AddressType.ADDRESS_TYPE_UNKNOWN
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 03b36dc..753d7bc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -231,9 +231,6 @@
         // Disable until ag/19949626 goes in (b/261918265)
         disable.add("MissingQuantity")
 
-        // Disable new lint check so that we can land incrementally.
-        disable.add("VisibleForTests")
-
         // Provide stricter enforcement for project types intended to run on a device.
         if (extension.type.compilationTarget == CompilationTarget.DEVICE) {
             fatal.add("Assert")
diff --git a/busytown/androidx_multiplatform.sh b/busytown/androidx_multiplatform.sh
deleted file mode 120000
index e2a6ac8..0000000
--- a/busytown/androidx_multiplatform.sh
+++ /dev/null
@@ -1 +0,0 @@
-./androidx_compose_multiplatform.sh
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt
index d0fa9e3..fc3bf34 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/Camera2CameraControlDeviceTest.kt
@@ -48,11 +48,12 @@
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
+import androidx.camera.camera2.pipe.testing.toCameraInfoAdapter
 import androidx.camera.core.CameraControl
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.UseCase
-import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
@@ -105,7 +106,7 @@
             CameraSelector.LENS_FACING_BACK
         ).build()
         camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
-        cameraControl = camera.cameraControl as CameraControlAdapter
+        cameraControl = camera.cameraControl.toCameraControlAdapter()
         camera2CameraControl = cameraControl.camera2cameraControl
         comboListener = camera2CameraControl.requestListener
     }
@@ -438,7 +439,7 @@
                 Context.CAMERA_SERVICE
             ) as CameraManager
         val characteristics = cameraManager.getCameraCharacteristics(
-            (camera.cameraInfo as CameraInfoInternal).cameraId
+            camera.cameraInfo.toCameraInfoAdapter().cameraId
         )
 
         val maxDigitalZoom = characteristics.get(
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
index a072374..a4eb05c 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
@@ -61,6 +61,8 @@
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
+import androidx.camera.camera2.pipe.testing.toCameraInfoAdapter
 import androidx.camera.core.Camera
 import androidx.camera.core.CameraControl
 import androidx.camera.core.CameraSelector
@@ -70,7 +72,6 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceOrientedMeteringPointFactory
 import androidx.camera.core.UseCase
-import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.Quirks
 import androidx.camera.core.impl.SessionConfig
@@ -147,7 +148,7 @@
             CameraSelector.LENS_FACING_BACK
         ).build()
         camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
-        cameraControl = camera.cameraControl as CameraControlAdapter
+        cameraControl = camera.cameraControl.toCameraControlAdapter()
         comboListener = cameraControl.camera2cameraControl.requestListener
 
         characteristics = CameraUtil.getCameraCharacteristics(
@@ -473,7 +474,7 @@
     }
 
     private fun Camera.getMaxSupportedZoomRatio(): Float {
-        return cameraInfo.zoomState.value!!.maxZoomRatio
+        return cameraInfo.toCameraInfoAdapter().zoomState.value!!.maxZoomRatio
     }
 
     private suspend fun verifyRequestOptions() {
@@ -506,7 +507,7 @@
             cameraSelector,
             *useCases,
         )
-        cameraControl = camera.cameraControl as CameraControlAdapter
+        cameraControl = camera.cameraControl.toCameraControlAdapter()
     }
 
     private fun createFakeRecordingUseCase(): FakeUseCase {
@@ -618,7 +619,7 @@
     }
 
     private fun Camera.getCameraQuirks(): Quirks {
-        return (cameraInfo as? CameraInfoInternal)?.cameraQuirks!!
+        return cameraInfo.toCameraInfoAdapter().cameraQuirks
     }
 
     private fun CameraCharacteristics.isAfModeSupported(
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
index 3f62b210..c8d8825a 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
@@ -24,6 +24,7 @@
 import android.view.Surface
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.impl.CameraCaptureCallback
@@ -101,7 +102,7 @@
             }
         }
 
-        cameraControl = camera!!.cameraControl as CameraControlAdapter
+        cameraControl = camera!!.cameraControl.toCameraControlAdapter()
     }
 
     @After
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
index 092a7c6..1c7730d 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
@@ -26,6 +26,7 @@
 import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
@@ -92,7 +93,7 @@
             CameraSelector.LENS_FACING_BACK
         ).build()
         camera = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
-        cameraControl = camera.cameraControl as CameraControlAdapter
+        cameraControl = camera.cameraControl.toCameraControlAdapter()
 
         @OptIn(ExperimentalCamera2Interop::class)
         comboListener = cameraControl.camera2cameraControl.requestListener
@@ -267,6 +268,6 @@
                 }
             },
         )
-        cameraControl = camera.cameraControl as CameraControlAdapter
+        cameraControl = camera.cameraControl.toCameraControlAdapter()
     }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt
index 0366575..a1d918e 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/TapToFocusDeviceTest.kt
@@ -26,6 +26,7 @@
 import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.testing.VerifyResultListener
+import androidx.camera.camera2.pipe.testing.toCameraControlAdapter
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.FocusMeteringAction
 import androidx.camera.core.ImageCapture
@@ -115,7 +116,7 @@
             imageCapture
         )
 
-        cameraControl = camera.cameraControl as CameraControlAdapter
+        cameraControl = camera.cameraControl.toCameraControlAdapter()
 
         @OptIn(ExperimentalCamera2Interop::class)
         comboListener = cameraControl.camera2cameraControl.requestListener
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUtil.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUtil.kt
new file mode 100644
index 0000000..9a7f3fc
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUtil.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.testing
+
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
+import androidx.camera.core.CameraControl
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.impl.CameraControlInternal
+import androidx.camera.core.impl.CameraInfoInternal
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+fun CameraControl.toCameraControlAdapter(): CameraControlAdapter {
+    return ((this as CameraControlInternal).implementation) as CameraControlAdapter
+}
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+fun CameraInfo.toCameraInfoAdapter(): CameraInfoAdapter {
+    return ((this as CameraInfoInternal).implementation) as CameraInfoAdapter
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
index c7f1e59..12acfb6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
@@ -69,6 +69,10 @@
         debug { "$this#close" }
     }
 
+    override fun setActiveResumingMode(enabled: Boolean) {
+        useCaseManager.setActiveResumeMode(enabled)
+    }
+
     override fun release(): ListenableFuture<Void> {
         return threads.scope.launch { useCaseManager.close() }.asListenableFuture()
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
index e05349a1..730067e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
@@ -19,6 +19,7 @@
 package androidx.camera.camera2.pipe.integration.config
 
 import android.media.MediaCodec
+import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraId
@@ -152,17 +153,23 @@
                 // need to explicitly close the capture session.
                 false
             } else {
-                cameraQuirks.quirks.contains(CloseCaptureSessionOnVideoQuirk::class.java) &&
+                if (cameraQuirks.quirks.contains(CloseCaptureSessionOnVideoQuirk::class.java) &&
                     containsVideo
+                ) {
+                    true
+                } else
+                // TODO(b/277675483): From the current test results, older devices (Android
+                //  version <= 8.1.0) seem to have a higher chance of encountering an issue where
+                //  not closing the capture session would lead to CameraDevice.close stalling
+                //  indefinitely. This version check might need to be further fine-turned down the
+                //  line.
+                    Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1
             }
         val combinedFlags = cameraGraphFlags.copy(
             quirkCloseCaptureSessionOnDisconnect = shouldCloseCaptureSessionOnDisconnect,
         )
 
         // Build up a config (using TEMPLATE_PREVIEW by default)
-        // TODO(b/277310425): Turn off CameraGraph.Flags.quirkFinalizeSessionOnCloseBehavior when
-        //  it's not needed. This should be needed only when all use cases are detached (with
-        //  VideoCapture) on devices where Surfaces cannot be released immediately.
         val graph = cameraPipe.create(
             CameraGraph.Config(
                 camera = cameraConfig.cameraId,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index a2457a4..32c7488 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -72,6 +72,8 @@
         priority: Config.OptionPriority = defaultOptionPriority,
     ): Deferred<Unit>
 
+    fun setActiveResumeMode(enabled: Boolean) {}
+
     // Lifecycle
     fun close(): Job
 }
@@ -159,6 +161,10 @@
         optionPriority = priority
     )
 
+    override fun setActiveResumeMode(enabled: Boolean) {
+        useCaseGraphConfig.graph.isForeground = enabled
+    }
+
     private fun UseCaseCameraRequestControl.setSessionConfigAsync(
         sessionConfig: SessionConfig
     ): Deferred<Unit> = setConfigAsync(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 9f39981..42e8d80 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -86,6 +86,9 @@
     @GuardedBy("lock")
     private val activeUseCases = mutableSetOf<UseCase>()
 
+    @GuardedBy("lock")
+    private var activeResumeEnabled = false
+
     private val meteringRepeating by lazy {
         MeteringRepeating.Builder(
             cameraProperties,
@@ -208,6 +211,11 @@
         }
     }
 
+    fun setActiveResumeMode(enabled: Boolean) = synchronized(lock) {
+        activeResumeEnabled = enabled
+        camera?.setActiveResumeMode(enabled)
+    }
+
     suspend fun close() {
         val closingJobs = synchronized(lock) {
             if (attachedUseCases.isNotEmpty()) {
@@ -271,6 +279,7 @@
         for (control in allControls) {
             control.useCaseCamera = camera
         }
+        camera?.setActiveResumeMode(activeResumeEnabled)
 
         refreshRunningUseCases()
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt
index d692dbe..2ca7aeb 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraControl.kt
@@ -27,6 +27,7 @@
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.core.CameraControl
+import androidx.camera.core.impl.CameraControlInternal
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.core.util.Preconditions
 import com.google.common.util.concurrent.ListenableFuture
@@ -186,11 +187,12 @@
          */
         @JvmStatic
         fun from(cameraControl: CameraControl): Camera2CameraControl {
+            var cameraControlImpl = (cameraControl as CameraControlInternal).implementation
             Preconditions.checkArgument(
-                cameraControl is CameraControlAdapter,
+                cameraControlImpl is CameraControlAdapter,
                 "CameraControl doesn't contain Camera2 implementation."
             )
-            return (cameraControl as CameraControlAdapter).camera2cameraControl
+            return (cameraControlImpl as CameraControlAdapter).camera2cameraControl
         }
 
         /**
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
index 2372dd3..3094fc4 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
@@ -23,6 +23,7 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.getSafely
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.core.CameraInfo
+import androidx.camera.core.impl.CameraInfoInternal
 import androidx.core.util.Preconditions
 
 /**
@@ -88,11 +89,12 @@
          */
         @JvmStatic
         fun from(@Suppress("UNUSED_PARAMETER") cameraInfo: CameraInfo): Camera2CameraInfo {
+            var cameraInfoImpl = (cameraInfo as CameraInfoInternal).implementation
             Preconditions.checkArgument(
-                cameraInfo is CameraInfoAdapter,
+                cameraInfoImpl is CameraInfoAdapter,
                 "CameraInfo doesn't contain Camera2 implementation."
             )
-            return (cameraInfo as CameraInfoAdapter).camera2CameraInfo
+            return (cameraInfoImpl as CameraInfoAdapter).camera2CameraInfo
         }
 
         /**
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt
index 6e30f2d..a3bbc1e 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraph.kt
@@ -34,6 +34,7 @@
 
     override val graphState: StateFlow<GraphState>
         get() = throw NotImplementedError("Not used in testing")
+    override var isForeground = false
 
     override suspend fun acquireSession(): CameraGraph.Session {
         return fakeCameraGraphSession
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
index a8b6841..5ce8070 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraControllerSimulator.kt
@@ -49,6 +49,7 @@
 ) : CameraController {
     override val cameraId: CameraId
         get() = graphConfig.camera
+    override var isForeground = false
 
     private val lock = Any()
     private var currentSurfaceMap: Map<StreamId, Surface> = emptyMap()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
index 2271494..fc864da 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraController.kt
@@ -39,6 +39,12 @@
     val cameraId: CameraId
 
     /**
+     * Whether the camera is being used in a foreground setting, and thus should be kept open on a
+     * best-effort basis, for example continuously retrying on a longer timeout.
+     */
+    var isForeground: Boolean
+
+    /**
      * Connect and start the underlying camera. This may be called on the main thread and should not
      * make long blocking calls. This may be called opportunistically (eg, whenever a lifecycle
      * indicates the camera should be in a running state)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index ee57224..50c6a23 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -45,6 +45,13 @@
     val graphState: StateFlow<GraphState>
 
     /**
+     * This is a hint an app can give to a camera graph to indicate whether the camera is being used
+     * in a foreground setting, for example whether the user could see the app itself. This would
+     * inform the underlying implementation to open cameras more actively (e.g., longer timeout).
+     */
+    var isForeground: Boolean
+
+    /**
      * This will cause the [CameraGraph] to start opening the [CameraDevice] and configuring a
      * [CameraCaptureSession]. While the CameraGraph is alive it will attempt to keep the camera
      * open, active, and in a configured running state.
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt
index 97090d9..d1fc183 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt
@@ -114,7 +114,9 @@
     }
 
     internal fun registerSurface(surface: Surface): AutoCloseable {
-        check(surface.isValid) { "Surface $surface isn't valid!" }
+        if (!surface.isValid) {
+            Log.warn { "registerSurface: Surface $surface isn't valid!" }
+        }
         val surfaceToken: SurfaceToken
         var listenersToInvoke: List<SurfaceListener>? = null
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
index 3d4ea8d..73ce419 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraController.kt
@@ -16,6 +16,7 @@
 
 package androidx.camera.camera2.pipe.compat
 
+import android.os.Build
 import android.view.Surface
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
@@ -64,6 +65,15 @@
 
     private val lock = Any()
 
+    override var isForeground: Boolean
+        get() = synchronized(lock) { _isForeground }
+        set(value) = synchronized(lock) {
+            _isForeground = value
+        }
+
+    @GuardedBy("lock")
+    private var _isForeground: Boolean = false
+
     @GuardedBy("lock")
     private var controllerState: ControllerState = ControllerState.STOPPED
 
@@ -88,8 +98,8 @@
         val camera = virtualCameraManager.open(
             config.camera,
             config.flags.allowMultipleActiveCameras,
-            graphListener
-        )
+            graphListener,
+        ) { _ -> isForeground }
 
         check(currentCamera == null)
         check(currentSession == null)
@@ -113,6 +123,7 @@
 
         controllerState = ControllerState.STARTED
         Log.debug { "Started Camera2CameraController" }
+        currentCameraStateJob?.cancel()
         currentCameraStateJob = scope.launch { bindSessionToCamera() }
     }
 
@@ -182,6 +193,9 @@
         currentCamera = null
         currentSession = null
 
+        currentCameraStateJob?.cancel()
+        currentCameraStateJob = null
+
         scope.launch {
             session?.disconnect()
             camera?.disconnect()
@@ -240,6 +254,12 @@
             ) {
                 controllerState = ControllerState.DISCONNECTED
                 Log.debug { "Camera2CameraController is disconnected" }
+                if (Build.VERSION.SDK_INT in (Build.VERSION_CODES.Q..Build.VERSION_CODES.S_V2) &&
+                    _isForeground
+                ) {
+                    Log.debug { "Quirk for multi-resume: Internal tryRestart()" }
+                    tryRestart(CameraStatus.CameraPrioritiesChanged)
+                }
             } else {
                 controllerState = ControllerState.ERROR
                 Log.debug {
@@ -251,7 +271,5 @@
         } else {
             controllerState = ControllerState.STOPPED
         }
-        currentCameraStateJob?.cancel()
-        currentCameraStateJob = null
     }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
index 30f87b1..5045610 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2DeviceCloser.kt
@@ -21,17 +21,15 @@
 import android.hardware.camera2.CameraDevice
 import android.view.Surface
 import androidx.annotation.RequiresApi
-import androidx.camera.camera2.pipe.CameraCloseStallException
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.core.Debug
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.core.Threading
 import androidx.camera.camera2.pipe.core.Threads
 import java.util.concurrent.CountDownLatch
 import javax.inject.Inject
 import javax.inject.Singleton
 import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withTimeoutOrNull
 
 internal interface Camera2DeviceCloser {
     fun closeCamera(
@@ -84,12 +82,8 @@
             }
         }
         Log.debug { "Closing $cameraDevice" }
-        runBlocking {
-            withTimeoutOrNull(2000L) {
-                cameraDevice.closeWithTrace()
-            } ?: {
-                throw CameraCloseStallException("The camera close call failed to return in 2000ms")
-            }
+        Threading.runBlockingWithTimeout(threads.backgroundDispatcher, 2000L) {
+            cameraDevice.closeWithTrace()
         }
         if (camera2Quirks.shouldWaitForCameraDeviceOnClosed(cameraId)) {
             Log.debug { "Waiting for camera device to be completely closed" }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
index f8cfa64..396ace8 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/ExternalRequestProcessor.kt
@@ -53,6 +53,7 @@
 
     override val cameraId: CameraId
         get() = graphConfig.camera
+    override var isForeground = false
 
     override fun start() {
         if (started.compareAndSet(expect = false, update = true)) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpener.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpener.kt
index e5929d6..a5d46ea 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpener.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpener.kt
@@ -28,7 +28,6 @@
 import androidx.camera.camera2.pipe.core.Debug
 import androidx.camera.camera2.pipe.core.DurationNs
 import androidx.camera.camera2.pipe.core.Log
-import androidx.camera.camera2.pipe.core.SystemTimeSource
 import androidx.camera.camera2.pipe.core.Threads
 import androidx.camera.camera2.pipe.core.TimeSource
 import androidx.camera.camera2.pipe.core.TimestampNs
@@ -43,8 +42,22 @@
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeoutOrNull
 
+// TODO(b/246180670): Replace all duration usage in CameraPipe with kotlin.time.Duration
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-private val cameraRetryTimeout = DurationNs(10_000_000_000) // 10 seconds
+private val defaultCameraRetryTimeoutNs = DurationNs(10_000_000_000L) // 10s
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+private val activeResumeCameraRetryTimeoutNs = DurationNs(30L * 60L * 1_000_000_000L) // 30m
+
+private const val defaultCameraRetryDelayMs = 500L
+
+private const val activeResumeCameraRetryDelayBaseMs = defaultCameraRetryDelayMs
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+private val activeResumeCameraRetryThresholds = arrayOf(
+    DurationNs(2L * 60L * 1_000_000_000L), // 2m
+    DurationNs(5L * 60L * 1_000_000_000L), // 5m
+)
 
 internal interface CameraOpener {
     fun openCamera(cameraId: CameraId, stateCallback: StateCallback)
@@ -216,6 +229,7 @@
 ) {
     internal suspend fun openCameraWithRetry(
         cameraId: CameraId,
+        isForegroundObserver: (Unit) -> Boolean = { _ -> true },
     ): OpenCameraResult {
         val requestTimestamp = Timestamps.now(timeSource)
         var attempts = 0
@@ -229,6 +243,7 @@
                     attempts,
                     requestTimestamp,
                 )
+            val elapsed = Timestamps.now(timeSource) - requestTimestamp
             with(result) {
                 if (cameraState != null) {
                     return result
@@ -246,13 +261,14 @@
                     return result
                 }
 
+                val isForeground = isForegroundObserver.invoke(Unit)
                 val willRetry =
                     shouldRetry(
                         errorCode,
                         attempts,
-                        requestTimestamp,
-                        timeSource,
-                        devicePolicyManager.camerasDisabled
+                        elapsed,
+                        devicePolicyManager.camerasDisabled,
+                        isForeground,
                     )
                 // Always notify if the decision is to not retry the camera open, otherwise allow
                 // 1 open call to happen silently without generating an error, and notify about each
@@ -268,12 +284,19 @@
                     }
                     return result
                 }
-            }
 
-            // Listen to availability - if we are notified that the cameraId is available then
-            // retry immediately.
-            if (!cameraAvailabilityMonitor.awaitAvailableCamera(cameraId, timeoutMillis = 500)) {
-                Log.debug { "Timeout expired, retrying camera open for camera $cameraId" }
+                // Listen to availability - if we are notified that the cameraId is available then
+                // retry immediately.
+                if (!cameraAvailabilityMonitor.awaitAvailableCamera(
+                        cameraId,
+                        timeoutMillis = getRetryDelayMs(
+                            elapsed,
+                            shouldActivateActiveResume(isForeground, errorCode)
+                        )
+                    )
+                ) {
+                    Log.debug { "Timeout expired, retrying camera open for camera $cameraId" }
+                }
             }
         }
     }
@@ -282,12 +305,13 @@
         internal fun shouldRetry(
             errorCode: CameraError,
             attempts: Int,
-            firstAttemptTimestampNs: TimestampNs,
-            timeSource: TimeSource = SystemTimeSource(),
-            camerasDisabledByDevicePolicy: Boolean
+            elapsedNs: DurationNs,
+            camerasDisabledByDevicePolicy: Boolean,
+            isForeground: Boolean = false,
         ): Boolean {
-            val elapsed = Timestamps.now(timeSource) - firstAttemptTimestampNs
-            if (elapsed > cameraRetryTimeout) {
+            val shouldActiveResume = shouldActivateActiveResume(isForeground, errorCode)
+            if (shouldActiveResume) Log.debug { "shouldRetry: Active resume mode is activated" }
+            if (elapsedNs > getRetryTimeoutNs(shouldActiveResume)) {
                 return false
             }
             return when (errorCode) {
@@ -350,5 +374,34 @@
                 }
             }
         }
+
+        internal fun shouldActivateActiveResume(
+            isForeground: Boolean,
+            errorCode: CameraError
+        ): Boolean = isForeground &&
+            Build.VERSION.SDK_INT in (Build.VERSION_CODES.Q..Build.VERSION_CODES.S_V2) &&
+            (errorCode == CameraError.ERROR_CAMERA_IN_USE ||
+                errorCode == CameraError.ERROR_CAMERA_LIMIT_EXCEEDED ||
+                errorCode == CameraError.ERROR_CAMERA_DISCONNECTED)
+
+        internal fun getRetryTimeoutNs(activeResumeActivated: Boolean) =
+            if (!activeResumeActivated) {
+                defaultCameraRetryTimeoutNs
+            } else {
+                activeResumeCameraRetryTimeoutNs
+            }
+
+        internal fun getRetryDelayMs(elapsedNs: DurationNs, activeResumeActivated: Boolean): Long {
+            if (!activeResumeActivated) {
+                return defaultCameraRetryDelayMs
+            }
+            return if (elapsedNs < activeResumeCameraRetryThresholds[0]) {
+                activeResumeCameraRetryDelayBaseMs
+            } else if (elapsedNs < activeResumeCameraRetryThresholds[1]) {
+                activeResumeCameraRetryDelayBaseMs * 4L
+            } else {
+                activeResumeCameraRetryDelayBaseMs * 8L
+            }
+        }
     }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt
index 45f13c5..9fa8135 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCamera.kt
@@ -39,9 +39,9 @@
 import androidx.camera.camera2.pipe.internal.CameraErrorListener
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
-import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -174,29 +174,29 @@
             // recently).
             //
             // Relevant bug: b/269619541
-            job =
-                launch(EmptyCoroutineContext) {
-                    state.collect {
-                        synchronized(lock) {
-                            if (!closed) {
-                                if (it is CameraStateOpen) {
-                                    val virtualAndroidCamera = VirtualAndroidCameraDevice(
-                                        it.cameraDevice as AndroidCameraDevice
-                                    )
-                                    // The ordering here is important. We need to set the current
-                                    // VirtualAndroidCameraDevice before emitting it out. Otherwise,
-                                    // the capture session can be started while we still don't have
-                                    // the current VirtualAndroidCameraDevice to disconnect when
-                                    // VirtualCameraState.disconnect() is called in parallel.
-                                    currentVirtualAndroidCamera = virtualAndroidCamera
-                                    emitState(CameraStateOpen(virtualAndroidCamera))
-                                } else {
-                                    emitState(it)
-                                }
-                            }
+            job = launch {
+                state.collect {
+                    synchronized(lock) {
+                        if (closed) {
+                            this.cancel()
+                        }
+                        if (it is CameraStateOpen) {
+                            val virtualAndroidCamera = VirtualAndroidCameraDevice(
+                                it.cameraDevice as AndroidCameraDevice
+                            )
+                            // The ordering here is important. We need to set the current
+                            // VirtualAndroidCameraDevice before emitting it out. Otherwise, the
+                            // capture session can be started while we still don't have the current
+                            // VirtualAndroidCameraDevice to disconnect when
+                            // VirtualCameraState.disconnect() is called in parallel.
+                            currentVirtualAndroidCamera = virtualAndroidCamera
+                            emitState(CameraStateOpen(virtualAndroidCamera))
+                        } else {
+                            emitState(it)
                         }
                     }
                 }
+            }
             this@VirtualCameraState.wakelockToken = wakelockToken
         }
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
index 8194f28..c4906ae 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/VirtualCameraManager.kt
@@ -42,7 +42,8 @@
 internal data class RequestOpen(
     val virtualCamera: VirtualCameraState,
     val share: Boolean = false,
-    val graphListener: GraphListener
+    val graphListener: GraphListener,
+    val isForegroundObserver: (Unit) -> Boolean,
 ) : CameraRequest()
 
 internal data class RequestClose(val activeCamera: VirtualCameraManager.ActiveCamera) :
@@ -77,10 +78,11 @@
     internal fun open(
         cameraId: CameraId,
         share: Boolean = false,
-        graphListener: GraphListener
+        graphListener: GraphListener,
+        isForegroundObserver: (Unit) -> Boolean,
     ): VirtualCamera {
         val result = VirtualCameraState(cameraId, graphListener)
-        offerChecked(RequestOpen(result, share, graphListener))
+        offerChecked(RequestOpen(result, share, graphListener, isForegroundObserver))
         return result
     }
 
@@ -186,7 +188,11 @@
             var realCamera = activeCameras.firstOrNull { it.cameraId == cameraIdToOpen }
             if (realCamera == null) {
                 val openResult =
-                    openCameraWithRetry(cameraIdToOpen, scope = this)
+                    openCameraWithRetry(
+                        cameraIdToOpen,
+                        request.isForegroundObserver,
+                        scope = this
+                    )
                 if (openResult.activeCamera != null) {
                     realCamera = openResult.activeCamera
                     activeCameras.add(realCamera)
@@ -205,6 +211,7 @@
 
     private suspend fun openCameraWithRetry(
         cameraId: CameraId,
+        isForegroundObserver: (Unit) -> Boolean,
         scope: CoroutineScope
     ): OpenVirtualCameraResult {
         // TODO: Figure out how 1-time permissions work, and see if they can be reset without
@@ -212,7 +219,7 @@
         check(permissions.hasCameraPermission) { "Missing camera permissions!" }
 
         Log.debug { "Opening $cameraId with retries..." }
-        val result = retryingCameraStateOpener.openCameraWithRetry(cameraId)
+        val result = retryingCameraStateOpener.openCameraWithRetry(cameraId, isForegroundObserver)
         if (result.cameraState == null) {
             return OpenVirtualCameraResult(lastCameraError = result.errorCode)
         }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Threading.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Threading.kt
new file mode 100644
index 0000000..184b6ac
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Threading.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.core
+
+import androidx.annotation.RequiresApi
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.withTimeoutOrNull
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+internal object Threading {
+    private val globalSupervisorScope =
+        CoroutineScope(CoroutineName("GlobalThreadingScope") + SupervisorJob())
+
+    /**
+     * runBlockingWithTime runs the specified [block] on a timeout of [timeoutMs] using the given
+     * [dispatcher]. The function runs the given block asynchronously on a supervised scope,
+     * allowing it to return after the timeout completes, even if the calling thread is blocked.
+     * Throws [kotlinx.coroutines.TimeoutCancellationException] when the execution of the [block]
+     * times out.
+     */
+    fun <T> runBlockingWithTimeout(
+        dispatcher: CoroutineDispatcher,
+        timeoutMs: Long,
+        block: suspend () -> T
+    ): T? {
+        return runBlocking {
+            val result = runAsyncSupervised(dispatcher, block)
+            withTimeout(timeoutMs) {
+                result.await()
+            }
+        }
+    }
+
+    /**
+     * runBlockingWithTimeOrNull runs the specified [block] on a timeout of [timeoutMs] using the
+     * given [dispatcher]. The function runs the given block asynchronously on a supervised scope,
+     * allowing it to return after the timeout completes, even if the calling thread is blocked.
+     * Returns null when the execution of the [block] times out.
+     */
+    fun <T> runBlockingWithTimeoutOrNull(
+        dispatcher: CoroutineDispatcher,
+        timeoutMs: Long,
+        block: suspend () -> T
+    ): T? {
+        return runBlocking {
+            val result = runAsyncSupervised(dispatcher, block)
+            withTimeoutOrNull(timeoutMs) {
+                result.await()
+            }
+        }
+    }
+
+    private fun <T> runAsyncSupervised(
+        dispatcher: CoroutineDispatcher,
+        block: suspend () -> T
+    ): Deferred<T> {
+        return globalSupervisorScope.async(dispatcher) {
+            block()
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
index 586f399..3922788 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphImpl.kt
@@ -104,6 +104,14 @@
     override val graphState: StateFlow<GraphState>
         get() = graphProcessor.graphState
 
+    private var _isForeground = false
+    override var isForeground: Boolean
+        get() = _isForeground
+        set(value) {
+            _isForeground = value
+            cameraController.isForeground = value
+        }
+
     override fun start() {
         Debug.traceStart { "$this#start" }
         Log.info { "Starting $this" }
@@ -138,8 +146,8 @@
 
     override fun setSurface(stream: StreamId, surface: Surface?) {
         Debug.traceStart { "$stream#setSurface" }
-        check(surface == null || surface.isValid) {
-            "Failed to set $surface to $stream: The surface was not valid."
+        if (surface != null && !surface.isValid) {
+            Log.warn { "$this#setSurface: $surface is invalid" }
         }
         surfaceGraph[stream] = surface
         Debug.traceStop()
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt
index 81d3845..898211b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphRequestProcessor.kt
@@ -106,7 +106,7 @@
     }
 
     internal fun close() {
-        Log.warn { "Closing $this" }
+        Log.debug { "Closing $this" }
         if (closed.compareAndSet(expect = false, update = true)) {
             captureSequenceProcessor.close()
         }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt
index a714105..c6f20ef 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/RetryingCameraStateOpenerTest.kt
@@ -24,13 +24,13 @@
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_CAMERA_DISCONNECTED
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_CAMERA_IN_USE
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_CAMERA_LIMIT_EXCEEDED
+import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_CAMERA_SERVICE
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_DO_NOT_DISTURB_ENABLED
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_ILLEGAL_ARGUMENT_EXCEPTION
 import androidx.camera.camera2.pipe.CameraError.Companion.ERROR_SECURITY_EXCEPTION
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.core.DurationNs
-import androidx.camera.camera2.pipe.core.TimestampNs
 import androidx.camera.camera2.pipe.core.Timestamps
 import androidx.camera.camera2.pipe.internal.CameraErrorListener
 import androidx.camera.camera2.pipe.testing.FakeCamera2DeviceCloser
@@ -128,75 +128,63 @@
 
     @Test
     fun testShouldRetryReturnsTrueWithinTimeout() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_IN_USE,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetryReturnsFalseWhenTimeoutExpires() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(30_000_000_000L) // 30 seconds
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_IN_USE,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(30_000_000_000L), // 30 seconds
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isFalse()
+        ).isFalse()
     }
 
     @Test
     fun testShouldRetryShouldFailUndetermined() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 CameraError.ERROR_UNDETERMINED,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isFalse()
+        ).isFalse()
     }
 
     @Test
     fun testShouldRetryCameraInUse() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_IN_USE,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // The second retry attempt should fail if SDK version < S, and succeed otherwise.
         val secondRetry =
             RetryingCameraStateOpener.shouldRetry(
-                ERROR_CAMERA_IN_USE, 2, firstAttemptTimestamp, fakeTimeSource, false
+                ERROR_CAMERA_IN_USE,
+                2,
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             assertThat(secondRetry).isFalse()
@@ -207,251 +195,343 @@
 
     @Test
     fun testShouldRetryCameraLimitExceeded() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_LIMIT_EXCEEDED,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should succeed as well.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_LIMIT_EXCEEDED,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetryOnceCameraDisabledWhenDpcCameraDisabled() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_DISABLED,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                camerasDisabledByDevicePolicy = true
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = true,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should fail if camera is disabled.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_DISABLED,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                camerasDisabledByDevicePolicy = true
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = true,
+                isForeground = false,
             )
-        )
-            .isFalse()
+        ).isFalse()
     }
 
     @Test
     fun testShouldRetryRepeatedlyCameraDisabledWhenDpcCameraEnabled() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_DISABLED,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                camerasDisabledByDevicePolicy = false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should success if camera is not disabled.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_DISABLED,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                camerasDisabledByDevicePolicy = false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetryCameraDevice() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 CameraError.ERROR_CAMERA_DEVICE,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should succeed as well.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 CameraError.ERROR_CAMERA_DEVICE,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetryCameraService() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
-                CameraError.ERROR_CAMERA_SERVICE,
+                ERROR_CAMERA_SERVICE,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should succeed as well.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
-                CameraError.ERROR_CAMERA_SERVICE,
+                ERROR_CAMERA_SERVICE,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetryCameraDisconnected() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_DISCONNECTED,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should succeed as well.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_CAMERA_DISCONNECTED,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetryIllegalArgumentException() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_ILLEGAL_ARGUMENT_EXCEPTION,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should succeed as well.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_ILLEGAL_ARGUMENT_EXCEPTION,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
     }
 
     @Test
     fun testShouldRetrySecurityException() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_SECURITY_EXCEPTION,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isTrue()
+        ).isTrue()
 
         // Second attempt should fail.
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_SECURITY_EXCEPTION,
                 2,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_001L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
-        )
-            .isFalse()
+        ).isFalse()
     }
 
     @Test
     fun testShouldNotRetryDoNotDisturbModeEnabled() {
-        val firstAttemptTimestamp = TimestampNs(0L)
-        fakeTimeSource.currentTimestamp = TimestampNs(1_000_000_000L) // 1 second
-
         assertThat(
             RetryingCameraStateOpener.shouldRetry(
                 ERROR_DO_NOT_DISTURB_ENABLED,
                 1,
-                firstAttemptTimestamp,
-                fakeTimeSource,
-                false
+                DurationNs(1_000_000_000L), // 1 second
+                camerasDisabledByDevicePolicy = false,
+                isForeground = false,
             )
+        ).isFalse()
+    }
+
+    @Test
+    fun testShouldRetryCameraInUseOnLongerTimeoutWhenActiveResume() {
+        if (Build.VERSION.SDK_INT !in (Build.VERSION_CODES.Q..Build.VERSION_CODES.S_V2)) {
+            // We won't activate active resume mode when the API level is not in [Q, S_V2].
+            return
+        }
+        assertThat(
+            RetryingCameraStateOpener.shouldRetry(
+                ERROR_CAMERA_IN_USE,
+                1,
+                DurationNs(1_000_000_000L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = true,
+            )
+        ).isTrue()
+
+        val secondRetry = RetryingCameraStateOpener.shouldRetry(
+            ERROR_CAMERA_IN_USE,
+            2,
+            DurationNs(30_000_000_000L), // 30s
+            camerasDisabledByDevicePolicy = false,
+            isForeground = true,
         )
-            .isFalse()
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // Multi-window available
+            assertThat(secondRetry).isFalse()
+        } else {
+            assertThat(secondRetry).isTrue()
+        }
+    }
+
+    @Test
+    fun testShouldRetryCameraLimitExceededOnLongerTimeoutWhenActiveResume() {
+        if (Build.VERSION.SDK_INT !in (Build.VERSION_CODES.Q..Build.VERSION_CODES.S_V2)) {
+            // We won't activate active resume mode when the API level is not in [Q, S_V2].
+            return
+        }
+        assertThat(
+            RetryingCameraStateOpener.shouldRetry(
+                ERROR_CAMERA_LIMIT_EXCEEDED,
+                1,
+                DurationNs(1_000_000_000L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = true,
+            )
+        ).isTrue()
+
+        assertThat(
+            RetryingCameraStateOpener.shouldRetry(
+                ERROR_CAMERA_LIMIT_EXCEEDED,
+                2,
+                DurationNs(30_000_000_000L), // 30s
+                camerasDisabledByDevicePolicy = false,
+                isForeground = true,
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testShouldRetryCameraDisconnectedOnLongerTimeoutWhenActiveResume() {
+        if (Build.VERSION.SDK_INT !in (Build.VERSION_CODES.Q..Build.VERSION_CODES.S_V2)) {
+            // We won't activate active resume mode when the API level is not in [Q, S_V2].
+            return
+        }
+        assertThat(
+            RetryingCameraStateOpener.shouldRetry(
+                ERROR_CAMERA_DISCONNECTED,
+                1,
+                DurationNs(1_000_000_000L),
+                camerasDisabledByDevicePolicy = false,
+                isForeground = true,
+            )
+        ).isTrue()
+
+        assertThat(
+            RetryingCameraStateOpener.shouldRetry(
+                ERROR_CAMERA_DISCONNECTED,
+                2,
+                DurationNs(30_000_000_000L), // 30s
+                camerasDisabledByDevicePolicy = false,
+                isForeground = true,
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testShouldActivateActiveResume() {
+        assertThat(
+            RetryingCameraStateOpener.shouldActivateActiveResume(
+                isForeground = false,
+                errorCode = ERROR_CAMERA_IN_USE,
+            )
+        ).isFalse()
+        if (Build.VERSION.SDK_INT in (Build.VERSION_CODES.Q..Build.VERSION_CODES.S_V2)) {
+            // Regardless of what error it is, we should only activate active resume mode when the
+            // API level is [Q, S_V2], where multi-resume is supported and camera access priority
+            // changes aren't properly notified.
+            assertThat(
+                RetryingCameraStateOpener.shouldActivateActiveResume(
+                    isForeground = true,
+                    errorCode = ERROR_CAMERA_SERVICE,
+                )
+            ).isFalse()
+            assertThat(
+                RetryingCameraStateOpener.shouldActivateActiveResume(
+                    isForeground = true,
+                    errorCode = ERROR_CAMERA_IN_USE,
+                )
+            ).isTrue()
+            assertThat(
+                RetryingCameraStateOpener.shouldActivateActiveResume(
+                    isForeground = true,
+                    errorCode = ERROR_CAMERA_LIMIT_EXCEEDED,
+                )
+            ).isTrue()
+            assertThat(
+                RetryingCameraStateOpener.shouldActivateActiveResume(
+                    isForeground = true,
+                    errorCode = ERROR_CAMERA_DISCONNECTED,
+                )
+            ).isTrue()
+        } else {
+            assertThat(
+                RetryingCameraStateOpener.shouldActivateActiveResume(
+                    isForeground = true,
+                    errorCode = ERROR_CAMERA_SERVICE,
+                )
+            ).isFalse()
+            assertThat(
+                RetryingCameraStateOpener.shouldActivateActiveResume(
+                    isForeground = true,
+                    errorCode = ERROR_CAMERA_IN_USE,
+                )
+            ).isFalse()
+        }
     }
 
     @Test
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/core/ThreadingTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/core/ThreadingTest.kt
new file mode 100644
index 0000000..3890397
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/core/ThreadingTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.core
+
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class ThreadingTest {
+    @Test
+    fun runBlockingWithTimeoutThrowsOnTimeout() = runTest {
+        val latch = CountDownLatch(1)
+        assertThrows<TimeoutCancellationException> {
+            Threading.runBlockingWithTimeout(Dispatchers.IO, 500L) {
+                // Simulate a long call that should time out.
+                latch.await(10, TimeUnit.SECONDS)
+            }
+        }
+    }
+
+    @Test
+    fun runBlockingWithTimeoutOrNullReturnsNullOnTimeout() = runTest {
+        val latch = CountDownLatch(1)
+        val result = Threading.runBlockingWithTimeoutOrNull(Dispatchers.IO, 500L) {
+            // Simulate a long call that should time out.
+            latch.await(10, TimeUnit.SECONDS)
+        }
+        assertThat(result).isNull()
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
index c8a6aa8..863ef6a 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraController.kt
@@ -28,6 +28,7 @@
     var surfaceMap: Map<StreamId, Surface>? = null
     override val cameraId: CameraId
         get() = CameraId.fromCamera2Id("0")
+    override var isForeground = true
 
     override fun start() {
         started = true
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
index 2f3e542..0a53825 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraControlImplDeviceTest.java
@@ -59,6 +59,7 @@
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
 import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
 import androidx.camera.camera2.internal.compat.workaround.AutoFlashAEModeDisabler;
+import androidx.camera.camera2.internal.util.TestUtil;
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraXConfig;
@@ -431,7 +432,7 @@
                 imageCapture);
 
         Camera2CameraControlImpl camera2CameraControlImpl =
-                (Camera2CameraControlImpl) mCamera.getCameraControl();
+                TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
 
         CameraCaptureCallback captureCallback = mock(CameraCaptureCallback.class);
         CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
@@ -458,7 +459,7 @@
                 ApplicationProvider.getApplicationContext(), CameraSelector.DEFAULT_BACK_CAMERA,
                 imageAnalysis);
 
-        return (Camera2CameraControlImpl) mCamera.getCameraControl();
+        return TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
     }
 
     private <T> void assertArraySize(T[] array, int expectedSize) {
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
index 8143d94..3e6a382 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/TorchControlDeviceTest.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 
 import androidx.camera.camera2.Camera2Config;
+import androidx.camera.camera2.internal.util.TestUtil;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.CameraXConfig;
 import androidx.camera.core.ImageAnalysis;
@@ -82,8 +83,8 @@
         // Make ImageAnalysis active.
         imageAnalysis.setAnalyzer(CameraXExecutors.mainThreadExecutor(), ImageProxy::close);
         mCamera = CameraUtil.createCameraAndAttachUseCase(context, cameraSelector, imageAnalysis);
-        Camera2CameraControlImpl cameraControl = (Camera2CameraControlImpl)
-                mCamera.getCameraControl();
+        Camera2CameraControlImpl cameraControl =
+                TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
 
         mTorchControl = cameraControl.getTorchControl();
     }
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/TestUtil.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/TestUtil.java
new file mode 100644
index 0000000..cd4f5ef
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/TestUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal.util;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.Camera2CameraControlImpl;
+import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
+import androidx.camera.core.CameraControl;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.impl.CameraControlInternal;
+import androidx.camera.core.impl.CameraInfoInternal;
+
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class TestUtil {
+    public static Camera2CameraControlImpl getCamera2CameraControlImpl(
+            CameraControl cameraControl) {
+        if (cameraControl instanceof CameraControlInternal) {
+            CameraControlInternal impl =
+                    ((CameraControlInternal) cameraControl).getImplementation();
+            return (Camera2CameraControlImpl) impl;
+        }
+        throw new IllegalArgumentException(
+                "Can't get Camera2CameraControlImpl from the CameraControl");
+    }
+
+    public static Camera2CameraInfoImpl getCamera2CameraInfoImpl(CameraInfo cameraInfo) {
+        if (cameraInfo instanceof CameraInfoInternal) {
+            CameraInfoInternal impl = ((CameraInfoInternal) cameraInfo).getImplementation();
+            return (Camera2CameraInfoImpl) impl;
+        }
+        throw new IllegalArgumentException(
+                "Can't get Camera2CameraInfoImpl from the CameraInfo");
+    }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java
index 7977243..2566caf 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/interop/Camera2CameraControlDeviceTest.java
@@ -39,9 +39,9 @@
 import androidx.annotation.OptIn;
 import androidx.camera.camera2.Camera2Config;
 import androidx.camera.camera2.internal.Camera2CameraControlImpl;
+import androidx.camera.camera2.internal.util.TestUtil;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ImageAnalysis;
-import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.testing.CameraUtil;
@@ -94,7 +94,8 @@
         mCameraSelector = new CameraSelector.Builder().requireLensFacing(
                 CameraSelector.LENS_FACING_BACK).build();
         mCamera = CameraUtil.createCameraUseCaseAdapter(mContext, mCameraSelector);
-        mCamera2CameraControlImpl = (Camera2CameraControlImpl) mCamera.getCameraControl();
+        mCamera2CameraControlImpl =
+                TestUtil.getCamera2CameraControlImpl(mCamera.getCameraControl());
         mCamera2CameraControl = mCamera2CameraControlImpl.getCamera2CameraControl();
         mMockCaptureCallback = mock(CameraCaptureSession.CaptureCallback.class);
     }
@@ -267,7 +268,7 @@
 
     private Rect getZoom2XCropRegion() throws Exception {
         AtomicReference<String> cameraIdRef = new AtomicReference<>();
-        String cameraId = ((CameraInfoInternal) mCamera.getCameraInfo()).getCameraId();
+        String cameraId = TestUtil.getCamera2CameraInfoImpl(mCamera.getCameraInfo()).getCameraId();
         cameraIdRef.set(cameraId);
 
         CameraManager cameraManager =
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
index 6e9ed1d..860400e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
@@ -20,12 +20,14 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.Logger;
 
+import java.util.Collections;
 import java.util.Set;
 
 @RequiresApi(28)
 class CameraCharacteristicsApi28Impl extends CameraCharacteristicsBaseImpl{
-
+    private static final String TAG = "CameraCharacteristicsImpl";
     CameraCharacteristicsApi28Impl(@NonNull CameraCharacteristics cameraCharacteristics) {
         super(cameraCharacteristics);
     }
@@ -33,6 +35,14 @@
     @NonNull
     @Override
     public Set<String> getPhysicalCameraIds() {
-        return mCameraCharacteristics.getPhysicalCameraIds();
+        try {
+            return mCameraCharacteristics.getPhysicalCameraIds();
+        } catch (Exception e) {
+            // getPhysicalCameraIds could cause crash in Robolectric and there is no known
+            // workaround
+            Logger.e(TAG,
+                    "CameraCharacteristics.getPhysicalCameraIds throws an exception.", e);
+            return Collections.emptySet();
+        }
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java
index 3591ef3..0b8f4b9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraControl.java
@@ -25,6 +25,7 @@
 import androidx.camera.camera2.internal.Camera2CameraControlImpl;
 import androidx.camera.camera2.internal.annotation.CameraExecutor;
 import androidx.camera.core.CameraControl;
+import androidx.camera.core.impl.CameraControlInternal;
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.TagBundle;
 import androidx.camera.core.impl.annotation.ExecutedBy;
@@ -125,9 +126,11 @@
      */
     @NonNull
     public static Camera2CameraControl from(@NonNull CameraControl cameraControl) {
-        Preconditions.checkArgument(cameraControl instanceof Camera2CameraControlImpl,
+        CameraControlInternal cameraControlImpl =
+                ((CameraControlInternal) cameraControl).getImplementation();
+        Preconditions.checkArgument(cameraControlImpl instanceof Camera2CameraControlImpl,
                 "CameraControl doesn't contain Camera2 implementation.");
-        return ((Camera2CameraControlImpl) cameraControl).getCamera2CameraControl();
+        return ((Camera2CameraControlImpl) cameraControlImpl).getCamera2CameraControl();
     }
 
     /**
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
index 5e18f87..33522d2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
@@ -25,6 +25,7 @@
 import androidx.annotation.RestrictTo.Scope;
 import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
 import androidx.camera.core.CameraInfo;
+import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.core.util.Preconditions;
 
 import java.util.Map;
@@ -58,9 +59,11 @@
      */
     @NonNull
     public static Camera2CameraInfo from(@NonNull CameraInfo cameraInfo) {
-        Preconditions.checkArgument(cameraInfo instanceof Camera2CameraInfoImpl,
+        CameraInfoInternal cameraInfoImpl =
+                ((CameraInfoInternal) cameraInfo).getImplementation();
+        Preconditions.checkArgument(cameraInfoImpl instanceof Camera2CameraInfoImpl,
                 "CameraInfo doesn't contain Camera2 implementation.");
-        return ((Camera2CameraInfoImpl) cameraInfo).getCamera2CameraInfo();
+        return ((Camera2CameraInfoImpl) cameraInfoImpl).getCamera2CameraInfo();
     }
 
     /**
@@ -119,9 +122,10 @@
     @NonNull
     public static CameraCharacteristics extractCameraCharacteristics(
             @NonNull CameraInfo cameraInfo) {
-        Preconditions.checkState(cameraInfo instanceof Camera2CameraInfoImpl, "CameraInfo does "
-                + "not contain any Camera2 information.");
-        Camera2CameraInfoImpl impl = (Camera2CameraInfoImpl) cameraInfo;
+        CameraInfoInternal cameraInfoImpl = ((CameraInfoInternal) cameraInfo).getImplementation();
+        Preconditions.checkState(cameraInfoImpl instanceof Camera2CameraInfoImpl,
+                "CameraInfo does not contain any Camera2 information.");
+        Camera2CameraInfoImpl impl = (Camera2CameraInfoImpl) cameraInfoImpl;
         return impl.getCameraCharacteristicsCompat().toCameraCharacteristics();
     }
 
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
index 2276320..8ad3050 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
@@ -83,6 +83,7 @@
         Camera2CameraInfo camera2CameraInfo = mock(Camera2CameraInfo.class);
         Camera2CameraInfoImpl cameraInfoImpl = mock(Camera2CameraInfoImpl.class);
         when(cameraInfoImpl.getCamera2CameraInfo()).thenAnswer(ignored -> camera2CameraInfo);
+        when(cameraInfoImpl.getImplementation()).thenAnswer(ignored -> cameraInfoImpl);
         Camera2CameraInfo resultCamera2CameraInfo = Camera2CameraInfo.from(cameraInfoImpl);
 
         assertThat(resultCamera2CameraInfo).isEqualTo(camera2CameraInfo);
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index d9c46f56..2b46a21 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -386,6 +386,7 @@
   @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
     method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
     method public int getTargetRotation();
     method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
     method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
@@ -397,6 +398,7 @@
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
     method public androidx.camera.core.Preview.Builder setTargetName(String);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
     method public androidx.camera.core.Preview.Builder setTargetRotation(int);
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index ffa924f..d7ab729 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -403,6 +403,7 @@
   @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
     method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
     method public int getTargetRotation();
     method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
     method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
@@ -414,6 +415,7 @@
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
     method public androidx.camera.core.Preview.Builder setTargetName(String);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
     method public androidx.camera.core.Preview.Builder setTargetRotation(int);
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index d9c46f56..2b46a21 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -386,6 +386,7 @@
   @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
     method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
     method public int getTargetRotation();
     method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
     method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
@@ -397,6 +398,7 @@
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
     method public androidx.camera.core.Preview.Builder setTargetName(String);
     method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
     method public androidx.camera.core.Preview.Builder setTargetRotation(int);
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index 56a6ebb..c789d6d 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -47,6 +47,8 @@
     testImplementation(libs.truth)
     testImplementation(libs.robolectric)
     testImplementation(libs.mockitoCore4)
+    testImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
+    testImplementation(project(":internal-testutils-truth"))
     testImplementation(project(":camera:camera-testing"), {
         exclude group: "androidx.camera", module: "camera-core"
     })
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
index a385c37..32462ac 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
@@ -42,6 +42,7 @@
 import androidx.camera.core.concurrent.CameraCoordinator;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.CameraCaptureMetaData;
+import androidx.camera.core.impl.CameraControlInternal;
 import androidx.camera.core.impl.CaptureConfig;
 import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.StreamSpec;
@@ -142,7 +143,7 @@
         ImageCapture.OnImageCapturedCallback callback = mock(
                 ImageCapture.OnImageCapturedCallback.class);
         FakeCameraControl fakeCameraControl =
-                ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+                getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
 
         fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
             // Notify the cancel after the capture request has been successfully submitted
@@ -173,7 +174,7 @@
         ImageCapture.OnImageCapturedCallback callback = mock(
                 ImageCapture.OnImageCapturedCallback.class);
         FakeCameraControl fakeCameraControl =
-                ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+                getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
         fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
             // Notify the failure after the capture request has been successfully submitted
             fakeCameraControl.notifyAllRequestsOnCaptureFailed();
@@ -309,6 +310,11 @@
         assertThat(hasJpegQuality(captureConfigs, jpegQuality)).isTrue();
     }
 
+    private FakeCameraControl getCameraControlImplementation(CameraControl cameraControl) {
+        CameraControlInternal impl = ((CameraControlInternal) cameraControl).getImplementation();
+        return (FakeCameraControl) impl;
+    }
+
     @NonNull
     private List<CaptureConfig> captureImage(@NonNull ImageCapture imageCapture,
             @NonNull Class<?> callbackClass) {
@@ -342,7 +348,7 @@
         }
 
         FakeCameraControl fakeCameraControl =
-                ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+                getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
         FakeCameraControl.OnNewCaptureRequestListener mockCaptureRequestListener =
                 mock(FakeCameraControl.OnNewCaptureRequestListener.class);
         fakeCameraControl.setOnNewCaptureRequestListener(mockCaptureRequestListener);
@@ -510,7 +516,7 @@
         ImageCapture.OnImageCapturedCallback callback = mock(
                 ImageCapture.OnImageCapturedCallback.class);
         FakeCameraControl fakeCameraControl =
-                ((FakeCameraControl) mCameraUseCaseAdapter.getCameraControl());
+                getCameraControlImplementation(mCameraUseCaseAdapter.getCameraControl());
         CountDownLatch latch = new CountDownLatch(1);
         fakeCameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
             latch.countDown();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
index 30f3edb..2faa545 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/FocusMeteringAction.java
@@ -186,6 +186,17 @@
         }
 
         /**
+         * Create a Builder from a {@link FocusMeteringAction}.
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public Builder(@NonNull FocusMeteringAction focusMeteringAction) {
+            mMeteringPointsAf.addAll(focusMeteringAction.getMeteringPointsAf());
+            mMeteringPointsAe.addAll(focusMeteringAction.getMeteringPointsAe());
+            mMeteringPointsAwb.addAll(focusMeteringAction.getMeteringPointsAwb());
+            mAutoCancelDurationInMillis = focusMeteringAction.getAutoCancelDurationInMillis();
+        }
+
+        /**
          * Adds another {@link MeteringPoint} with default metering mode {@link #FLAG_AF} |
          * {@link #FLAG_AE} | {@link #FLAG_AWB}.
          *
@@ -271,6 +282,27 @@
         }
 
         /**
+         *
+         * Remove all points of the given meteringMode.
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @NonNull
+        public Builder removePoints(@MeteringMode int meteringMode) {
+            if ((meteringMode & FLAG_AF) != 0) {
+                mMeteringPointsAf.clear();
+            }
+
+            if ((meteringMode & FLAG_AE) != 0) {
+                mMeteringPointsAe.clear();
+            }
+
+            if ((meteringMode & FLAG_AWB) != 0) {
+                mMeteringPointsAwb.clear();
+            }
+            return this;
+        }
+
+        /**
          * Builds the {@link FocusMeteringAction} instance.
          */
         @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index 30d1109..02d7366 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -40,6 +40,7 @@
 import static androidx.camera.core.impl.PreviewConfig.OPTION_USE_CASE_EVENT_CALLBACK;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAMERA_SELECTOR;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_HIGH_RESOLUTION_DISABLED;
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_TARGET_FRAME_RATE;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_ZSL_DISABLED;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.core.util.Preconditions.checkNotNull;
@@ -54,6 +55,7 @@
 import android.media.ImageReader;
 import android.media.MediaCodec;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Display;
 import android.view.Surface;
@@ -155,7 +157,6 @@
 
     /**
      * Provides a static configuration with implementation-agnostic options.
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public static final Defaults DEFAULT_CONFIG = new Defaults();
@@ -574,7 +575,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @Override
@@ -595,7 +595,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
@@ -610,7 +609,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -621,7 +619,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @Override
@@ -631,7 +628,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @Override
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -645,7 +641,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @Override
     @RestrictTo(Scope.LIBRARY)
@@ -655,6 +650,7 @@
     }
 
     /**
+     *
      */
     @VisibleForTesting
     @NonNull
@@ -676,6 +672,24 @@
     }
 
     /**
+     * Returns the target frame rate range, in frames per second, for the associated Preview use
+     * case.
+     * <p>The target frame rate can be set prior to constructing a Preview using
+     * {@link Preview.Builder#setTargetFrameRate(Range)}.
+     * If not set, the target frame rate defaults to the value of
+     * {@link StreamSpec#FRAME_RATE_RANGE_UNSPECIFIED}.
+     *
+     * <p>This is just the frame rate range requested by the user, and may not necessarily be
+     * equal to the range the camera is actually operating at.
+     *
+     *  @return the target frame rate range of this Preview.
+     */
+    @NonNull
+    public Range<Integer> getTargetFrameRate() {
+        return getTargetFrameRateInternal();
+    }
+
+    /**
      * A interface implemented by the application to provide a {@link Surface} for {@link Preview}.
      *
      * <p> This interface is implemented by the application to provide a {@link Surface}. This
@@ -748,7 +762,6 @@
      *
      * <p>These values may be overridden by the implementation. They only provide a minimum set of
      * defaults that are implementation independent.
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public static final class Defaults implements ConfigProvider<PreviewConfig> {
@@ -811,7 +824,6 @@
 
         /**
          * Generates a Builder from another Config object
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
@@ -833,7 +845,6 @@
 
         /**
          * {@inheritDoc}
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @Override
@@ -987,7 +998,6 @@
 
         /**
          * setMirrorMode is not supported on Preview.
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
@@ -1135,6 +1145,30 @@
             return this;
         }
 
+        /**
+         * Sets the target frame rate range in frames per second for the associated Preview use
+         * case.
+         *
+         * <p>
+         * Device will try to get as close as possible to the target frame rate. This may affect
+         * the selected resolutions of the surfaces, resulting in better frame rates at the
+         * potential reduction of resolution.
+         *
+         * <p>
+         * Achieving target frame rate is dependent on device capabilities, as well as other
+         * concurrently attached use cases and their target frame rates.
+         * Because of this, the frame rate that is ultimately selected is not guaranteed to be a
+         * perfect match to the requested target.
+         *
+         * @param targetFrameRate a desired frame rate range.
+         * @return the current Builder.
+         */
+        @NonNull
+        public Builder setTargetFrameRate(@NonNull Range<Integer> targetFrameRate) {
+            getMutableConfig().insertOption(OPTION_TARGET_FRAME_RATE, targetFrameRate);
+            return this;
+        }
+
         // Implementations of UseCaseConfig.Builder default methods
 
         @RestrictTo(Scope.LIBRARY_GROUP)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
index 33688e9..c89bb2f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraControlInternal.java
@@ -39,9 +39,13 @@
 /**
  * The CameraControlInternal Interface.
  *
+ *
  * <p>CameraControlInternal is used for global camera operations like zoom, focus, flash and
- * triggering
- * AF/AE.
+ * triggering AF/AE as well as some internal operations.
+ *
+ * <p>{@link #getImplementation()} returns a {@link CameraControlInternal} instance
+ * that contains the actual implementation and can be cast to an implementation specific class.
+ * If the instance itself is the implementation instance, then it should return <code>this</code>.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface CameraControlInternal extends CameraControl {
@@ -136,6 +140,16 @@
     @NonNull
     Config getInteropConfig();
 
+    /**
+     * Gets the underlying implementation instance which could be cast into an implementation
+     * specific class for further use in implementation module. Returns <code>this</code> if this
+     * instance is the implementation instance.
+     */
+    @NonNull
+    default CameraControlInternal getImplementation() {
+        return this;
+    }
+
     CameraControlInternal DEFAULT_EMPTY_INSTANCE = new CameraControlInternal() {
         @FlashMode
         @Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
index 791efe0..e6a1e9b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
@@ -36,6 +36,10 @@
  * An interface for retrieving camera information.
  *
  * <p>Contains methods for retrieving characteristics for a specific camera.
+ *
+ * <p>{@link #getImplementation()} returns a {@link CameraInfoInternal} instance
+ * that contains the actual implementation and can be cast to an implementation specific class.
+ * If the instance itself is the implementation instance, then it should return <code>this</code>.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface CameraInfoInternal extends CameraInfo {
@@ -101,6 +105,17 @@
     @NonNull
     Set<DynamicRange> getSupportedDynamicRanges();
 
+    /**
+     * Gets the underlying implementation instance which could be cast into an implementation
+     * specific class for further use in implementation module. Returns <code>this</code> if this
+     * instance is the implementation instance.
+     */
+    @NonNull
+    default CameraInfoInternal getImplementation() {
+        return this;
+    }
+
+
     /** {@inheritDoc} */
     @NonNull
     @Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
new file mode 100644
index 0000000..ea73135
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraControl.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl;
+
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringResult;
+import androidx.camera.core.ImageCapture;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * A {@link CameraControlInternal} that forwards all the calls into the given
+ * {@link CameraControlInternal}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ForwardingCameraControl implements CameraControlInternal {
+    private final CameraControlInternal mCameraControlInternal;
+
+    /**
+     * Create an instance that will forward all calls to the supplied {@link CameraControlInternal}
+     * instance.
+     */
+    public ForwardingCameraControl(@NonNull CameraControlInternal cameraControlInternal) {
+        mCameraControlInternal = cameraControlInternal;
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> enableTorch(boolean torch) {
+        return mCameraControlInternal.enableTorch(torch);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
+            @NonNull FocusMeteringAction action) {
+        return mCameraControlInternal.startFocusAndMetering(action);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> cancelFocusAndMetering() {
+        return mCameraControlInternal.cancelFocusAndMetering();
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> setZoomRatio(float ratio) {
+        return mCameraControlInternal.setZoomRatio(ratio);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> setLinearZoom(float linearZoom) {
+        return mCameraControlInternal.setLinearZoom(linearZoom);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
+        return mCameraControlInternal.setExposureCompensationIndex(value);
+    }
+
+    @Override
+    @ImageCapture.FlashMode
+    public int getFlashMode() {
+        return mCameraControlInternal.getFlashMode();
+    }
+
+    @Override
+    public void setFlashMode(@ImageCapture.FlashMode int flashMode) {
+        mCameraControlInternal.setFlashMode(flashMode);
+    }
+
+    @Override
+    public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
+        mCameraControlInternal.addZslConfig(sessionConfigBuilder);
+    }
+
+    @Override
+    public void setZslDisabledByUserCaseConfig(boolean disabled) {
+        mCameraControlInternal.setZslDisabledByUserCaseConfig(disabled);
+    }
+
+    @Override
+    public boolean isZslDisabledByByUserCaseConfig() {
+        return mCameraControlInternal.isZslDisabledByByUserCaseConfig();
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<List<Void>> submitStillCaptureRequests(
+            @NonNull List<CaptureConfig> captureConfigs,
+            @ImageCapture.CaptureMode int captureMode,
+            @ImageCapture.FlashType int flashType) {
+        return mCameraControlInternal.submitStillCaptureRequests(
+                captureConfigs,
+                captureMode,
+                flashType);
+    }
+
+    @NonNull
+    @Override
+    public SessionConfig getSessionConfig() {
+        return mCameraControlInternal.getSessionConfig();
+    }
+
+    @NonNull
+    @Override
+    public Rect getSensorRect() {
+        return mCameraControlInternal.getSensorRect();
+    }
+
+    @Override
+    public void addInteropConfig(@NonNull Config config) {
+        mCameraControlInternal.addInteropConfig(config);
+    }
+
+    @Override
+    public void clearInteropConfig() {
+        mCameraControlInternal.clearInteropConfig();
+    }
+
+    @NonNull
+    @Override
+    public Config getInteropConfig() {
+        return mCameraControlInternal.getInteropConfig();
+    }
+
+    @NonNull
+    @Override
+    public CameraControlInternal getImplementation() {
+        return mCameraControlInternal.getImplementation();
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
new file mode 100644
index 0000000..c389a1d
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl;
+
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.CameraState;
+import androidx.camera.core.DynamicRange;
+import androidx.camera.core.ExperimentalZeroShutterLag;
+import androidx.camera.core.ExposureState;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.ZoomState;
+import androidx.lifecycle.LiveData;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link CameraInfoInternal} that forwards all the calls into the given
+ * {@link CameraInfoInternal}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ForwardingCameraInfo implements CameraInfoInternal {
+
+    private final CameraInfoInternal mCameraInfoInternal;
+
+    /**
+     * Create an instance that will forward all calls to the supplied {@link CameraInfoInternal}
+     * instance.
+     */
+    public ForwardingCameraInfo(@NonNull CameraInfoInternal cameraInfoInternal) {
+        mCameraInfoInternal = cameraInfoInternal;
+    }
+
+    @Override
+    public int getSensorRotationDegrees() {
+        return mCameraInfoInternal.getSensorRotationDegrees();
+    }
+
+    @Override
+    public int getSensorRotationDegrees(int relativeRotation) {
+        return mCameraInfoInternal.getSensorRotationDegrees(relativeRotation);
+    }
+
+    @Override
+    public boolean hasFlashUnit() {
+        return mCameraInfoInternal.hasFlashUnit();
+    }
+
+    @NonNull
+    @Override
+    public LiveData<Integer> getTorchState() {
+        return mCameraInfoInternal.getTorchState();
+    }
+
+    @NonNull
+    @Override
+    public LiveData<ZoomState> getZoomState() {
+        return mCameraInfoInternal.getZoomState();
+    }
+
+    @NonNull
+    @Override
+    public ExposureState getExposureState() {
+        return mCameraInfoInternal.getExposureState();
+    }
+
+    @NonNull
+    @Override
+    public LiveData<CameraState> getCameraState() {
+        return mCameraInfoInternal.getCameraState();
+    }
+
+    @NonNull
+    @Override
+    public String getImplementationType() {
+        return mCameraInfoInternal.getImplementationType();
+    }
+
+    @Override
+    public int getLensFacing() {
+        return mCameraInfoInternal.getLensFacing();
+    }
+
+    @Override
+    public float getIntrinsicZoomRatio() {
+        return mCameraInfoInternal.getIntrinsicZoomRatio();
+    }
+
+    @Override
+    public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
+        return mCameraInfoInternal.isFocusMeteringSupported(action);
+    }
+
+    @Override
+    @ExperimentalZeroShutterLag
+    public boolean isZslSupported() {
+        return mCameraInfoInternal.isZslSupported();
+    }
+
+    @NonNull
+    @Override
+    public Set<Range<Integer>> getSupportedFrameRateRanges() {
+        return mCameraInfoInternal.getSupportedFrameRateRanges();
+    }
+
+    @Override
+    public boolean isPrivateReprocessingSupported() {
+        return mCameraInfoInternal.isPrivateReprocessingSupported();
+    }
+
+    @NonNull
+    @Override
+    public String getCameraId() {
+        return mCameraInfoInternal.getCameraId();
+    }
+
+    @Override
+    public void addSessionCaptureCallback(@NonNull Executor executor,
+            @NonNull CameraCaptureCallback callback) {
+        mCameraInfoInternal.addSessionCaptureCallback(executor, callback);
+    }
+
+    @Override
+    public void removeSessionCaptureCallback(@NonNull CameraCaptureCallback callback) {
+        mCameraInfoInternal.removeSessionCaptureCallback(callback);
+    }
+
+    @NonNull
+    @Override
+    public Quirks getCameraQuirks() {
+        return mCameraInfoInternal.getCameraQuirks();
+    }
+
+    @NonNull
+    @Override
+    public EncoderProfilesProvider getEncoderProfilesProvider() {
+        return mCameraInfoInternal.getEncoderProfilesProvider();
+    }
+
+    @NonNull
+    @Override
+    public Timebase getTimebase() {
+        return mCameraInfoInternal.getTimebase();
+    }
+
+    @NonNull
+    @Override
+    public List<Size> getSupportedResolutions(int format) {
+        return mCameraInfoInternal.getSupportedResolutions(format);
+    }
+
+    @NonNull
+    @Override
+    public List<Size> getSupportedHighResolutions(int format) {
+        return mCameraInfoInternal.getSupportedHighResolutions(format);
+    }
+
+    @NonNull
+    @Override
+    public Set<DynamicRange> getSupportedDynamicRanges() {
+        return mCameraInfoInternal.getSupportedDynamicRanges();
+    }
+
+    @NonNull
+    @Override
+    public CameraInfoInternal getImplementation() {
+        return mCameraInfoInternal.getImplementation();
+    }
+
+    @NonNull
+    @Override
+    public CameraSelector getCameraSelector() {
+        return mCameraInfoInternal.getCameraSelector();
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
new file mode 100644
index 0000000..6fc61f8
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraControl.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.FocusMeteringResult;
+import androidx.camera.core.impl.utils.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A {@link CameraControlInternal} whose capabilities can be restricted via
+ * {@link #enableRestrictedOperations(boolean, Set)}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class RestrictedCameraControl extends ForwardingCameraControl {
+    /**
+     * Defines the list of supported camera operations.
+     */
+    public static final int ZOOM = 0;
+    public static final int AUTO_FOCUS = 1;
+    public static final int AF_REGION = 2;
+    public static final int AE_REGION = 3;
+    public static final int AWB_REGION = 4;
+    public static final int FLASH = 5;
+    public static final int TORCH = 6;
+    public static final int EXPOSURE_COMPENSATION = 7;
+
+    public @interface CameraOperation {
+    }
+
+    private final CameraControlInternal mCameraControl;
+    private volatile boolean mUseRestrictedCameraOperations = false;
+    @Nullable
+    private volatile @CameraOperation Set<Integer> mRestrictedCameraOperations;
+
+    /**
+     * Creates the restricted version of the given {@link CameraControlInternal}.
+     */
+    public RestrictedCameraControl(@NonNull CameraControlInternal cameraControl) {
+        super(cameraControl);
+        mCameraControl = cameraControl;
+    }
+
+    /**
+     * Enable or disable the restricted operations. If disabled, it works just like the origin
+     * CameraControlInternal instance.
+     */
+    public void enableRestrictedOperations(boolean enable,
+            @Nullable @CameraOperation Set<Integer> restrictedOperations) {
+        mUseRestrictedCameraOperations = enable;
+        mRestrictedCameraOperations = restrictedOperations;
+    }
+
+    /**
+     * Returns implementation instance.
+     */
+    @NonNull
+    @Override
+    public CameraControlInternal getImplementation() {
+        return mCameraControl;
+    }
+
+    boolean isOperationSupported(
+            @NonNull @CameraOperation int... operations) {
+        if (!mUseRestrictedCameraOperations || mRestrictedCameraOperations == null) {
+            return true;
+        }
+
+        // Arrays.asList doesn't work for int array.
+        List<Integer> operationList = new ArrayList<>(operations.length);
+        for (int operation : operations) {
+            operationList.add(operation);
+        }
+
+        return mRestrictedCameraOperations.containsAll(operationList);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> enableTorch(boolean torch) {
+        if (!isOperationSupported(TORCH)) {
+            return Futures.immediateFailedFuture(
+                    new IllegalStateException("Torch is not supported"));
+        }
+        return mCameraControl.enableTorch(torch);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
+            @NonNull FocusMeteringAction action) {
+        FocusMeteringAction modifiedAction = getModifiedFocusMeteringAction(action);
+        if (modifiedAction == null) {
+            return Futures.immediateFailedFuture(
+                    new IllegalStateException("FocusMetering is not supported"));
+        }
+
+        return mCameraControl.startFocusAndMetering(modifiedAction);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> cancelFocusAndMetering() {
+        return mCameraControl.cancelFocusAndMetering();
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> setZoomRatio(float ratio) {
+        if (!isOperationSupported(ZOOM)) {
+            return Futures.immediateFailedFuture(
+                    new IllegalStateException("Zoom is not supported"));
+        }
+        return mCameraControl.setZoomRatio(ratio);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> setLinearZoom(float linearZoom) {
+        if (!isOperationSupported(ZOOM)) {
+            return Futures.immediateFailedFuture(
+                    new IllegalStateException("Zoom is not supported"));
+        }
+        return mCameraControl.setLinearZoom(linearZoom);
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
+        if (!isOperationSupported(EXPOSURE_COMPENSATION)) {
+            return Futures.immediateFailedFuture(
+                    new IllegalStateException("ExposureCompensation is not supported"));
+        }
+        return mCameraControl.setExposureCompensationIndex(value);
+    }
+
+    /**
+     * Returns the modified {@link FocusMeteringAction} that filters out unsupported AE/AF/AWB
+     * regions. Returns null if none of AF/AE/AWB regions can be supported after the filtering.
+     */
+    @Nullable
+    FocusMeteringAction getModifiedFocusMeteringAction(@NonNull FocusMeteringAction action) {
+        boolean shouldModify = false;
+        FocusMeteringAction.Builder builder = new FocusMeteringAction.Builder(action);
+        if (!action.getMeteringPointsAf().isEmpty()
+                && !isOperationSupported(AUTO_FOCUS, AF_REGION)) {
+            shouldModify = true;
+            builder.removePoints(FocusMeteringAction.FLAG_AF);
+        }
+
+        if (!action.getMeteringPointsAe().isEmpty()
+                && !isOperationSupported(AE_REGION)) {
+            shouldModify = true;
+            builder.removePoints(FocusMeteringAction.FLAG_AE);
+        }
+
+        if (!action.getMeteringPointsAwb().isEmpty()
+                && !isOperationSupported(AWB_REGION)) {
+            shouldModify = true;
+            builder.removePoints(FocusMeteringAction.FLAG_AWB);
+        }
+
+        // Returns origin action if no need to modify.
+        if (!shouldModify) {
+            return action;
+        }
+
+        FocusMeteringAction modifyAction = builder.build();
+        if (modifyAction.getMeteringPointsAf().isEmpty()
+                && modifyAction.getMeteringPointsAe().isEmpty()
+                && modifyAction.getMeteringPointsAwb().isEmpty()) {
+            // All regions are not allowed, return null.
+            return null;
+        }
+        return builder.build();
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
new file mode 100644
index 0000000..b2148f5
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/RestrictedCameraInfo.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl;
+
+import android.util.Range;
+import android.util.Rational;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ExposureState;
+import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.TorchState;
+import androidx.camera.core.ZoomState;
+import androidx.camera.core.internal.ImmutableZoomState;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+/**
+ * A {@link CameraInfoInternal} that returns disabled state if the corresponding operation in the
+ * given {@link RestrictedCameraControl} is disabled.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class RestrictedCameraInfo extends ForwardingCameraInfo {
+    private final CameraInfoInternal mCameraInfo;
+    private final RestrictedCameraControl mRestrictedCameraControl;
+
+    public RestrictedCameraInfo(@NonNull CameraInfoInternal cameraInfo,
+            @NonNull RestrictedCameraControl restrictedCameraControl) {
+        super(cameraInfo);
+        mCameraInfo = cameraInfo;
+        mRestrictedCameraControl = restrictedCameraControl;
+    }
+
+    @NonNull
+    @Override
+    public CameraInfoInternal getImplementation() {
+        return mCameraInfo;
+    }
+
+    @Override
+    public boolean hasFlashUnit() {
+        if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.FLASH)) {
+            return false;
+        }
+
+        return mCameraInfo.hasFlashUnit();
+    }
+
+    @NonNull
+    @Override
+    public LiveData<Integer> getTorchState() {
+        if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.TORCH)) {
+            return new MutableLiveData<>(TorchState.OFF);
+        }
+
+        return mCameraInfo.getTorchState();
+    }
+
+    @NonNull
+    @Override
+    public LiveData<ZoomState> getZoomState() {
+        if (!mRestrictedCameraControl.isOperationSupported(RestrictedCameraControl.ZOOM)) {
+            return new MutableLiveData<>(ImmutableZoomState.create(
+                    /* zoomRatio */1f, /* maxZoomRatio */ 1f,
+                    /* minZoomRatio */ 1f, /* linearZoom*/ 0f));
+        }
+        return mCameraInfo.getZoomState();
+    }
+
+    @NonNull
+    @Override
+    public ExposureState getExposureState() {
+        if (!mRestrictedCameraControl.isOperationSupported(
+                RestrictedCameraControl.EXPOSURE_COMPENSATION)) {
+            return new ExposureState() {
+                @Override
+                public int getExposureCompensationIndex() {
+                    return 0;
+                }
+
+                @NonNull
+                @Override
+                public Range<Integer> getExposureCompensationRange() {
+                    return new Range<>(0, 0);
+                }
+
+                @NonNull
+                @Override
+                public Rational getExposureCompensationStep() {
+                    return Rational.ZERO;
+                }
+
+                @Override
+                public boolean isExposureCompensationSupported() {
+                    return false;
+                }
+            };
+        }
+        return mCameraInfo.getExposureState();
+    }
+
+    @Override
+    public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
+        if (mRestrictedCameraControl.getModifiedFocusMeteringAction(action) == null) {
+            return false;
+        }
+        return mCameraInfo.isFocusMeteringSupported(action);
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
index f9858f5..70b5c2b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionProcessor.java
@@ -24,7 +24,9 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.CameraInfo;
 
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A processor for (1) transforming the surfaces used in Preview/ImageCapture/ImageAnalysis
@@ -127,6 +129,14 @@
     }
 
     /**
+     * Returns the supported camera operations when the SessionProcessor is enabled.
+     */
+    @NonNull
+    default @RestrictedCameraControl.CameraOperation Set<Integer> getSupportedCameraOperations() {
+        return Collections.emptySet();
+    }
+
+    /**
      * Callback for {@link #startRepeating} and {@link #startCapture}.
      */
     interface CaptureCallback {
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 9d3021c..4e74b5e 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
@@ -59,6 +59,10 @@
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.impl.CameraMode;
 import androidx.camera.core.impl.Config;
+import androidx.camera.core.impl.RestrictedCameraControl;
+import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
+import androidx.camera.core.impl.RestrictedCameraInfo;
+import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.core.impl.StreamSpec;
 import androidx.camera.core.impl.SurfaceConfig;
 import androidx.camera.core.impl.UseCaseConfig;
@@ -143,6 +147,12 @@
     @Nullable
     private StreamSharing mStreamSharing;
 
+    @NonNull
+    private final RestrictedCameraControl mRestrictedCameraControl;
+    @NonNull
+    private final RestrictedCameraInfo mRestrictedCameraInfo;
+
+
     /**
      * Create a new {@link CameraUseCaseAdapter} instance.
      *
@@ -167,6 +177,12 @@
         mCameraCoordinator = cameraCoordinator;
         mCameraDeviceSurfaceManager = cameraDeviceSurfaceManager;
         mUseCaseConfigFactory = useCaseConfigFactory;
+        // TODO(b/279996499): bind the same restricted CameraControl and CameraInfo to use cases.
+        mRestrictedCameraControl =
+                new RestrictedCameraControl(mCameraInternal.getCameraControlInternal());
+        mRestrictedCameraInfo =
+                new RestrictedCameraInfo(mCameraInternal.getCameraInfoInternal(),
+                        mRestrictedCameraControl);
     }
 
     /**
@@ -603,14 +619,14 @@
             Map<UseCaseConfig<?>, List<Size>> configToSupportedSizesMap = new HashMap<>();
             Rect sensorRect;
             try {
-                sensorRect = ((CameraControlInternal) getCameraControl()).getSensorRect();
+                sensorRect = mCameraInternal.getCameraControlInternal().getSensorRect();
             } catch (NullPointerException e) {
                 // TODO(b/274531208): Remove the unnecessary SENSOR_INFO_ACTIVE_ARRAY_SIZE NPE
                 //  check related code only which is used for robolectric tests
                 sensorRect = null;
             }
             SupportedOutputSizesSorter supportedOutputSizesSorter = new SupportedOutputSizesSorter(
-                    (CameraInfoInternal) getCameraInfo(),
+                    cameraInfoInternal,
                     sensorRect != null ? rectToSize(sensorRect) : null);
             for (UseCase useCase : newUseCases) {
                 ConfigPair configPair = configPairMap.get(useCase);
@@ -809,13 +825,13 @@
     @NonNull
     @Override
     public CameraControl getCameraControl() {
-        return mCameraInternal.getCameraControlInternal();
+        return mRestrictedCameraControl;
     }
 
     @NonNull
     @Override
     public CameraInfo getCameraInfo() {
-        return mCameraInternal.getCameraInfoInternal();
+        return mRestrictedCameraInfo;
     }
 
     @NonNull
@@ -846,6 +862,14 @@
             }
 
             mCameraConfig = cameraConfig;
+            SessionProcessor sessionProcessor = mCameraConfig.getSessionProcessor(null);
+            if (sessionProcessor != null) {
+                @CameraOperation Set<Integer> supportedOps =
+                        sessionProcessor.getSupportedCameraOperations();
+                mRestrictedCameraControl.enableRestrictedOperations(true, supportedOps);
+            } else {
+                mRestrictedCameraControl.enableRestrictedOperations(false, null);
+            }
 
             //Configure the CameraInternal as well so that it can get SessionProcessor.
             mCameraInternal.setExtendedConfig(mCameraConfig);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
index 1ca8ace..294173d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraControl.java
@@ -20,18 +20,14 @@
 import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
 
-import android.graphics.Rect;
 import android.os.Build;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
-import androidx.camera.core.FocusMeteringAction;
-import androidx.camera.core.FocusMeteringResult;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.impl.CameraControlInternal;
 import androidx.camera.core.impl.CaptureConfig;
-import androidx.camera.core.impl.Config;
-import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.impl.ForwardingCameraControl;
 import androidx.camera.core.impl.utils.futures.Futures;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -42,83 +38,20 @@
  * A {@link CameraControlInternal} that is used to control the virtual camera.
  */
 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public class VirtualCameraControl implements CameraControlInternal {
+public class VirtualCameraControl extends ForwardingCameraControl {
 
     private static final int DEFAULT_JPEG_QUALITY = 100;
 
-    private final CameraControlInternal mParent;
     private final StreamSharing.Control mStreamSharingControl;
 
     VirtualCameraControl(@NonNull CameraControlInternal parent,
             @NonNull StreamSharing.Control streamSharingControl) {
-        mParent = parent;
+        super(parent);
         mStreamSharingControl = streamSharingControl;
     }
 
     @NonNull
     @Override
-    public ListenableFuture<Void> enableTorch(boolean torch) {
-        return mParent.enableTorch(torch);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
-            @NonNull FocusMeteringAction action) {
-        return mParent.startFocusAndMetering(action);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<Void> cancelFocusAndMetering() {
-        return mParent.cancelFocusAndMetering();
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<Void> setZoomRatio(float ratio) {
-        return mParent.setZoomRatio(ratio);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<Void> setLinearZoom(float linearZoom) {
-        return mParent.setLinearZoom(linearZoom);
-    }
-
-    @NonNull
-    @Override
-    public ListenableFuture<Integer> setExposureCompensationIndex(int value) {
-        return mParent.setExposureCompensationIndex(value);
-    }
-
-    @Override
-    public int getFlashMode() {
-        return mParent.getFlashMode();
-    }
-
-    @Override
-    public void setFlashMode(int flashMode) {
-        mParent.setFlashMode(flashMode);
-    }
-
-    @Override
-    public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
-        mParent.addZslConfig(sessionConfigBuilder);
-    }
-
-    @Override
-    public void setZslDisabledByUserCaseConfig(boolean disabled) {
-        mParent.setZslDisabledByUserCaseConfig(disabled);
-    }
-
-    @Override
-    public boolean isZslDisabledByByUserCaseConfig() {
-        return mParent.isZslDisabledByByUserCaseConfig();
-    }
-
-    @NonNull
-    @Override
     public ListenableFuture<List<Void>> submitStillCaptureRequests(
             @NonNull List<CaptureConfig> captureConfigs,
             @ImageCapture.CaptureMode int captureMode,
@@ -132,32 +65,4 @@
         return requireNonNull(captureConfig.getImplementationOptions().retrieveOption(
                 CaptureConfig.OPTION_JPEG_QUALITY, DEFAULT_JPEG_QUALITY));
     }
-
-    @NonNull
-    @Override
-    public SessionConfig getSessionConfig() {
-        return mParent.getSessionConfig();
-    }
-
-    @NonNull
-    @Override
-    public Rect getSensorRect() {
-        return mParent.getSensorRect();
-    }
-
-    @Override
-    public void addInteropConfig(@NonNull Config config) {
-        mParent.addInteropConfig(config);
-    }
-
-    @Override
-    public void clearInteropConfig() {
-        mParent.clearInteropConfig();
-    }
-
-    @NonNull
-    @Override
-    public Config getInteropConfig() {
-        return mParent.getInteropConfig();
-    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java b/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
index c20d759..1f7200f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/FocusMeteringActionTest.java
@@ -23,6 +23,7 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
+import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(RobolectricTestRunner.class)
@@ -311,13 +312,53 @@
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void builderWithNullPoint() {
-        new FocusMeteringAction.Builder(null).build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
     public void builderWithNullPoint2() {
         new FocusMeteringAction.Builder(null, FocusMeteringAction.FLAG_AF).build();
     }
 
+    @Test
+    public void copyBuilder() {
+        // 1. Arrange
+        FocusMeteringAction action1 = new FocusMeteringAction.Builder(mPoint1)
+                .addPoint(mPoint2, FocusMeteringAction.FLAG_AE)
+                .setAutoCancelDuration(8000, TimeUnit.MILLISECONDS)
+                .build();
+
+        // 2. Act
+        FocusMeteringAction action2 = new FocusMeteringAction.Builder(action1).build();
+
+        // 3. Assert
+        assertThat(action1.getMeteringPointsAf()).containsExactlyElementsIn(
+                action2.getMeteringPointsAf()
+        );
+        assertThat(action1.getMeteringPointsAe()).containsExactlyElementsIn(
+                action2.getMeteringPointsAe()
+        );
+        assertThat(action1.getMeteringPointsAwb()).containsExactlyElementsIn(
+                action2.getMeteringPointsAwb()
+        );
+        assertThat(action1.getAutoCancelDurationInMillis())
+                .isEqualTo(action2.getAutoCancelDurationInMillis());
+        assertThat(action1.isAutoCancelEnabled()).isEqualTo(action2.isAutoCancelEnabled());
+    }
+
+    @Test
+    public void removePoints() {
+        // 1. Arrange
+        FocusMeteringAction.Builder builder = new FocusMeteringAction.Builder(mPoint1)
+                .addPoint(mPoint2, FocusMeteringAction.FLAG_AE);
+
+        // 2. Act
+        FocusMeteringAction action = builder.removePoints(FocusMeteringAction.FLAG_AE).build();
+
+        // 3. Assert
+        assertThat(action.getMeteringPointsAe()).isEmpty();
+        assertThat(action.getMeteringPointsAf()).containsExactlyElementsIn(
+                Arrays.asList(mPoint1)
+        );
+        assertThat(action.getMeteringPointsAwb()).containsExactlyElementsIn(
+                Arrays.asList(mPoint1)
+        );
+    }
+
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index fe701ff..6f8fa37 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -23,6 +23,7 @@
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Looper.getMainLooper
+import android.util.Range
 import android.util.Rational
 import android.util.Size
 import android.view.Surface
@@ -669,6 +670,13 @@
         }
     }
 
+    @Test
+    fun canSetTargetFrameRate() {
+        val preview = Preview.Builder().setTargetFrameRate(Range(15, 30))
+            .build()
+        assertThat(preview.targetFrameRate).isEqualTo(Range(15, 30))
+    }
+
     private fun bindToLifecycleAndGetSurfaceRequest(): SurfaceRequest {
         return bindToLifecycleAndGetResult(null).first
     }
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 cabf0cc..670ef68 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
@@ -20,15 +20,22 @@
 import android.graphics.Matrix
 import android.graphics.Rect
 import android.os.Build
+import android.util.Range
 import android.util.Rational
 import android.util.Size
 import android.view.Surface
 import androidx.camera.core.CameraEffect
 import androidx.camera.core.CameraEffect.PREVIEW
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.FocusMeteringAction
+import androidx.camera.core.FocusMeteringAction.FLAG_AE
+import androidx.camera.core.FocusMeteringAction.FLAG_AF
+import androidx.camera.core.FocusMeteringAction.FLAG_AWB
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
+import androidx.camera.core.SurfaceOrientedMeteringPointFactory
+import androidx.camera.core.TorchState
 import androidx.camera.core.UseCase
 import androidx.camera.core.ViewPort
 import androidx.camera.core.concurrent.CameraCoordinator
@@ -38,6 +45,8 @@
 import androidx.camera.core.impl.Identifier
 import androidx.camera.core.impl.MutableOptionsBundle
 import androidx.camera.core.impl.OptionsBundle
+import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.StreamSpec
 import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -45,17 +54,23 @@
 import androidx.camera.core.processing.DefaultSurfaceProcessor
 import androidx.camera.core.streamsharing.StreamSharing
 import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraControl
 import androidx.camera.testing.fakes.FakeCameraCoordinator
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.fakes.FakeSessionProcessor
 import androidx.camera.testing.fakes.FakeSurfaceEffect
 import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 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 androidx.concurrent.futures.await
+import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
+import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -81,7 +96,6 @@
     instrumentedPackages = ["androidx.camera.core"]
 )
 class CameraUseCaseAdapterTest {
-
     private lateinit var effects: List<CameraEffect>
     private lateinit var executor: ExecutorService
 
@@ -93,6 +107,8 @@
     private lateinit var sharedEffect: FakeSurfaceEffect
     private lateinit var cameraCoordinator: CameraCoordinator
     private lateinit var surfaceProcessorInternal: FakeSurfaceProcessorInternal
+    private lateinit var fakeCameraControl: FakeCameraControl
+    private lateinit var fakeCameraInfo: FakeCameraInfoInternal
     private val fakeCameraSet = LinkedHashSet<CameraInternal>()
     private val imageEffect = GrayscaleImageEffect()
     private val preview = Preview.Builder().build()
@@ -106,7 +122,9 @@
     @Before
     fun setUp() {
         fakeCameraDeviceSurfaceManager = FakeCameraDeviceSurfaceManager()
-        fakeCamera = FakeCamera(CAMERA_ID)
+        fakeCameraControl = FakeCameraControl()
+        fakeCameraInfo = FakeCameraInfoInternal()
+        fakeCamera = FakeCamera(CAMERA_ID, fakeCameraControl, fakeCameraInfo)
         cameraCoordinator = FakeCameraCoordinator()
         useCaseConfigFactory = FakeUseCaseConfigFactory()
         fakeCameraSet.add(fakeCamera)
@@ -209,7 +227,7 @@
     fun invalidUseCaseComboCantBeFixedByStreamSharing_throwsException() {
         // Arrange: create a camera that only support one JPEG stream.
         fakeCameraDeviceSurfaceManager.setValidSurfaceCombos(setOf(listOf(JPEG)))
-        // Act: add PRIV and JPEG streams.
+        // Act: add PRIVATE and JPEG streams.
         adapter.addUseCases(setOf(preview, image))
     }
 
@@ -909,6 +927,316 @@
         assertThat(video.effect).isNull()
     }
 
+    private fun createAdapterWithSupportedCameraOperations(
+        @RestrictedCameraControl.CameraOperation supportedOps: Set<Int>
+    ): CameraUseCaseAdapter {
+        val cameraUseCaseAdapter = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory
+        )
+
+        val fakeSessionProcessor = FakeSessionProcessor()
+        // no camera operations are supported.
+        fakeSessionProcessor.restrictedCameraOperations = supportedOps
+        val cameraConfig: CameraConfig = FakeCameraConfig(fakeSessionProcessor)
+        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
+        return cameraUseCaseAdapter
+    }
+
+    @Test
+    fun cameraControlFailed_whenNoCameraOperationsSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(supportedOps = emptySet())
+
+        // 2. Act && Assert
+        assertThrows<IllegalStateException> {
+            cameraUseCaseAdapter.cameraControl.setZoomRatio(1.0f).await()
+        }
+        assertThrows<IllegalStateException> {
+            cameraUseCaseAdapter.cameraControl.setLinearZoom(1.0f).await()
+        }
+        assertThrows<IllegalStateException> {
+            cameraUseCaseAdapter.cameraControl.enableTorch(true).await()
+        }
+        assertThrows<IllegalStateException> {
+            cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+                getFocusMeteringAction()
+            ).await()
+        }
+        assertThrows<IllegalStateException> {
+            cameraUseCaseAdapter.cameraControl.setExposureCompensationIndex(0).await()
+        }
+    }
+
+    private fun getFocusMeteringAction(
+        meteringMode: Int = FLAG_AF or FLAG_AE or FLAG_AWB
+    ): FocusMeteringAction {
+        val pointFactory = SurfaceOrientedMeteringPointFactory(1f, 1f)
+        return FocusMeteringAction.Builder(
+            pointFactory.createPoint(0.5f, 0.5f), meteringMode)
+            .build()
+    }
+
+    @Test
+    fun zoomEnabled_whenZoomOperationsSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(RestrictedCameraControl.ZOOM))
+
+        // 2. Act && Assert
+        cameraUseCaseAdapter.cameraControl.setZoomRatio(2.0f).await()
+        assertThat(fakeCameraControl.zoomRatio).isEqualTo(2.0f)
+        cameraUseCaseAdapter.cameraControl.setLinearZoom(1.0f).await()
+        assertThat(fakeCameraControl.linearZoom).isEqualTo(1.0f)
+    }
+
+    @Test
+    fun torchEnabled_whenTorchOperationSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(RestrictedCameraControl.TORCH))
+
+        // 2. Act
+        cameraUseCaseAdapter.cameraControl.enableTorch(true).await()
+
+        // 3. Assert
+        assertThat(fakeCameraControl.torchEnabled).isEqualTo(true)
+    }
+
+    @Test
+    fun focusMetering_afEnabled_whenAfOperationSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(
+                    RestrictedCameraControl.AUTO_FOCUS,
+                    RestrictedCameraControl.AF_REGION,
+                    ))
+
+        // 2. Act
+        cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+            getFocusMeteringAction()
+        ).await()
+
+        // 3. Assert
+        // Only AF point remains, AE/AWB points removed.
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAf?.size)
+            .isEqualTo(1)
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAe)
+            .isEmpty()
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAwb)
+            .isEmpty()
+    }
+
+    @Test
+    fun focusMetering_aeEnabled_whenAeOperationsSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(
+                    RestrictedCameraControl.AE_REGION,
+                ))
+
+        // 2. Act
+        cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+            getFocusMeteringAction()
+        ).await()
+
+        // 3. Assert
+        // Only AE point remains, AF/AWB points removed.
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAe?.size)
+            .isEqualTo(1)
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAf)
+            .isEmpty()
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAwb)
+            .isEmpty()
+    }
+
+    @Test
+    fun focusMetering_awbEnabled_whenAwbOperationsSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(
+                    RestrictedCameraControl.AWB_REGION,
+                ))
+
+        // 2. Act
+        cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+            getFocusMeteringAction()
+        ).await()
+
+        // 3. Assert
+        // Only AWB point remains, AF/AE points removed.
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAwb?.size)
+            .isEqualTo(1)
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAf)
+            .isEmpty()
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction?.meteringPointsAe)
+            .isEmpty()
+    }
+
+    @Test
+    fun focusMetering_disabled_whenNoneIsSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(
+                    RestrictedCameraControl.AE_REGION,
+                ))
+
+        // 2. Act && Assert
+        assertThrows<IllegalStateException> {
+            cameraUseCaseAdapter.cameraControl.startFocusAndMetering(
+                getFocusMeteringAction(FLAG_AF or FLAG_AWB)
+            ).await()
+        }
+        assertThat(fakeCameraControl.lastSubmittedFocusMeteringAction).isNull()
+    }
+
+    @Test
+    fun exposureEnabled_whenExposureOperationSupported(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(RestrictedCameraControl.EXPOSURE_COMPENSATION))
+
+        // 2. Act
+        cameraUseCaseAdapter.cameraControl.setExposureCompensationIndex(0).await()
+
+        // 3. Assert
+        assertThat(fakeCameraControl.exposureCompensationIndex).isEqualTo(0)
+    }
+
+    @Test
+    fun cameraInfo_returnsDisabledState_AllOpsDisabled(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = emptySet())
+
+        // 2. Act && Assert
+        // Zoom is disabled
+        val zoomState = cameraUseCaseAdapter.cameraInfo.zoomState.value!!
+        assertThat(zoomState.minZoomRatio).isEqualTo(1f)
+        assertThat(zoomState.maxZoomRatio).isEqualTo(1f)
+        assertThat(zoomState.zoomRatio).isEqualTo(1f)
+        assertThat(zoomState.linearZoom).isEqualTo(0f)
+
+        // Flash is disabled
+        assertThat(cameraUseCaseAdapter.cameraInfo.hasFlashUnit()).isFalse()
+
+        // Torch is disabled.
+        assertThat(cameraUseCaseAdapter.cameraInfo.torchState.value).isEqualTo(TorchState.OFF)
+
+        // FocusMetering is disabled.
+        assertThat(cameraUseCaseAdapter.cameraInfo
+            .isFocusMeteringSupported(getFocusMeteringAction()))
+            .isFalse()
+
+        // ExposureCompensation is disabled.
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.isExposureCompensationSupported)
+            .isFalse()
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationRange)
+            .isEqualTo(Range(0, 0))
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationStep)
+            .isEqualTo(Rational.ZERO)
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationIndex)
+            .isEqualTo(0)
+    }
+
+    @Test
+    fun cameraInfo_zoomEnabled(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(RestrictedCameraControl.ZOOM)
+            )
+        fakeCameraInfo.setZoom(10f, 0.6f, 10f, 1f)
+
+        // 2. Act
+        val zoomState = cameraUseCaseAdapter.cameraInfo.zoomState.value!!
+
+        // 3. Assert
+        val fakeZoomState = fakeCameraInfo.zoomState.value!!
+        assertThat(zoomState.zoomRatio).isEqualTo(fakeZoomState.zoomRatio)
+        assertThat(zoomState.minZoomRatio).isEqualTo(fakeZoomState.minZoomRatio)
+        assertThat(zoomState.maxZoomRatio).isEqualTo(fakeZoomState.maxZoomRatio)
+        assertThat(zoomState.linearZoom).isEqualTo(fakeZoomState.linearZoom)
+    }
+
+    @Test
+    fun cameraInfo_torchEnabled(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(RestrictedCameraControl.TORCH)
+            )
+        fakeCameraInfo.setTorch(TorchState.ON)
+
+        // 2. Act && Assert
+        assertThat(cameraUseCaseAdapter.cameraInfo.torchState.value)
+            .isEqualTo(fakeCameraInfo.torchState.value)
+    }
+
+    @Test
+    fun cameraInfo_afEnabled(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(
+                    RestrictedCameraControl.AUTO_FOCUS,
+                    RestrictedCameraControl.AF_REGION
+                )
+            )
+        fakeCameraInfo.setIsFocusMeteringSupported(true)
+
+        // 2. Act && Assert
+        assertThat(cameraUseCaseAdapter.cameraInfo.isFocusMeteringSupported(
+            getFocusMeteringAction()
+        )).isTrue()
+    }
+
+    @Test
+    fun cameraInfo_exposureExposureEnabled(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(
+                    RestrictedCameraControl.EXPOSURE_COMPENSATION,
+                )
+            )
+        fakeCameraInfo.setExposureState(2, Range.create(0, 10), Rational(1, 1), true)
+
+        // 2. Act && Assert
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationIndex)
+            .isEqualTo(fakeCameraInfo.exposureState.exposureCompensationIndex)
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationRange)
+            .isEqualTo(fakeCameraInfo.exposureState.exposureCompensationRange)
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.exposureCompensationStep)
+            .isEqualTo(fakeCameraInfo.exposureState.exposureCompensationStep)
+        assertThat(cameraUseCaseAdapter.cameraInfo.exposureState.isExposureCompensationSupported)
+            .isEqualTo(fakeCameraInfo.exposureState.isExposureCompensationSupported)
+    }
+
+    @Test
+    fun cameraInfo_flashEnabled(): Unit = runBlocking {
+        // 1. Arrange
+        val cameraUseCaseAdapter =
+            createAdapterWithSupportedCameraOperations(
+                supportedOps = setOf(RestrictedCameraControl.FLASH)
+            )
+
+        // 2. Act && Assert
+        assertThat(cameraUseCaseAdapter.cameraInfo.hasFlashUnit())
+            .isEqualTo(fakeCameraInfo.hasFlashUnit())
+    }
+
     private fun createCoexistingRequiredRuleCameraConfig(): CameraConfig {
         return object : CameraConfig {
             private val mUseCaseConfigFactory =
@@ -950,7 +1278,9 @@
         return false
     }
 
-    private class FakeCameraConfig : CameraConfig {
+    private class FakeCameraConfig(
+        val sessionProcessor: FakeSessionProcessor? = null
+    ) : CameraConfig {
         private val mUseCaseConfigFactory =
             UseCaseConfigFactory { _, _ -> null }
         private val mIdentifier = Identifier.create(Any())
@@ -965,5 +1295,13 @@
         override fun getConfig(): Config {
             return OptionsBundle.emptyBundle()
         }
+
+        override fun getSessionProcessor(valueIfMissing: SessionProcessor?): SessionProcessor? {
+            return sessionProcessor ?: valueIfMissing
+        }
+
+        override fun getSessionProcessor(): SessionProcessor {
+            return sessionProcessor!!
+        }
     }
 }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index 0e76994..cfa06c8 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -204,7 +204,8 @@
     @Test
     fun canInvokeStartTrigger() = runBlocking {
         val fakeSessionProcessImpl = FakeSessionProcessImpl()
-        val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl, context)
+        val advancedSessionProcessor = AdvancedSessionProcessor(
+            fakeSessionProcessImpl, emptyList(), context)
 
         val parametersMap: MutableMap<CaptureRequest.Key<*>, Any> = mutableMapOf(
             CaptureRequest.CONTROL_AF_MODE to CaptureRequest.CONTROL_AF_MODE_AUTO,
@@ -375,7 +376,8 @@
         imageCapture: ImageCapture,
         imageAnalysis: ImageAnalysis? = null
     ) {
-        val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl, context)
+        val advancedSessionProcessor = AdvancedSessionProcessor(fakeSessionProcessImpl,
+            emptyList(), context)
         val latchPreviewFrame = CountDownLatch(1)
         val latchAnalysis = CountDownLatch(1)
         val deferCapturedImage = CompletableDeferred<ImageProxy>()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index f9e1b5a..288929c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -154,7 +154,7 @@
         fakePreviewExtenderImpl = FakePreviewExtenderImpl(previewProcessorType)
         fakeCaptureExtenderImpl = FakeImageCaptureExtenderImpl(hasCaptureProcessor)
         basicExtenderSessionProcessor = BasicExtenderSessionProcessor(
-            fakePreviewExtenderImpl, fakeCaptureExtenderImpl, context
+            fakePreviewExtenderImpl, fakeCaptureExtenderImpl, emptyList(), context
         )
     }
 
@@ -205,7 +205,7 @@
             hasCaptureProcessor, throwErrorOnProcess = true
         )
         basicExtenderSessionProcessor = BasicExtenderSessionProcessor(
-            fakePreviewExtenderImpl, fakeCaptureExtenderImpl, context
+            fakePreviewExtenderImpl, fakeCaptureExtenderImpl, emptyList(), context
         )
         val preview = Preview.Builder().build()
         val imageCapture = ImageCapture.Builder().build()
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
index 17a7a78..5722a6b 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
 import android.util.Pair;
 import android.util.Range;
 import android.util.Size;
@@ -27,9 +28,11 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.camera2.interop.Camera2CameraInfo;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
+import androidx.camera.core.Logger;
 import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.extensions.ExtensionMode;
 import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl;
@@ -52,6 +55,7 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class AdvancedVendorExtender implements VendorExtender {
+    private static final String TAG = "AdvancedVendorExtender";
     private final ExtensionDisabledValidator mExtensionDisabledValidator =
             new ExtensionDisabledValidator();
     private final AdvancedExtenderImpl mAdvancedExtenderImpl;
@@ -84,6 +88,11 @@
         }
     }
 
+    @VisibleForTesting
+    AdvancedVendorExtender(AdvancedExtenderImpl advancedExtenderImpl) {
+        mAdvancedExtenderImpl = advancedExtenderImpl;
+    }
+
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Override
     public void init(@NonNull CameraInfo cameraInfo) {
@@ -151,11 +160,28 @@
         return yuvList == null ? new Size[0] : yuvList.toArray(new Size[0]);
     }
 
+    @NonNull
+    private List<CaptureRequest.Key> getSupportedParameterKeys() {
+        List<CaptureRequest.Key> keys = Collections.emptyList();
+        if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_3) >= 0) {
+            try {
+                keys = Collections.unmodifiableList(
+                        mAdvancedExtenderImpl.getAvailableCaptureRequestKeys());
+            } catch (Exception e) {
+                Logger.e(TAG, "AdvancedExtenderImpl.getAvailableCaptureRequestKeys "
+                        + "throws exceptions", e);
+            }
+        }
+        return keys;
+    }
+
     @Nullable
     @Override
     public SessionProcessor createSessionProcessor(@NonNull Context context) {
         Preconditions.checkNotNull(mCameraId, "VendorExtender#init() must be called first");
         return new AdvancedSessionProcessor(
-                mAdvancedExtenderImpl.createSessionProcessor(), context);
+                mAdvancedExtenderImpl.createSessionProcessor(),
+                getSupportedParameterKeys(),
+                context);
     }
 }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index 2a7ed76..61f0550 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Build;
 import android.util.Pair;
 import android.util.Range;
 import android.util.Size;
@@ -28,6 +30,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.camera2.interop.Camera2CameraInfo;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
@@ -46,14 +49,14 @@
 import androidx.camera.extensions.impl.NightImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.NightPreviewExtenderImpl;
 import androidx.camera.extensions.impl.PreviewExtenderImpl;
+import androidx.camera.extensions.internal.compat.workaround.AvailableKeysRetriever;
 import androidx.camera.extensions.internal.compat.workaround.ExtensionDisabledValidator;
 import androidx.camera.extensions.internal.sessionprocessor.BasicExtenderSessionProcessor;
 import androidx.core.util.Preconditions;
 
-import org.jetbrains.annotations.TestOnly;
-
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -69,6 +72,27 @@
     private PreviewExtenderImpl mPreviewExtenderImpl = null;
     private ImageCaptureExtenderImpl mImageCaptureExtenderImpl = null;
     private CameraInfo mCameraInfo;
+    private String mCameraId;
+    private CameraCharacteristics mCameraCharacteristics;
+    private AvailableKeysRetriever mAvailableKeysRetriever = new AvailableKeysRetriever();
+
+    static final List<CaptureRequest.Key> sBaseSupportedKeys = new ArrayList<>(Arrays.asList(
+            CaptureRequest.SCALER_CROP_REGION,
+            CaptureRequest.CONTROL_AF_MODE,
+            CaptureRequest.CONTROL_AF_TRIGGER,
+            CaptureRequest.CONTROL_AF_REGIONS,
+            CaptureRequest.CONTROL_AE_REGIONS,
+            CaptureRequest.CONTROL_AWB_REGIONS,
+            CaptureRequest.CONTROL_AE_MODE,
+            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+            CaptureRequest.FLASH_MODE,
+            CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
+    ));
+    static {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            sBaseSupportedKeys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
+        }
+    }
 
     public BasicVendorExtender(@ExtensionMode.Mode int mode) {
         try {
@@ -102,7 +126,7 @@
         }
     }
 
-    @TestOnly
+    @VisibleForTesting
     BasicVendorExtender(ImageCaptureExtenderImpl imageCaptureExtenderImpl,
             PreviewExtenderImpl previewExtenderImpl) {
         mPreviewExtenderImpl = previewExtenderImpl;
@@ -136,11 +160,11 @@
             return;
         }
 
-        String cameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
-        CameraCharacteristics cameraCharacteristics =
+        mCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
+        mCameraCharacteristics =
                 Camera2CameraInfo.extractCameraCharacteristics(cameraInfo);
-        mPreviewExtenderImpl.init(cameraId, cameraCharacteristics);
-        mImageCaptureExtenderImpl.init(cameraId, cameraCharacteristics);
+        mPreviewExtenderImpl.init(mCameraId, mCameraCharacteristics);
+        mImageCaptureExtenderImpl.init(mCameraId, mCameraCharacteristics);
 
         Logger.d(TAG, "PreviewExtender processorType= " + mPreviewExtenderImpl.getProcessorType());
         Logger.d(TAG, "ImageCaptureExtender processor= "
@@ -285,11 +309,41 @@
         return getOutputSizes(ImageFormat.YUV_420_888);
     }
 
+    @NonNull
+    private List<CaptureRequest.Key> getSupportedParameterKeys(Context context) {
+        if (ExtensionVersion.getRuntimeVersion().compareTo(Version.VERSION_1_3) >= 0) {
+            try {
+                List<CaptureRequest.Key> keys =
+                        Collections.unmodifiableList(
+                                mAvailableKeysRetriever.getAvailableCaptureRequestKeys(
+                                        mImageCaptureExtenderImpl,
+                                        mCameraId,
+                                        mCameraCharacteristics,
+                                        context));
+                if (keys == null) {
+                    keys = Collections.emptyList();
+                }
+                return keys;
+            } catch (Exception e) {
+                // it could crash on some OEMs.
+                Logger.e(TAG, "ImageCaptureExtenderImpl.getAvailableCaptureRequestKeys "
+                        + "throws exceptions", e);
+                return Collections.emptyList();
+            }
+        } else {
+            // For Basic Extender implementing v1.2 or below, we assume zoom/tap-to-focus/flash/EC
+            // are supported for compatibility reason.
+            return Collections.unmodifiableList(sBaseSupportedKeys);
+        }
+    }
+
     @Nullable
     @Override
     public SessionProcessor createSessionProcessor(@NonNull Context context) {
         Preconditions.checkNotNull(mCameraInfo, "VendorExtender#init() must be called first");
-        return new BasicExtenderSessionProcessor(mPreviewExtenderImpl, mImageCaptureExtenderImpl,
+        return new BasicExtenderSessionProcessor(
+                mPreviewExtenderImpl, mImageCaptureExtenderImpl,
+                getSupportedParameterKeys(context),
                 context);
     }
 }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java
index ec3f327..52af0b8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ExtensionVersion.java
@@ -22,6 +22,8 @@
 import androidx.camera.core.Logger;
 import androidx.camera.extensions.impl.ExtensionVersionImpl;
 
+import org.jetbrains.annotations.TestOnly;
+
 /**
  * Provides interfaces to check the extension version.
  */
@@ -31,6 +33,15 @@
 
     private static volatile ExtensionVersion sExtensionVersion;
 
+    /**
+     * For testing only. Inject a fake {@link ExtensionVersion}. Set it to {@code null} to unset
+     * it.
+     */
+    @TestOnly
+    public static void injectInstance(@Nullable ExtensionVersion extensionVersion) {
+        sExtensionVersion = extensionVersion;
+    }
+
     private static ExtensionVersion getInstance() {
         if (sExtensionVersion != null) {
             return sExtensionVersion;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
index 82bb2e7..532e1e8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
@@ -48,6 +48,10 @@
             quirks.add(new CrashWhenOnDisableTooSoon());
         }
 
+        if (GetAvailableKeysNeedsOnInit.load()) {
+            quirks.add(new GetAvailableKeysNeedsOnInit());
+        }
+
         return quirks;
     }
 }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/GetAvailableKeysNeedsOnInit.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/GetAvailableKeysNeedsOnInit.java
new file mode 100644
index 0000000..57f0e76
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/GetAvailableKeysNeedsOnInit.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * <p>QuirkSummary
+ * Bug Id: b/279541627,
+ * Description: ImageCaptureExtenderImpl.getAvailableCaptureRequestKeys and
+ * getAvailableCaptureResultKeys incorrectly expect onInit() to be invoked to supply the
+ * CameraCharacteristics. It causes a {@link NullPointerException} if onInit() is not invoked in
+ * prior to getAvailableCaptureRequestKeys or getAvailableCaptureResultKeys.
+ * Device(s): All Samsung devices
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class GetAvailableKeysNeedsOnInit implements Quirk {
+    static boolean load() {
+        return Build.BRAND.equalsIgnoreCase("SAMSUNG");
+    }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetriever.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetriever.java
new file mode 100644
index 0000000..605719e
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetriever.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal.compat.workaround;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl;
+import androidx.camera.extensions.internal.compat.quirk.DeviceQuirks;
+import androidx.camera.extensions.internal.compat.quirk.GetAvailableKeysNeedsOnInit;
+
+import java.util.List;
+
+/**
+ * A workaround for getting the available CaptureRequest keys safely.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class AvailableKeysRetriever {
+    boolean mShouldInvokeOnInit;
+
+    /**
+     * Default constructor.
+     */
+    public AvailableKeysRetriever() {
+        mShouldInvokeOnInit = DeviceQuirks.get(GetAvailableKeysNeedsOnInit.class) != null;
+    }
+
+    /**
+     * Get available CaptureRequest keys from the given {@link ImageCaptureExtenderImpl}. The
+     * cameraId, cameraCharacteristics and the context is needed for invoking onInit whenever
+     * necessary.
+     */
+    @NonNull
+    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys(
+            @NonNull ImageCaptureExtenderImpl imageCaptureExtender,
+            @NonNull String cameraId,
+            @NonNull CameraCharacteristics cameraCharacteristics,
+            @NonNull Context context) {
+        if (mShouldInvokeOnInit) {
+            imageCaptureExtender.onInit(cameraId, cameraCharacteristics, context);
+        }
+
+        try {
+            return imageCaptureExtender.getAvailableCaptureRequestKeys();
+        } finally {
+            if (mShouldInvokeOnInit) {
+                imageCaptureExtender.onDeInit();
+            }
+        }
+    }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
index ac7b358..ab42134 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/AdvancedSessionProcessor.java
@@ -62,7 +62,10 @@
     private final SessionProcessorImpl mImpl;
     private final Context mContext;
 
-    public AdvancedSessionProcessor(@NonNull SessionProcessorImpl impl, @NonNull Context context) {
+    public AdvancedSessionProcessor(@NonNull SessionProcessorImpl impl,
+            @NonNull List<CaptureRequest.Key> supportedKeys,
+            @NonNull Context context) {
+        super(supportedKeys);
         mImpl = impl;
         mContext = context;
     }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
index 5452a8a..4a282ab 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
@@ -96,7 +96,9 @@
 
     public BasicExtenderSessionProcessor(@NonNull PreviewExtenderImpl previewExtenderImpl,
             @NonNull ImageCaptureExtenderImpl imageCaptureExtenderImpl,
+            @NonNull List<CaptureRequest.Key> supportedKeys,
             @NonNull Context context) {
+        super(supportedKeys);
         mPreviewExtenderImpl = previewExtenderImpl;
         mImageCaptureExtenderImpl = imageCaptureExtenderImpl;
         mContext = context;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
index a10ddcb..f02f5fa 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/SessionProcessorBase.java
@@ -20,6 +20,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.media.Image;
 import android.media.ImageReader;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 
@@ -36,15 +37,20 @@
 import androidx.camera.core.Logger;
 import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.OutputSurface;
+import androidx.camera.core.impl.RestrictedCameraControl;
+import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.SessionProcessor;
 import androidx.camera.core.impl.SessionProcessorSurface;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Base class for SessionProcessor implementation. It is responsible for creating image readers and
@@ -67,6 +73,67 @@
     private String mCameraId;
 
     @NonNull
+
+    private final @CameraOperation Set<Integer> mSupportedCameraOperations;
+
+    SessionProcessorBase(@NonNull List<CaptureRequest.Key> supportedParameterKeys) {
+        mSupportedCameraOperations = getSupportedCameraOperations(supportedParameterKeys);
+    }
+
+    private @CameraOperation Set<Integer> getSupportedCameraOperations(
+            @NonNull List<CaptureRequest.Key> supportedParameterKeys) {
+        @CameraOperation Set<Integer> operations = new HashSet<>();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            if (supportedParameterKeys.contains(CaptureRequest.CONTROL_ZOOM_RATIO)
+                    || supportedParameterKeys.contains(CaptureRequest.SCALER_CROP_REGION)) {
+                operations.add(RestrictedCameraControl.ZOOM);
+            }
+        } else {
+            if (supportedParameterKeys.contains(CaptureRequest.SCALER_CROP_REGION)) {
+                operations.add(RestrictedCameraControl.ZOOM);
+            }
+        }
+
+        if (supportedParameterKeys.containsAll(
+                Arrays.asList(
+                        CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_MODE))) {
+            operations.add(RestrictedCameraControl.AUTO_FOCUS);
+        }
+
+        if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AF_REGIONS)) {
+            operations.add(RestrictedCameraControl.AF_REGION);
+        }
+
+        if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AE_REGIONS)) {
+            operations.add(RestrictedCameraControl.AE_REGION);
+        }
+
+        if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AWB_REGIONS)) {
+            operations.add(RestrictedCameraControl.AWB_REGION);
+        }
+
+        if (supportedParameterKeys.containsAll(
+                Arrays.asList(
+                        CaptureRequest.CONTROL_AE_MODE,
+                        CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER))) {
+            operations.add(RestrictedCameraControl.FLASH);
+        }
+
+        if (supportedParameterKeys.containsAll(
+                Arrays.asList(
+                        CaptureRequest.CONTROL_AE_MODE,
+                        CaptureRequest.FLASH_MODE))) {
+            operations.add(RestrictedCameraControl.TORCH);
+        }
+
+        if (supportedParameterKeys.contains(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION)) {
+            operations.add(RestrictedCameraControl.EXPOSURE_COMPENSATION);
+        }
+        return operations;
+    }
+
+    @NonNull
     private static SessionProcessorSurface createOutputConfigSurface(
             @NonNull Camera2OutputConfig outputConfig, Map<Integer, ImageReader> imageReaderMap) {
         if (outputConfig instanceof SurfaceOutputConfig) {
@@ -97,6 +164,7 @@
         }
         throw new UnsupportedOperationException("Unsupported Camera2OutputConfig:" + outputConfig);
     }
+
     @NonNull
     @Override
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
@@ -163,6 +231,12 @@
     }
 
     @NonNull
+    @Override
+    public @CameraOperation Set<Integer> getSupportedCameraOperations() {
+        return mSupportedCameraOperations;
+    }
+
+    @NonNull
     protected abstract Camera2SessionConfig initSessionInternal(
             @NonNull String cameraId,
             @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
new file mode 100644
index 0000000..0efc40a
--- /dev/null
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/SupportedCameraOperationsTest.kt
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal
+
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.SessionConfiguration
+import android.os.Build
+import android.util.Pair
+import android.util.Range
+import android.util.Size
+import androidx.camera.camera2.internal.Camera2CameraInfoImpl
+import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.core.impl.RestrictedCameraControl
+import androidx.camera.extensions.impl.CaptureStageImpl
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
+import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl
+import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl
+import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl
+import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
+import androidx.camera.extensions.impl.advanced.RequestProcessorImpl
+import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.ShadowCameraManager
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+    minSdk = Build.VERSION_CODES.LOLLIPOP,
+    instrumentedPackages = arrayOf("androidx.camera.extensions.internal")
+)
+class SupportedCameraOperationsTest(
+    private val extenderType: String
+) {
+    val context = RuntimeEnvironment.getApplication()
+
+    companion object {
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
+        fun createTestSet(): List<String> {
+            return listOf("basic", "advanced")
+        }
+    }
+    private fun setCameraXExtensionsVersion(version: String) {
+        val field = VersionName::class.java.getDeclaredField("CURRENT")
+        field.isAccessible = true
+        field[null] = VersionName(version)
+    }
+
+    private fun setExtensionRuntimeVersion(version: String) {
+        ExtensionVersion.injectInstance(object : ExtensionVersion() {
+            override fun isAdvancedExtenderSupportedInternal(): Boolean {
+                return false
+            }
+
+            override fun getVersionObject(): Version {
+                return Version.parse(version)!!
+            }
+        })
+    }
+
+    @Before
+    fun setUp() {
+        setupCameraCharacteristics()
+        setCameraXExtensionsVersion("1.3.0")
+        setExtensionRuntimeVersion("1.3.0")
+    }
+
+    private fun setupCameraCharacteristics() {
+        val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+        val shadowCharacteristics = Shadow.extract<ShadowCameraCharacteristics>(characteristics)
+        shadowCharacteristics.set(
+            CameraCharacteristics.LENS_FACING, CameraCharacteristics.LENS_FACING_BACK
+        )
+        shadowCharacteristics.set(
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, arrayOf(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
+            )
+        )
+        val cameraManager = ApplicationProvider.getApplicationContext<Context>()
+            .getSystemService(Context.CAMERA_SERVICE) as CameraManager
+        (Shadow.extract<Any>(cameraManager) as ShadowCameraManager)
+            .addCamera("0", characteristics)
+    }
+
+    private fun testSupportedCameraOperation(
+        supportedCaptureRequestKeys: List<CaptureRequest.Key<out Any>>,
+        @RestrictedCameraControl.CameraOperation expectSupportedOperations: Set<Int>
+    ) {
+        var vendorExtender: VendorExtender? = null
+        if (extenderType == "basic") {
+            val fakeImageCaptureExtenderImpl = FakeImageCaptureExtenderImpl(
+                supportedRequestKeys = supportedCaptureRequestKeys
+            )
+            vendorExtender = BasicVendorExtender(fakeImageCaptureExtenderImpl, null)
+        } else if (extenderType == "advanced") {
+            val fakeAdvancedExtenderImpl = FakeAdvancedVendorExtenderImpl(
+                supportedRequestKeys = supportedCaptureRequestKeys
+            )
+            vendorExtender = AdvancedVendorExtender(fakeAdvancedExtenderImpl)
+        }
+
+        val cameraInfo = Camera2CameraInfoImpl("0", CameraManagerCompat.from(context))
+        vendorExtender!!.init(cameraInfo)
+        val sessionProcessor = vendorExtender.createSessionProcessor(context)!!
+        assertThat(sessionProcessor.supportedCameraOperations)
+            .containsExactlyElementsIn(expectSupportedOperations)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.R)
+    @Test
+    fun supportedCameraOperations_zoomIsEnabled_androidR() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_ZOOM_RATIO
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.ZOOM
+            )
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.R)
+    @Test
+    fun supportedCameraOperations_cropregion_zoomIsEnabled_androidR() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.SCALER_CROP_REGION
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.ZOOM
+            )
+        )
+    }
+
+    @Config(maxSdk = Build.VERSION_CODES.Q)
+    @Test
+    fun supportedCameraOperations_zoomIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.SCALER_CROP_REGION
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.ZOOM
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_autoFocusIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AF_MODE,
+                CaptureRequest.CONTROL_AF_TRIGGER
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.AUTO_FOCUS
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_afRegionIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AF_REGIONS,
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.AF_REGION
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_aeRegionIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AE_REGIONS,
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.AE_REGION
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_awbRegionIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AWB_REGIONS,
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.AWB_REGION
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_torchIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AE_MODE,
+                CaptureRequest.FLASH_MODE
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.TORCH
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_flashIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AE_MODE,
+                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.FLASH
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_exposureCompensationIsEnabled() {
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION,
+            ),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.EXPOSURE_COMPENSATION
+            )
+        )
+    }
+
+    // For Basic extender under 1.3.0, ensures all operations are supported
+    @Test
+    fun supportedCameraOperations_allOperationsEnabled_basic1_2_and_below() {
+        assumeTrue(extenderType == "basic")
+        setExtensionRuntimeVersion("1.2.0")
+        setCameraXExtensionsVersion("1.3.0")
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = emptyList(),
+            expectSupportedOperations = setOf(
+                RestrictedCameraControl.ZOOM,
+                RestrictedCameraControl.AUTO_FOCUS,
+                RestrictedCameraControl.TORCH,
+                RestrictedCameraControl.AF_REGION,
+                RestrictedCameraControl.AE_REGION,
+                RestrictedCameraControl.AWB_REGION,
+                RestrictedCameraControl.EXPOSURE_COMPENSATION,
+                RestrictedCameraControl.FLASH,
+            )
+        )
+    }
+
+    @Test
+    fun supportedCameraOperations_allOperationsDisabled_advanced1_2_and_below() {
+        assumeTrue(extenderType == "advanced")
+        setExtensionRuntimeVersion("1.2.0")
+        setCameraXExtensionsVersion("1.3.0")
+        testSupportedCameraOperation(
+            supportedCaptureRequestKeys = listOf(
+                CaptureRequest.SCALER_CROP_REGION,
+                CaptureRequest.CONTROL_AF_MODE,
+                CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_REGIONS,
+                CaptureRequest.CONTROL_AE_REGIONS,
+                CaptureRequest.CONTROL_AWB_REGIONS,
+                CaptureRequest.CONTROL_AE_MODE,
+                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                CaptureRequest.FLASH_MODE,
+                CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
+            ),
+            expectSupportedOperations = emptySet() // No ops should be supported.
+        )
+    }
+
+    private class FakeImageCaptureExtenderImpl(
+        val supportedRequestKeys: List<CaptureRequest.Key<out Any>>
+    ) : ImageCaptureExtenderImpl {
+        override fun isExtensionAvailable(
+            cameraId: String,
+            cameraCharacteristics: CameraCharacteristics
+        ): Boolean = true
+        override fun init(cameraId: String, cameraCharacteristics: CameraCharacteristics) {
+        }
+        override fun getCaptureProcessor() = null
+        override fun getCaptureStages(): List<CaptureStageImpl> = emptyList()
+        override fun getMaxCaptureStage() = 2
+        override fun getSupportedResolutions() = null
+        override fun getEstimatedCaptureLatencyRange(size: Size?) = null
+        override fun getAvailableCaptureRequestKeys(): List<CaptureRequest.Key<out Any>> {
+            return supportedRequestKeys
+        }
+
+        override fun getAvailableCaptureResultKeys(): List<CaptureResult.Key<Any>> {
+            return mutableListOf()
+        }
+
+        override fun getSupportedPostviewResolutions(
+            captureSize: Size
+        ): MutableList<Pair<Int, Array<Size>>>? = null
+
+        override fun isCaptureProcessProgressAvailable() = false
+
+        override fun getRealtimeCaptureLatency(): Pair<Long, Long>? = null
+        override fun isPostviewAvailable() = false
+        override fun onInit(
+            cameraId: String,
+            cameraCharacteristics: CameraCharacteristics,
+            context: Context
+        ) {}
+
+        override fun onDeInit() {}
+        override fun onPresetSession(): CaptureStageImpl? = null
+
+        override fun onEnableSession(): CaptureStageImpl? = null
+
+        override fun onDisableSession(): CaptureStageImpl? = null
+        override fun onSessionType(): Int = SessionConfiguration.SESSION_REGULAR
+    }
+
+    private class FakeAdvancedVendorExtenderImpl(
+        val supportedRequestKeys: List<CaptureRequest.Key<out Any>>
+    ) : AdvancedExtenderImpl {
+        override fun isExtensionAvailable(
+            cameraId: String,
+            characteristicsMap: MutableMap<String, CameraCharacteristics>
+        ): Boolean = true
+
+        override fun init(
+            cameraId: String,
+            characteristicsMap: MutableMap<String, CameraCharacteristics>
+        ) {}
+        override fun getEstimatedCaptureLatencyRange(
+            cameraId: String,
+            captureOutputSize: Size?,
+            imageFormat: Int
+        ): Range<Long>? = null
+        override fun getSupportedPreviewOutputResolutions(
+            cameraId: String
+        ): Map<Int, MutableList<Size>> = emptyMap()
+        override fun getSupportedCaptureOutputResolutions(
+            cameraId: String
+        ): Map<Int, MutableList<Size>> = emptyMap()
+
+        override fun getSupportedPostviewResolutions(
+            captureSize: Size
+        ): Map<Int, MutableList<Size>> = emptyMap()
+        override fun getSupportedYuvAnalysisResolutions(cameraId: String) = null
+        override fun createSessionProcessor(): SessionProcessorImpl = DummySessionProcessorImpl()
+        override fun getAvailableCaptureRequestKeys():
+            List<CaptureRequest.Key<out Any>> = supportedRequestKeys
+
+        override fun getAvailableCaptureResultKeys(): List<CaptureResult.Key<Any>> = emptyList()
+        override fun isCaptureProcessProgressAvailable() = false
+        override fun isPostviewAvailable() = false
+    }
+
+    private class DummySessionProcessorImpl : SessionProcessorImpl {
+        override fun initSession(
+            cameraId: String,
+            cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+            context: Context,
+            surfaceConfigs: OutputSurfaceConfigurationImpl
+        ): Camera2SessionConfigImpl {
+            throw UnsupportedOperationException("Not supported")
+        }
+        override fun initSession(
+            cameraId: String,
+            cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+            context: Context,
+            previewSurfaceConfig: OutputSurfaceImpl,
+            imageCaptureSurfaceConfig: OutputSurfaceImpl,
+            imageAnalysisSurfaceConfig: OutputSurfaceImpl?
+        ): Camera2SessionConfigImpl {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun deInitSession() {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun setParameters(parameters: MutableMap<CaptureRequest.Key<*>, Any>) {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun startTrigger(
+            triggers: MutableMap<CaptureRequest.Key<*>, Any>,
+            callback: SessionProcessorImpl.CaptureCallback
+        ): Int {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun onCaptureSessionStart(requestProcessor: RequestProcessorImpl) {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun onCaptureSessionEnd() {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback): Int {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun stopRepeating() {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun startCapture(callback: SessionProcessorImpl.CaptureCallback): Int {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun startCaptureWithPostview(callback: SessionProcessorImpl.CaptureCallback): Int {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun abortCapture(captureSequenceId: Int) {
+            throw UnsupportedOperationException("Not supported")
+        }
+
+        override fun getRealtimeCaptureLatency(): Pair<Long, Long>? {
+            throw UnsupportedOperationException("Not supported")
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetrieverTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetrieverTest.kt
new file mode 100644
index 0000000..816aaf4
--- /dev/null
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/AvailableKeysRetrieverTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal.compat.workaround
+
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.SessionConfiguration
+import android.os.Build
+import android.util.Pair
+import android.util.Size
+import androidx.camera.extensions.impl.CaptureStageImpl
+import androidx.camera.extensions.impl.ImageCaptureExtenderImpl
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.util.ReflectionHelpers
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+    minSdk = Build.VERSION_CODES.LOLLIPOP,
+    instrumentedPackages = arrayOf("androidx.camera.extensions.internal")
+)
+class AvailableKeysRetrieverTest {
+    private val context: Context = RuntimeEnvironment.getApplication()
+    private val availableKeys = listOf<CaptureRequest.Key<out Any>>(
+        CaptureRequest.CONTROL_AE_REGIONS, CaptureRequest.CONTROL_AF_MODE
+    )
+    private val fakeImageCaptureExtenderImpl = FakeImageCaptureExtenderImpl(availableKeys)
+    private val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+
+    @Test
+    fun shouldInvokeOnInit() {
+        // 1. Arrange
+        ReflectionHelpers.setStaticField(Build::class.java, "BRAND", "SAMSUNG")
+        val retriever = AvailableKeysRetriever()
+
+        // 2. Act
+        val resultKeys = retriever.getAvailableCaptureRequestKeys(
+            fakeImageCaptureExtenderImpl, "0", characteristics, context)
+
+        // 3. Assert
+        assertThat(resultKeys).containsExactlyElementsIn(availableKeys)
+        assertThat(fakeImageCaptureExtenderImpl.invokeList).containsExactly(
+            "onInit", "getAvailableCaptureRequestKeys", "onDeInit"
+        )
+    }
+
+    @Test
+    fun shouldNotInvokeOnInit() {
+        // 1. Arrange
+        ReflectionHelpers.setStaticField(Build::class.java, "BRAND", "OTHER")
+        val retriever = AvailableKeysRetriever()
+
+        // 2. Act
+        val resultKeys = retriever.getAvailableCaptureRequestKeys(
+            fakeImageCaptureExtenderImpl, "0", characteristics, context)
+
+        // 3. Assert
+        assertThat(resultKeys).containsExactlyElementsIn(availableKeys)
+        assertThat(fakeImageCaptureExtenderImpl.invokeList).containsExactly(
+            "getAvailableCaptureRequestKeys"
+        )
+    }
+
+    class FakeImageCaptureExtenderImpl(
+        private var availableRequestKeys: List<CaptureRequest.Key<out Any>>
+    ) : ImageCaptureExtenderImpl {
+        val invokeList = mutableListOf<String>()
+        override fun isExtensionAvailable(
+            cameraId: String,
+            cameraCharacteristics: CameraCharacteristics
+        ): Boolean = true
+        override fun init(cameraId: String, cameraCharacteristics: CameraCharacteristics) {
+            invokeList.add("init")
+        }
+        override fun getCaptureProcessor() = null
+        override fun getCaptureStages(): List<CaptureStageImpl> = emptyList()
+        override fun getMaxCaptureStage() = 2
+        override fun getSupportedResolutions() = null
+        override fun getEstimatedCaptureLatencyRange(size: Size?) = null
+        override fun getAvailableCaptureRequestKeys(): List<CaptureRequest.Key<out Any>> {
+            invokeList.add("getAvailableCaptureRequestKeys")
+
+            return availableRequestKeys
+        }
+
+        override fun getAvailableCaptureResultKeys(): List<CaptureResult.Key<Any>> {
+            return mutableListOf()
+        }
+
+        override fun getSupportedPostviewResolutions(
+            captureSize: Size
+        ): MutableList<Pair<Int, Array<Size>>>? = null
+
+        override fun isCaptureProcessProgressAvailable() = false
+
+        override fun getRealtimeCaptureLatency(): Pair<Long, Long>? = null
+        override fun isPostviewAvailable() = false
+        override fun onInit(
+            cameraId: String,
+            cameraCharacteristics: CameraCharacteristics,
+            context: Context
+        ) {
+            invokeList.add("onInit")
+        }
+
+        override fun onDeInit() {
+            invokeList.add("onDeInit")
+        }
+        override fun onPresetSession(): CaptureStageImpl? = null
+        override fun onEnableSession(): CaptureStageImpl? = null
+        override fun onDisableSession(): CaptureStageImpl? = null
+        override fun onSessionType(): Int = SessionConfiguration.SESSION_REGULAR
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
index c032e08..73c441f 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraControl.java
@@ -23,6 +23,7 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.FocusMeteringResult;
@@ -80,6 +81,10 @@
     private float mZoomRatio = -1;
     private float mLinearZoom = -1;
     private boolean mTorchEnabled = false;
+    private int mExposureCompensation = -1;
+
+    @Nullable
+    private FocusMeteringAction mLastSubmittedFocusMeteringAction = null;
 
     public FakeCameraControl() {
         this(NO_OP_CALLBACK);
@@ -189,9 +194,14 @@
     @NonNull
     @Override
     public ListenableFuture<Integer> setExposureCompensationIndex(int exposure) {
+        mExposureCompensation = exposure;
         return Futures.immediateFuture(null);
     }
 
+    public int getExposureCompensationIndex() {
+        return mExposureCompensation;
+    }
+
     @NonNull
     @Override
     public ListenableFuture<List<Void>> submitStillCaptureRequests(
@@ -229,6 +239,7 @@
     @Override
     public ListenableFuture<FocusMeteringResult> startFocusAndMetering(
             @NonNull FocusMeteringAction action) {
+        mLastSubmittedFocusMeteringAction = action;
         return Futures.immediateFuture(FocusMeteringResult.emptyInstance());
     }
 
@@ -265,6 +276,11 @@
         return mLinearZoom;
     }
 
+    @Nullable
+    public FocusMeteringAction getLastSubmittedFocusMeteringAction() {
+        return mLastSubmittedFocusMeteringAction;
+    }
+
     @Override
     public void addInteropConfig(@NonNull Config config) {
         for (Config.Option<?> option : config.listOptions()) {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
index 6fb7552..7a4b53a 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
@@ -92,6 +92,9 @@
     private boolean mIsPrivateReprocessingSupported = false;
     private float mIntrinsicZoomRatio = 1.0F;
 
+    private boolean mIsFocusMeteringSupported = false;
+
+    private ExposureState mExposureState = new FakeExposureState();
     @NonNull
     private final List<Quirk> mCameraQuirks = new ArrayList<>();
 
@@ -117,6 +120,37 @@
         mZoomLiveData = new MutableLiveData<>(ImmutableZoomState.create(1.0f, 4.0f, 1.0f, 0.0f));
     }
 
+    /**
+     * Sets the zoom parameter.
+     */
+    public void setZoom(float zoomRatio, float minZoomRatio, float maxZoomRatio, float linearZoom) {
+        mZoomLiveData.postValue(ImmutableZoomState.create(
+                zoomRatio, maxZoomRatio, minZoomRatio, linearZoom
+        ));
+    }
+
+    /**
+     * Sets the exposure compensation parameters.
+     */
+    public void setExposureState(int index, @NonNull Range<Integer> range,
+            @NonNull Rational step, boolean isSupported) {
+        mExposureState = new FakeExposureState(index, range, step, isSupported);
+    }
+
+    /**
+     * Sets the torch state.
+     */
+    public void setTorch(int torchState) {
+        mTorchState.postValue(torchState);
+    }
+
+    /**
+     * Sets the return value for {@link #isFocusMeteringSupported(FocusMeteringAction)}.
+     */
+    public void setIsFocusMeteringSupported(boolean supported) {
+        mIsFocusMeteringSupported = supported;
+    }
+
     @Override
     public int getLensFacing() {
         return mLensFacing;
@@ -169,7 +203,7 @@
     @NonNull
     @Override
     public ExposureState getExposureState() {
-        return new FakeExposureState();
+        return mExposureState;
     }
 
     @NonNull
@@ -246,7 +280,7 @@
 
     @Override
     public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
-        return false;
+        return mIsFocusMeteringSupported;
     }
 
     @Override
@@ -317,26 +351,41 @@
 
     @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
     static final class FakeExposureState implements ExposureState {
+        private int mIndex = 0;
+        private Range<Integer> mRange = new Range<>(0, 0);
+        private Rational mStep = Rational.ZERO;
+        private boolean mIsSupported = true;
+
+        FakeExposureState() {
+        }
+        FakeExposureState(int index, Range<Integer> range,
+                Rational step, boolean isSupported) {
+            mIndex = index;
+            mRange = range;
+            mStep = step;
+            mIsSupported = isSupported;
+        }
+
         @Override
         public int getExposureCompensationIndex() {
-            return 0;
+            return mIndex;
         }
 
         @NonNull
         @Override
         public Range<Integer> getExposureCompensationRange() {
-            return Range.create(0, 0);
+            return mRange;
         }
 
         @NonNull
         @Override
         public Rational getExposureCompensationStep() {
-            return Rational.ZERO;
+            return mStep;
         }
 
         @Override
         public boolean isExposureCompensationSupported() {
-            return true;
+            return mIsSupported;
         }
     }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt
index e08f64e..ee51c7d 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeSessionProcessor.kt
@@ -33,6 +33,7 @@
 import androidx.camera.core.impl.OptionsBundle
 import androidx.camera.core.impl.OutputSurface
 import androidx.camera.core.impl.RequestProcessor
+import androidx.camera.core.impl.RestrictedCameraControl
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.SessionProcessorSurface
@@ -46,8 +47,8 @@
 
 @RequiresApi(28) // writing to PRIVATE surface requires API 28+
 class FakeSessionProcessor(
-    val inputFormatPreview: Int?,
-    val inputFormatCapture: Int?
+    val inputFormatPreview: Int? = null,
+    val inputFormatCapture: Int? = null
 ) : SessionProcessor {
     private lateinit var previewProcessorSurface: DeferrableSurface
     private lateinit var captureProcessorSurface: DeferrableSurface
@@ -77,6 +78,9 @@
     private var rotationDegrees = 0
     private var jpegQuality = 100
 
+    @RestrictedCameraControl.CameraOperation
+    var restrictedCameraOperations: Set<Int> = emptySet()
+
     fun releaseSurfaces() {
         intermediaPreviewImageReader?.close()
         intermediaCaptureImageReader?.close()
@@ -215,6 +219,11 @@
         return latestParameters
     }
 
+    @RestrictedCameraControl.CameraOperation
+    override fun getSupportedCameraOperations(): Set<Int> {
+        return restrictedCameraOperations
+    }
+
     override fun startRepeating(callback: SessionProcessor.CaptureCallback): Int {
         startRepeatingCalled.complete(SystemClock.elapsedRealtimeNanos())
         val builder = RequestProcessorRequest.Builder().apply {
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index f1af41e..0f605d3 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -173,6 +173,7 @@
     field public static final int ERROR_NONE = 0; // 0x0
     field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
     field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
     field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
     field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
diff --git a/camera/camera-video/api/public_plus_experimental_current.txt b/camera/camera-video/api/public_plus_experimental_current.txt
index f1af41e..0f605d3 100644
--- a/camera/camera-video/api/public_plus_experimental_current.txt
+++ b/camera/camera-video/api/public_plus_experimental_current.txt
@@ -173,6 +173,7 @@
     field public static final int ERROR_NONE = 0; // 0x0
     field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
     field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
     field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
     field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index f1af41e..0f605d3 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -173,6 +173,7 @@
     field public static final int ERROR_NONE = 0; // 0x0
     field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
     field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
     field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
     field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 1a32b681..6bbe1fe 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -68,6 +68,7 @@
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NO_VALID_DATA
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_RECORDER_ERROR
+import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_RECORDING_GARBAGE_COLLECTED
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
 import androidx.camera.video.VideoRecordEvent.Pause
 import androidx.camera.video.VideoRecordEvent.Resume
@@ -118,6 +119,7 @@
 private const val GENERAL_TIMEOUT = 5000L
 private const val STATUS_TIMEOUT = 15000L
 private const val TEST_ATTRIBUTION_TAG = "testAttribution"
+
 // For the file size is small, the final file length possibly exceeds the file size limit
 // after adding the file header. We still add the buffer for the tolerance of comparing the
 // file length and file size limit.
@@ -546,6 +548,7 @@
     fun checkStreamState() {
         // Arrange.
         val recorder = createRecorder()
+
         @Suppress("UNCHECKED_CAST")
         val streamInfoObserver = mock(Observer::class.java) as Observer<StreamInfo>
         val inOrder = inOrder(streamInfoObserver)
@@ -766,7 +769,9 @@
         // very overloaded here. This event means the recording has finished, but does not relate
         // to the finalizer that runs during garbage collection. However, that is what causes the
         // recording to finish.
-        listener.verifyFinalize()
+        listener.verifyFinalize { finalize ->
+            assertThat(finalize.error).isEqualTo(ERROR_RECORDING_GARBAGE_COLLECTED)
+        }
     }
 
     @Test
@@ -902,6 +907,46 @@
     }
 
     @Test
+    fun audioAmplitudeIsNoneWhenAudioIsDisabled() {
+        // Arrange.
+        val recording = createRecordingProcess(withAudio = false)
+
+        // Act.
+        recording.startAndVerify { onStatus ->
+            val amplitude = onStatus[0].recordingStats.audioStats.audioAmplitude
+            assertThat(amplitude).isEqualTo(AudioStats.AUDIO_AMPLITUDE_NONE)
+        }
+
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            val uri = finalize.outputResults.outputUri
+            checkFileHasAudioAndVideo(uri, hasAudio = false)
+            assertThat(finalize.recordingStats.audioStats.audioAmplitude)
+                .isEqualTo(AudioStats.AUDIO_AMPLITUDE_NONE)
+        }
+    }
+
+    @Test
+    fun canGetAudioStatsAmplitude() {
+        // Arrange.
+        val recording = createRecordingProcess()
+
+        // Act.
+        recording.startAndVerify { onStatus ->
+            val amplitude = onStatus[0].recordingStats.audioStats.audioAmplitude
+            assertThat(amplitude).isAtLeast(AudioStats.AUDIO_AMPLITUDE_NONE)
+        }
+
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            val uri = finalize.outputResults.outputUri
+            checkFileHasAudioAndVideo(uri, hasAudio = true)
+            assertThat(finalize.recordingStats.audioStats.audioAmplitude)
+                .isAtLeast(AudioStats.AUDIO_AMPLITUDE_NONE)
+        }
+    }
+
+    @Test
     fun cannotStartMultiplePendingRecordingsWhileInitializing() {
         // Arrange: Prepare 1st recording and start.
         val recorder = createRecorder(sendSurfaceRequest = false)
@@ -922,13 +967,13 @@
         var createEncoderRequestCount = 0
         val recorder = createRecorder(
             videoEncoderFactory = { executor, config ->
-            if (createEncoderRequestCount < 2) {
-                createEncoderRequestCount++
-                throw InvalidConfigException("Create video encoder fail on purpose.")
-            } else {
-                Recorder.DEFAULT_ENCODER_FACTORY.createEncoder(executor, config)
-            }
-        })
+                if (createEncoderRequestCount < 2) {
+                    createEncoderRequestCount++
+                    throw InvalidConfigException("Create video encoder fail on purpose.")
+                } else {
+                    Recorder.DEFAULT_ENCODER_FACTORY.createEncoder(executor, config)
+                }
+            })
         // Recorder initialization should fail by 1st encoder creation fail.
         // Wait STREAM_ID_ERROR which indicates Recorder enter the error state.
         withTimeoutOrNull(3000) {
@@ -1194,8 +1239,10 @@
             it.setDataSource(context, uri)
             // Only test on mp4 output format, others will be ignored.
             val mime = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
-            assumeTrue("Unsupported mime = $mime",
-                "video/mp4".equals(mime, ignoreCase = true))
+            assumeTrue(
+                "Unsupported mime = $mime",
+                "video/mp4".equals(mime, ignoreCase = true)
+            )
             val value = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
             assertThat(value).isNotNull()
             // ex: (90, 180) => "+90.0000+180.0000/" (ISO-6709 standard)
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java b/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
index 55404f6..7f171f5 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
@@ -19,6 +19,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 
@@ -46,8 +47,9 @@
     }
 
     @NonNull
-    static AudioStats of(@AudioState int state, @Nullable Throwable errorCause) {
-        return new AutoValue_AudioStats(state, errorCause);
+    static AudioStats of(@AudioState int state, @Nullable Throwable errorCause,
+            double audioAmplitude) {
+        return new AutoValue_AudioStats(state, audioAmplitude, errorCause);
     }
 
     /**
@@ -98,6 +100,13 @@
      */
     public static final int AUDIO_STATE_MUTED = 5;
 
+    /**
+     * Should audio recording be disabled, any attempts to retrieve the amplitude will
+     * return this value.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static final double AUDIO_AMPLITUDE_NONE = 0;
+
     @IntDef({AUDIO_STATE_ACTIVE, AUDIO_STATE_DISABLED, AUDIO_STATE_SOURCE_SILENCED,
             AUDIO_STATE_ENCODER_ERROR, AUDIO_STATE_SOURCE_ERROR, AUDIO_STATE_MUTED})
     @Retention(RetentionPolicy.SOURCE)
@@ -145,10 +154,38 @@
     public abstract int getAudioState();
 
     /**
+     * Returns the average amplitude of the most recent audio samples.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    abstract double getAudioAmplitudeInternal();
+
+    /**
      * Gets the error cause.
      *
      * <p>Returns {@code null} if {@link #hasError()} returns {@code false}.
      */
     @Nullable
     public abstract Throwable getErrorCause();
+
+    /**
+     * Returns the maximum absolute amplitude of the audio most recently sampled. Returns
+     * {@link #AUDIO_AMPLITUDE_NONE} if audio is disabled.
+     *
+     * <p>The amplitude is the maximum absolute value over all channels which the audio was
+     * most recently sampled from.
+     *
+     * <p>Amplitude is a relative measure of the maximum sound pressure/voltage range of the device
+     * microphone.
+     *
+     * <p>The amplitude value returned will be a double between {@code 0} and {@code 1}.
+     */
+    @OptIn(markerClass = ExperimentalAudioApi.class)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public double getAudioAmplitude() {
+        if (getAudioState() == AUDIO_STATE_DISABLED) {
+            return AUDIO_AMPLITUDE_NONE;
+        } else {
+            return getAudioAmplitudeInternal();
+        }
+    }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
new file mode 100644
index 0000000..bf07a6e
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
@@ -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.camera.video;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import androidx.annotation.RequiresOptIn;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Denotes that the methods on retrieving audio amplitude data are experimental and may
+ * change in a future release.
+ */
+@Retention(CLASS)
+@RequiresOptIn
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public @interface ExperimentalAudioApi {
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
index 3678cc7..a69685a 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/PendingRecording.java
@@ -157,7 +157,9 @@
      *
      * <p>If the returned {@link Recording} is garbage collected, the recording will be
      * automatically stopped. A reference to the active recording must be maintained as long as
-     * the recording needs to be active.
+     * the recording needs to be active. If the recording is garbage collected, the
+     * {@link VideoRecordEvent.Finalize} event will contain error
+     * {@link VideoRecordEvent.Finalize#ERROR_RECORDING_GARBAGE_COLLECTED}.
      *
      * @throws IllegalStateException if the associated Recorder currently has an unfinished
      * active recording.
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index 448b743..2d1811f 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -16,6 +16,7 @@
 
 package androidx.camera.video;
 
+import static androidx.camera.video.AudioStats.AUDIO_AMPLITUDE_NONE;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_ENCODING_FAILED;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED;
@@ -436,6 +437,7 @@
     @Nullable
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     VideoEncoderSession mVideoEncoderSessionToRelease = null;
+    double mAudioAmplitude = 0;
     //--------------------------------------------------------------------------------------------//
 
     Recorder(@Nullable Executor executor, @NonNull MediaSpec mediaSpec,
@@ -855,7 +857,8 @@
         }
     }
 
-    void stop(@NonNull Recording activeRecording) {
+    void stop(@NonNull Recording activeRecording, @VideoRecordError int error,
+            @Nullable Throwable errorCause) {
         RecordingRecord pendingRecordingToFinalize = null;
         synchronized (mLock) {
             if (!isSameRecording(activeRecording, mPendingRecordingRecord) && !isSameRecording(
@@ -900,7 +903,7 @@
                     long explicitlyStopTimeUs = TimeUnit.NANOSECONDS.toMicros(System.nanoTime());
                     RecordingRecord finalActiveRecordingRecord = mActiveRecordingRecord;
                     mSequentialExecutor.execute(() -> stopInternal(finalActiveRecordingRecord,
-                            explicitlyStopTimeUs, ERROR_NONE, null));
+                            explicitlyStopTimeUs, error, errorCause));
                     break;
                 case ERROR:
                     // In an error state, the recording will already be finalized. Treat as a
@@ -910,9 +913,13 @@
         }
 
         if (pendingRecordingToFinalize != null) {
+            if (error == VideoRecordEvent.Finalize.ERROR_RECORDING_GARBAGE_COLLECTED) {
+                Logger.e(TAG, "Recording was stopped due to recording being garbage collected "
+                        + "before any valid data has been produced.");
+            }
             finalizePendingRecording(pendingRecordingToFinalize, ERROR_NO_VALID_DATA,
                     new RuntimeException("Recording was stopped before any data could be "
-                            + "produced."));
+                            + "produced.", errorCause));
         }
     }
 
@@ -942,7 +949,8 @@
                         recordingToFinalize.getOutputOptions(),
                         RecordingStats.of(/*duration=*/0L,
                                 /*bytes=*/0L,
-                                AudioStats.of(AudioStats.AUDIO_STATE_DISABLED, mAudioErrorCause)),
+                                AudioStats.of(AudioStats.AUDIO_STATE_DISABLED, mAudioErrorCause,
+                                        AUDIO_AMPLITUDE_NONE)),
                         OutputResults.of(Uri.EMPTY),
                         error,
                         cause));
@@ -1685,6 +1693,11 @@
                                             audioErrorConsumer.accept(throwable);
                                         }
                                     }
+
+                                    @Override
+                                    public void onAmplitudeValue(double maxAmplitude) {
+                                        mAudioAmplitude = maxAmplitude;
+                                    }
                                 });
 
                         mAudioEncoder.setEncoderCallback(new EncoderCallback() {
@@ -2180,6 +2193,7 @@
         mRecordingStopError = ERROR_UNKNOWN;
         mRecordingStopErrorCause = null;
         mAudioErrorCause = null;
+        mAudioAmplitude = AUDIO_AMPLITUDE_NONE;
         clearPendingAudioRingBuffer();
 
         switch (mAudioState) {
@@ -2478,7 +2492,8 @@
     @NonNull
     RecordingStats getInProgressRecordingStats() {
         return RecordingStats.of(mRecordingDurationNs, mRecordingBytes,
-                AudioStats.of(internalAudioStateToAudioStatsState(mAudioState), mAudioErrorCause));
+                AudioStats.of(internalAudioStateToAudioStatsState(mAudioState), mAudioErrorCause,
+                        mAudioAmplitude));
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recording.java b/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
index 1105731..84f9eb2 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recording.java
@@ -19,6 +19,7 @@
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.camera.core.impl.utils.CloseGuardHelper;
@@ -193,11 +194,7 @@
      */
     @Override
     public void close() {
-        mCloseGuard.close();
-        if (mIsClosed.getAndSet(true)) {
-            return;
-        }
-        mRecorder.stop(this);
+        stopWithError(VideoRecordEvent.Finalize.ERROR_NONE, /*errorCause=*/ null);
     }
 
     @Override
@@ -205,7 +202,8 @@
     protected void finalize() throws Throwable {
         try {
             mCloseGuard.warnIfOpen();
-            stop();
+            stopWithError(VideoRecordEvent.Finalize.ERROR_RECORDING_GARBAGE_COLLECTED,
+                    new RuntimeException("Recording stopped due to being garbage collected."));
         } finally {
             super.finalize();
         }
@@ -231,4 +229,13 @@
     public boolean isClosed() {
         return mIsClosed.get();
     }
+
+    private void stopWithError(@VideoRecordEvent.Finalize.VideoRecordError int error,
+            @Nullable Throwable errorCause) {
+        mCloseGuard.close();
+        if (mIsClosed.getAndSet(true)) {
+            return;
+        }
+        mRecorder.stop(this, error, errorCause);
+    }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java
index 507f856..7549f7f 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoRecordEvent.java
@@ -332,6 +332,18 @@
         public static final int ERROR_DURATION_LIMIT_REACHED = 9;
 
         /**
+         * The recording was stopped because the {@link Recording} object was garbage collected.
+         *
+         * <p>The {@link Recording} object returned by
+         * {@link PendingRecording#start(Executor, Consumer)} must be referenced until the
+         * recording is no longer needed. If it is not, the active recording will be stopped and
+         * this error will be produced. Once {@link Recording#stop()} or
+         * {@link Recording#close()} has been invoked, the recording object no longer needs to be
+         * referenced.
+         */
+        public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10;
+
+        /**
          * Describes the error that occurred during a video recording.
          *
          * <p>This is the error code returning from {@link Finalize#getError()}.
@@ -342,7 +354,7 @@
         @IntDef(value = {ERROR_NONE, ERROR_UNKNOWN, ERROR_FILE_SIZE_LIMIT_REACHED,
                 ERROR_INSUFFICIENT_STORAGE, ERROR_INVALID_OUTPUT_OPTIONS, ERROR_ENCODING_FAILED,
                 ERROR_RECORDER_ERROR, ERROR_NO_VALID_DATA, ERROR_SOURCE_INACTIVE,
-                ERROR_DURATION_LIMIT_REACHED})
+                ERROR_DURATION_LIMIT_REACHED, ERROR_RECORDING_GARBAGE_COLLECTED})
         public @interface VideoRecordError {
         }
 
@@ -418,6 +430,7 @@
                 case ERROR_NO_VALID_DATA: return "ERROR_NO_VALID_DATA";
                 case ERROR_SOURCE_INACTIVE: return "ERROR_SOURCE_INACTIVE";
                 case ERROR_DURATION_LIMIT_REACHED: return "ERROR_DURATION_LIMIT_REACHED";
+                case ERROR_RECORDING_GARBAGE_COLLECTED: return "ERROR_RECORDING_GARBAGE_COLLECTED";
             }
 
             // Should never reach here, but just in case...
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java
index e7e96db..74035f5 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/AudioSource.java
@@ -27,6 +27,7 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.media.AudioFormat;
 import android.media.AudioRecord;
 
 import androidx.annotation.NonNull;
@@ -47,6 +48,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -140,6 +142,10 @@
     boolean mMuted;
     @Nullable
     private byte[] mZeroBytes;
+    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+            double mAudioAmplitude;
+    long mAmplitudeTimestamp = 0;
+    private final int mAudioFormat;
 
     /**
      * Creates an AudioSource for the given settings.
@@ -186,6 +192,7 @@
         }
         mAudioStream.setCallback(new AudioStreamCallback(), mExecutor);
         mSilentAudioStream = new SilentAudioStream(settings);
+        mAudioFormat = settings.getAudioFormat();
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
@@ -449,6 +456,13 @@
                         if (mMuted) {
                             overrideBySilence(byteBuffer, packetInfo.getSizeInBytes());
                         }
+                        // should only be ENCODING_PCM_16BIT for now at least
+                        // reads incoming bytebuffer for amplitude value every .2 seconds
+                        if (mCallbackExecutor != null
+                                && (packetInfo.getTimestampNs() - mAmplitudeTimestamp) >= 200) {
+                            mAmplitudeTimestamp = packetInfo.getTimestampNs();
+                            postMaxAmplitude(byteBuffer);
+                        }
                         byteBuffer.limit(byteBuffer.position() + packetInfo.getSizeInBytes());
                         inputBuffer.setPresentationTimeUs(
                                 NANOSECONDS.toMicros(packetInfo.getTimestampNs()));
@@ -618,6 +632,31 @@
         mState = state;
     }
 
+    void postMaxAmplitude(ByteBuffer byteBuffer) {
+        Executor executor = mCallbackExecutor;
+        AudioSourceCallback callback = mAudioSourceCallback;
+        double maxAmplitude = 0;
+
+        if (mAudioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+            //TODO
+            // may want to add calculation for different audio formats
+            ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
+
+            while (shortBuffer.hasRemaining()) {
+                maxAmplitude = Math.max(maxAmplitude, Math.abs(shortBuffer.get()));
+            }
+
+            maxAmplitude = maxAmplitude / Short.MAX_VALUE;
+
+            mAudioAmplitude = maxAmplitude;
+
+            if (executor != null && callback != null) {
+                executor.execute(() -> callback.onAmplitudeValue(mAudioAmplitude));
+            }
+        }
+    }
+
+
     @Nullable
     private static BufferProvider.State fetchBufferProviderState(
             @NonNull BufferProvider<? extends InputBuffer> bufferProvider) {
@@ -666,5 +705,10 @@
          * The method called when the audio source encountered errors.
          */
         void onError(@NonNull Throwable t);
+
+        /**
+         * The method called to retrieve audio amplitude values.
+         */
+        void onAmplitudeValue(double maxAmplitude);
     }
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/CameraUseInconsistentTimebaseQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/CameraUseInconsistentTimebaseQuirk.java
index 63e0121..0dc036e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/CameraUseInconsistentTimebaseQuirk.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/CameraUseInconsistentTimebaseQuirk.java
@@ -24,15 +24,18 @@
 import androidx.camera.video.internal.workaround.VideoTimebaseConverter;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
 /**
  * <p>QuirkSummary
- *     Bug Id: 197805856
- *     Description: Quirk that denotes some Samsung devices use inconsistent timebase for camera
- *                  frame.
- *     Device(s): Some Samsung devices
+ *     Bug Id: 197805856, 280121263
+ *     Description: Quirk that denotes some devices use a timebase for camera frames that is
+ *                  different than what is reported by
+ *                  {@link android.hardware.camera2.CameraCharacteristics
+ *                  #SENSOR_INFO_TIMESTAMP_SOURCE}. This can cause A/V sync issues.
+ *     Device(s): Some Samsung devices and devices running on certain Qualcomm SoCs
  *     @see VideoTimebaseConverter
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@@ -43,7 +46,20 @@
             "qcom"
     ));
 
+    private static final Set<String> BUILD_SOC_MODEL_SET = new HashSet<>(Collections.singletonList(
+            "sm6375"
+    ));
+
     static boolean load() {
+        return usesAffectedSoc() || isAffectedSamsungDevice();
+    }
+
+    private static boolean usesAffectedSoc() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+                && BUILD_SOC_MODEL_SET.contains(Build.SOC_MODEL.toLowerCase());
+    }
+
+    private static boolean isAffectedSamsungDevice() {
         return "SAMSUNG".equalsIgnoreCase(Build.BRAND)
                 && BUILD_HARDWARE_SET.contains(Build.HARDWARE.toLowerCase());
     }
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
index 33cc6eb..6421b0fc 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoRecordEventTest.kt
@@ -32,7 +32,7 @@
 private const val INVALID_FILE_PATH = "/invalid/file/path"
 private val TEST_OUTPUT_OPTION = FileOutputOptions.Builder(File(INVALID_FILE_PATH)).build()
 private val TEST_RECORDING_STATE =
-    RecordingStats.of(0, 0, AudioStats.of(AudioStats.AUDIO_STATE_ACTIVE, null))
+    RecordingStats.of(0, 0, AudioStats.of(AudioStats.AUDIO_STATE_ACTIVE, null, 0.0))
 private val TEST_OUTPUT_RESULT = OutputResults.of(Uri.EMPTY)
 
 @RunWith(RobolectricTestRunner::class)
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/FakeAudioSourceCallback.kt b/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/FakeAudioSourceCallback.kt
index 62aa19c5..2ae5a4d 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/FakeAudioSourceCallback.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/FakeAudioSourceCallback.kt
@@ -25,6 +25,7 @@
     private val onSuspendedCallbacks = MockConsumer<Boolean>()
     private val onSilencedCallbacks = MockConsumer<Boolean>()
     private val onErrorCallbacks = MockConsumer<Throwable>()
+    private val onAmplitudeCallbacks = MockConsumer<Double>()
 
     override fun onSuspendStateChanged(suspended: Boolean) {
         onSuspendedCallbacks.accept(suspended)
@@ -38,6 +39,10 @@
         onErrorCallbacks.accept(error)
     }
 
+    override fun onAmplitudeValue(maxAmplitude: Double) {
+        onAmplitudeCallbacks.accept(maxAmplitude)
+    }
+
     fun verifyOnSuspendStateChanged(
         callTimes: CallTimes,
         timeoutMs: Long = NO_TIMEOUT,
diff --git a/camera/integration-tests/avsynctestapp/build.gradle b/camera/integration-tests/avsynctestapp/build.gradle
index 0f4683f..bba9ab2 100644
--- a/camera/integration-tests/avsynctestapp/build.gradle
+++ b/camera/integration-tests/avsynctestapp/build.gradle
@@ -47,13 +47,13 @@
     implementation(project(":camera:camera-video"))
 
     // Compose
-    def compose_version = "1.1.1"
+    def compose_version = "1.4.0"
     implementation("androidx.activity:activity-compose:1.5.0-alpha03")
     implementation("androidx.compose.material:material:$compose_version")
     implementation("androidx.compose.ui:ui:$compose_version")
     implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
-    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1")
-    implementation(project(":lifecycle:lifecycle-viewmodel"))
+    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
 
     compileOnly(libs.kotlinCompiler)
 
@@ -62,7 +62,7 @@
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     androidTestImplementation(project(":camera:camera-testing"))
-    androidTestImplementation(project(":lifecycle:lifecycle-viewmodel"))
+    androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
     androidTestImplementation(libs.kotlinCoroutinesTest)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRules)
diff --git a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
index 920268e..09c42a4 100644
--- a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
+++ b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/CameraPipeActivity.kt
@@ -85,11 +85,13 @@
     override fun onResume() {
         super.onResume()
         Log.i("CXCP-App", "Activity onResume")
+        currentCamera?.resume()
     }
 
     override fun onPause() {
         super.onPause()
         Log.i("CXCP-App", "Activity onPause")
+        currentCamera?.pause()
     }
 
     override fun onStop() {
@@ -139,7 +141,8 @@
             for (id in cameras) {
                 val metadata = cameraPipe.cameras().getCameraMetadata(id)
                 if (metadata != null && metadata[CameraCharacteristics.LENS_FACING] ==
-                    CameraCharacteristics.LENS_FACING_BACK) {
+                    CameraCharacteristics.LENS_FACING_BACK
+                ) {
                     return id
                 }
             }
diff --git a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt
index e9a7863..e852046 100644
--- a/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt
+++ b/camera/integration-tests/camerapipetestapp/src/main/java/androidx/camera/integration/camera2/pipe/SimpleCamera.kt
@@ -332,6 +332,14 @@
         cameraGraph.start()
     }
 
+    fun resume() {
+        cameraGraph.isForeground = true
+    }
+
+    fun pause() {
+        cameraGraph.isForeground = false
+    }
+
     fun stop() {
         Log.i("CXCP-App", "Stopping $cameraGraph")
         cameraGraph.stop()
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index 8b4532c..dfa51fa 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -89,13 +89,13 @@
 
     // Compose
     implementation 'androidx.activity:activity-compose:1.4.0'
-    implementation 'androidx.compose.material:material:1.1.1'
-    implementation 'androidx.compose.animation:animation:1.1.1'
-    implementation 'androidx.compose.runtime:runtime:1.1.1'
-    implementation 'androidx.compose.ui:ui-tooling:1.1.1'
+    implementation 'androidx.compose.material:material:1.4.0'
+    implementation 'androidx.compose.animation:animation:1.4.0'
+    implementation 'androidx.compose.runtime:runtime:1.4.0'
+    implementation 'androidx.compose.ui:ui-tooling:1.4.0'
     implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
     implementation 'androidx.navigation:navigation-compose:2.4.2'
-    implementation 'androidx.compose.material:material-icons-extended:1.1.1'
+    implementation 'androidx.compose.material:material-icons-extended:1.4.0'
     androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
 
     // Testing framework
diff --git a/camera/integration-tests/viewtestapp/build.gradle b/camera/integration-tests/viewtestapp/build.gradle
index e20e292..6903dea 100644
--- a/camera/integration-tests/viewtestapp/build.gradle
+++ b/camera/integration-tests/viewtestapp/build.gradle
@@ -83,9 +83,9 @@
 
     // Compose UI
     implementation(project(":compose:runtime:runtime"))
-    implementation("androidx.compose.ui:ui:1.0.5")
-    implementation("androidx.compose.material:material:1.0.5")
-    implementation("androidx.compose.ui:ui-tooling:1.0.5")
+    implementation("androidx.compose.ui:ui:1.4.0")
+    implementation("androidx.compose.material:material:1.4.0")
+    implementation("androidx.compose.ui:ui-tooling:1.4.0")
     implementation("androidx.activity:activity-compose:1.3.1")
 
     // Testing framework
diff --git a/car/app/app-automotive/src/main/res/values-nl/strings.xml b/car/app/app-automotive/src/main/res/values-nl/strings.xml
index a622f67..d1c0cbd 100644
--- a/car/app/app-automotive/src/main/res/values-nl/strings.xml
+++ b/car/app/app-automotive/src/main/res/values-nl/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="error_action_finish" msgid="7621130025103996211">"App sluiten"</string>
-    <string name="error_action_update_host" msgid="4802951804749609593">"Checken op updates"</string>
+    <string name="error_action_update_host" msgid="4802951804749609593">"Controleren op updates"</string>
     <string name="error_action_retry" msgid="985347670495166517">"Opnieuw proberen"</string>
     <string name="error_message_client_side_error" msgid="3323186720368387787">"App-fout. Meld deze fout aan de app-ontwikkelaar."</string>
     <string name="error_message_host_error" msgid="5484419926049675696">"Systeemfout"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
index 81e17cb..f5d2c1f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
@@ -243,7 +243,7 @@
     <string name="sign_in_template_not_supported_text" msgid="7184733753948837646">"Host ne podržava šablon za prijavljivanje"</string>
     <string name="sign_in_template_not_supported_title" msgid="4892883228898541764">"Nekompatibilan host"</string>
     <string name="email_hint" msgid="7205549445477319606">"Imejl"</string>
-    <string name="sign_in_title" msgid="4551967308262681703">"Prijavite se"</string>
+    <string name="sign_in_title" msgid="4551967308262681703">"Prijavi me"</string>
     <string name="sign_in_instructions" msgid="9044850228284294762">"Unesite akreditive"</string>
     <string name="invalid_email_error_msg" msgid="5261362663718987167">"Korisničko ime mora da bude važeća imejl adresa"</string>
     <string name="invalid_length_error_msg" msgid="8238905276326976425">"Najmanji broj znakova u korisničkom imenu je %s"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
index 7b4b837..eb4dc26 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
@@ -243,7 +243,7 @@
     <string name="sign_in_template_not_supported_text" msgid="7184733753948837646">"Хост не подржава шаблон за пријављивање"</string>
     <string name="sign_in_template_not_supported_title" msgid="4892883228898541764">"Некомпатибилан хост"</string>
     <string name="email_hint" msgid="7205549445477319606">"Имејл"</string>
-    <string name="sign_in_title" msgid="4551967308262681703">"Пријавите се"</string>
+    <string name="sign_in_title" msgid="4551967308262681703">"Пријави ме"</string>
     <string name="sign_in_instructions" msgid="9044850228284294762">"Унесите акредитиве"</string>
     <string name="invalid_email_error_msg" msgid="5261362663718987167">"Корисничко име мора да буде важећа имејл адреса"</string>
     <string name="invalid_length_error_msg" msgid="8238905276326976425">"Најмањи број знакова у корисничком имену је %s"</string>
diff --git a/compose/compiler/compiler-hosted/integration-tests/build.gradle b/compose/compiler/compiler-hosted/integration-tests/build.gradle
index 0ac6b78..b874154 100644
--- a/compose/compiler/compiler-hosted/integration-tests/build.gradle
+++ b/compose/compiler/compiler-hosted/integration-tests/build.gradle
@@ -28,6 +28,7 @@
 dependencies {
     implementation(libs.kotlinStdlib)
     testImplementation(libs.junit)
+    testImplementation(libs.kotlinMetadataJvm)
     testImplementation(libs.robolectric)
 
     testCompileOnly(libs.kotlinCompiler)
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt
index 9f65d26..16ae41f 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/CodegenMetadataTests.kt
@@ -17,6 +17,7 @@
 package androidx.compose.compiler.plugins.kotlin
 
 import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.junit.Assert.assertEquals
 import org.junit.Test
 
 class CodegenMetadataTests : AbstractLoweringTests() {
@@ -41,4 +42,54 @@
         val main = loader.loadClass("Main").methods.single { it.name == "main" }
         main.invoke(null)
     }
+
+    @Test
+    fun testDelegatedProperties() {
+        val className = "Test_${uniqueNumber++}"
+        val fileName = "$className.kt"
+        val loader = classLoader(
+            """
+            import androidx.compose.runtime.Composable
+            import kotlin.reflect.KProperty
+            import kotlinx.metadata.jvm.KotlinClassMetadata
+            import kotlinx.metadata.jvm.localDelegatedProperties
+
+            inline operator fun String.getValue(thisRef: Any?, property: KProperty<*>) = 0
+
+            @Composable
+            inline operator fun Int.getValue(thisRef: Any?, property: KProperty<*>) = 0
+
+            object Main {
+                @JvmStatic
+                fun main(): List<String> {
+                    val metadataAnnotation = Main::class.java
+                        .annotations
+                        .filter { it.annotationClass.qualifiedName == "kotlin.Metadata" }
+                        .single() as Metadata
+
+                    val cls = (
+                        KotlinClassMetadata.read(metadataAnnotation) as KotlinClassMetadata.Class
+                    ).toKmClass()
+                    return cls.localDelegatedProperties.map { it.name }
+                }
+
+                fun test() {
+                    val foo by ""
+                    println(foo)
+                }
+
+                @Composable fun ComposableTest(value: Int) {
+                    val fooComposable by value
+                    ComposableTest(fooComposable)
+                }
+            }
+            """,
+            fileName,
+            false
+        )
+        val main = loader.loadClass("Main").methods.single { it.name == "main" }
+        val delegates = main.invoke(null)
+
+        assertEquals(delegates, listOf("foo", "fooComposable"))
+    }
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
index 9731b2f..5b3aa04 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TargetAnnotationsTransformTests.kt
@@ -1059,7 +1059,7 @@
             }
             BasicText(AnnotatedString(
               text = "Some text"
-            ), null, null, null, <unsafe-coerce>(0), false, 0, 0, null, %composer, 0b0110, 0b000111111110)
+            ), null, null, null, <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0b0110, 0b001111111110)
             if (isTraceInProgress()) {
               traceEventEnd()
             }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
index 96672ff..016b6e8 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableTypeRemapper.kt
@@ -32,6 +32,7 @@
 import org.jetbrains.kotlin.ir.declarations.IrField
 import org.jetbrains.kotlin.ir.declarations.IrFile
 import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty
 import org.jetbrains.kotlin.ir.declarations.IrMetadataSourceOwner
 import org.jetbrains.kotlin.ir.declarations.IrProperty
 import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -187,6 +188,13 @@
         return super.visitConstructorCall(expression)
     }
 
+    override fun visitLocalDelegatedProperty(
+        declaration: IrLocalDelegatedProperty
+    ): IrLocalDelegatedProperty =
+        super.visitLocalDelegatedProperty(declaration).apply {
+            metadata = declaration.metadata
+        }
+
     private fun IrFunction.needsComposableRemapping(): Boolean {
         if (
             needsComposableRemapping(dispatchReceiverParameter?.type) ||
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt
index 9c6fce4..6143aff 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/DecoyTransformBase.kt
@@ -24,9 +24,16 @@
 import org.jetbrains.kotlin.ir.IrElement
 import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
 import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
+import org.jetbrains.kotlin.ir.declarations.IrClass
+import org.jetbrains.kotlin.ir.declarations.IrConstructor
 import org.jetbrains.kotlin.ir.declarations.IrDeclaration
 import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
+import org.jetbrains.kotlin.ir.declarations.IrField
+import org.jetbrains.kotlin.ir.declarations.IrFile
 import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty
+import org.jetbrains.kotlin.ir.declarations.IrProperty
+import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
 import org.jetbrains.kotlin.ir.expressions.IrConst
 import org.jetbrains.kotlin.ir.expressions.IrExpression
 import org.jetbrains.kotlin.ir.expressions.IrVararg
@@ -43,6 +50,8 @@
 import org.jetbrains.kotlin.ir.util.DeepCopyIrTreeWithSymbols
 import org.jetbrains.kotlin.ir.util.DeepCopyTypeRemapper
 import org.jetbrains.kotlin.ir.util.IdSignature
+import org.jetbrains.kotlin.ir.util.SymbolRemapper
+import org.jetbrains.kotlin.ir.util.SymbolRenamer
 import org.jetbrains.kotlin.ir.util.TypeRemapper
 import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
 import org.jetbrains.kotlin.ir.util.getAnnotation
@@ -198,7 +207,7 @@
     } ?: false
 }
 
-inline fun <reified T : IrElement> T.copyWithNewTypeParams(
+internal inline fun <reified T : IrElement> T.copyWithNewTypeParams(
     source: IrFunction,
     target: IrFunction
 ): T {
@@ -208,9 +217,55 @@
                 return typeRemapper.remapType(type.remapTypeParameters(source, target))
             }
         }
-
-        val deepCopy = DeepCopyIrTreeWithSymbols(symbolRemapper, typeParamRemapper)
+        val deepCopy = DeepCopySavingMetadata(
+            symbolRemapper,
+            typeParamRemapper,
+            SymbolRenamer.DEFAULT
+        )
         (typeRemapper as? DeepCopyTypeRemapper)?.deepCopy = deepCopy
         deepCopy
     }
+}
+
+internal class DeepCopySavingMetadata(
+    symbolRemapper: SymbolRemapper,
+    typeRemapper: TypeRemapper,
+    symbolRenamer: SymbolRenamer
+) : DeepCopyIrTreeWithSymbols(symbolRemapper, typeRemapper, symbolRenamer) {
+    override fun visitFile(declaration: IrFile): IrFile =
+        super.visitFile(declaration).apply {
+            metadata = declaration.metadata
+        }
+
+    override fun visitClass(declaration: IrClass): IrClass =
+        super.visitClass(declaration).apply {
+            metadata = declaration.metadata
+        }
+
+    override fun visitConstructor(declaration: IrConstructor): IrConstructor =
+        super.visitConstructor(declaration).apply {
+            metadata = declaration.metadata
+        }
+
+    override fun visitSimpleFunction(declaration: IrSimpleFunction): IrSimpleFunction =
+        super.visitSimpleFunction(declaration).apply {
+            metadata = declaration.metadata
+        }
+
+    override fun visitProperty(declaration: IrProperty): IrProperty =
+        super.visitProperty(declaration).apply {
+            metadata = declaration.metadata
+        }
+
+    override fun visitField(declaration: IrField): IrField =
+        super.visitField(declaration).apply {
+            metadata = declaration.metadata
+        }
+
+    override fun visitLocalDelegatedProperty(
+        declaration: IrLocalDelegatedProperty
+    ): IrLocalDelegatedProperty =
+        super.visitLocalDelegatedProperty(declaration).apply {
+            metadata = declaration.metadata
+        }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
index 60e3434..338f910 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Padding.kt
@@ -131,7 +131,7 @@
  * @sample androidx.compose.foundation.layout.samples.PaddingValuesModifier
  */
 @Stable
-fun Modifier.padding(paddingValues: PaddingValues) = this then PaddingValuesModifierElement(
+fun Modifier.padding(paddingValues: PaddingValues) = this then PaddingValuesElement(
     paddingValues = paddingValues,
     inspectorInfo = {
         name = "padding"
@@ -408,7 +408,7 @@
     }
 }
 
-private class PaddingValuesModifierElement(
+private class PaddingValuesElement(
     val paddingValues: PaddingValues,
     val inspectorInfo: InspectorInfo.() -> Unit
 ) : ModifierNodeElement<PaddingValuesModifier>() {
@@ -423,8 +423,8 @@
     override fun hashCode(): Int = paddingValues.hashCode()
 
     override fun equals(other: Any?): Boolean {
-        val otherModifierElement = other as? PaddingValuesModifierElement ?: return false
-        return paddingValues == otherModifierElement.paddingValues
+        val otherElement = other as? PaddingValuesElement ?: return false
+        return paddingValues == otherElement.paddingValues
     }
 
     override fun InspectorInfo.inspectableProperties() {
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index feba6f7..393276e 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -1,3 +1,7 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridItemInfo#getContentType():
+    Added method androidx.compose.foundation.lazy.grid.LazyGridItemInfo.getContentType()
+
+
 InvalidNullConversion: androidx.compose.foundation.MutatorMutex#mutateWith(T, androidx.compose.foundation.MutatePriority, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?>, kotlin.coroutines.Continuation<? super R>) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter receiver in androidx.compose.foundation.MutatorMutex.mutateWith(T receiver, androidx.compose.foundation.MutatePriority priority, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> arg4)
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index c78e700..f59ada3 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -403,10 +403,12 @@
   }
 
   public interface LazyListItemInfo {
+    method public default Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public int getOffset();
     method public int getSize();
+    property public default Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract int offset;
@@ -514,12 +516,14 @@
 
   public sealed interface LazyGridItemInfo {
     method public int getColumn();
+    method public Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public long getOffset();
     method public int getRow();
     method public long getSize();
     property public abstract int column;
+    property public abstract Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract long offset;
@@ -624,11 +628,13 @@
   }
 
   public sealed interface LazyStaggeredGridItemInfo {
+    method public Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public int getLane();
     method public long getOffset();
     method public long getSize();
+    property public abstract Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract int lane;
@@ -860,10 +866,14 @@
   }
 
   public final class BasicTextKt {
-    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines);
-    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent);
+    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional androidx.compose.ui.graphics.ColorLambda? color);
+    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional kotlin.jvm.functions.Function0<? extends androidx.compose.ui.graphics.Brush>? brush, optional androidx.compose.ui.unit.FloatLambda? alpha);
+    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional androidx.compose.ui.graphics.ColorLambda? color);
+    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function0<? extends androidx.compose.ui.graphics.Brush>? brush, optional androidx.compose.ui.unit.FloatLambda? alpha);
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines);
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,? extends androidx.compose.foundation.text.InlineTextContent> inlineContent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines);
+    method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,? extends androidx.compose.foundation.text.InlineTextContent> inlineContent);
   }
 
   public final class ClickableTextKt {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index e6a133d..c959cca 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -537,10 +537,12 @@
   }
 
   public interface LazyListItemInfo {
+    method public default Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public int getOffset();
     method public int getSize();
+    property public default Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract int offset;
@@ -650,12 +652,14 @@
 
   public sealed interface LazyGridItemInfo {
     method public int getColumn();
+    method public Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public long getOffset();
     method public int getRow();
     method public long getSize();
     property public abstract int column;
+    property public abstract Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract long offset;
@@ -868,11 +872,13 @@
   }
 
   public sealed interface LazyStaggeredGridItemInfo {
+    method public Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public int getLane();
     method public long getOffset();
     method public long getSize();
+    property public abstract Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract int lane;
@@ -1208,10 +1214,14 @@
   }
 
   public final class BasicTextKt {
-    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines);
-    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent);
+    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional androidx.compose.ui.graphics.ColorLambda? color);
+    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional kotlin.jvm.functions.Function0<? extends androidx.compose.ui.graphics.Brush>? brush, optional androidx.compose.ui.unit.FloatLambda? alpha);
+    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional androidx.compose.ui.graphics.ColorLambda? color);
+    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function0<? extends androidx.compose.ui.graphics.Brush>? brush, optional androidx.compose.ui.unit.FloatLambda? alpha);
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines);
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,? extends androidx.compose.foundation.text.InlineTextContent> inlineContent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines);
+    method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,? extends androidx.compose.foundation.text.InlineTextContent> inlineContent);
   }
 
   public final class ClickableTextKt {
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index feba6f7..393276e 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -1,3 +1,7 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridItemInfo#getContentType():
+    Added method androidx.compose.foundation.lazy.grid.LazyGridItemInfo.getContentType()
+
+
 InvalidNullConversion: androidx.compose.foundation.MutatorMutex#mutateWith(T, androidx.compose.foundation.MutatePriority, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?>, kotlin.coroutines.Continuation<? super R>) parameter #0:
     Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter receiver in androidx.compose.foundation.MutatorMutex.mutateWith(T receiver, androidx.compose.foundation.MutatePriority priority, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R> arg4)
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index c78e700..f59ada3 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -403,10 +403,12 @@
   }
 
   public interface LazyListItemInfo {
+    method public default Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public int getOffset();
     method public int getSize();
+    property public default Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract int offset;
@@ -514,12 +516,14 @@
 
   public sealed interface LazyGridItemInfo {
     method public int getColumn();
+    method public Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public long getOffset();
     method public int getRow();
     method public long getSize();
     property public abstract int column;
+    property public abstract Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract long offset;
@@ -624,11 +628,13 @@
   }
 
   public sealed interface LazyStaggeredGridItemInfo {
+    method public Object? getContentType();
     method public int getIndex();
     method public Object getKey();
     method public int getLane();
     method public long getOffset();
     method public long getSize();
+    property public abstract Object? contentType;
     property public abstract int index;
     property public abstract Object key;
     property public abstract int lane;
@@ -860,10 +866,14 @@
   }
 
   public final class BasicTextKt {
-    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines);
-    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent);
+    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional androidx.compose.ui.graphics.ColorLambda? color);
+    method @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional kotlin.jvm.functions.Function0<? extends androidx.compose.ui.graphics.Brush>? brush, optional androidx.compose.ui.unit.FloatLambda? alpha);
+    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional androidx.compose.ui.graphics.ColorLambda? color);
+    method @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function0<? extends androidx.compose.ui.graphics.Brush>? brush, optional androidx.compose.ui.unit.FloatLambda? alpha);
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines);
     method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,? extends androidx.compose.foundation.text.InlineTextContent> inlineContent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(String text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines);
+    method @Deprecated @androidx.compose.runtime.Composable public static void BasicText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,? extends kotlin.Unit>? onTextLayout, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional java.util.Map<java.lang.String,? extends androidx.compose.foundation.text.InlineTextContent> inlineContent);
   }
 
   public final class ClickableTextKt {
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/lazyhosted/TextLazyReuse.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/lazyhosted/TextLazyReuse.kt
index e0f379e..f6b097b 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/lazyhosted/TextLazyReuse.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/lazyhosted/TextLazyReuse.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalComposeUiApi::class)
-
 package androidx.compose.foundation.benchmark.text.lazyhosted
 
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -30,9 +28,7 @@
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,8 +46,7 @@
     private var reuseKey = mutableStateOf(0)
 
     private val style = TextStyle.Default.copy(
-        fontFamily = FontFamily.Monospace,
-        color = Color.Red,
+        fontFamily = FontFamily.Monospace
     )
 
     @Composable
@@ -61,7 +56,6 @@
                 Text(
                     toggleText.value,
                     style = style,
-                    color = style.color, /* for now, ignore color merge allocs */
                     modifier = Modifier.fillMaxWidth()
                 )
             }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt
index 1603fdc..5e2b764 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt
@@ -35,7 +35,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
-private val style = TextStyle(color = Color.Magenta)
+private val style = TextStyle.Default
 /**
  * These demos are for using the memory profiler to observe initial compo and recompo memory
  * pressure.
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextAnimationDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextAnimationDemo.kt
index 018fda0..347098b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextAnimationDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextAnimationDemo.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.foundation.demos.text
 
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.InfiniteRepeatableSpec
 import androidx.compose.animation.core.RepeatMode
 import androidx.compose.animation.core.animateFloat
 import androidx.compose.animation.core.infiniteRepeatable
@@ -23,14 +26,18 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.material.LocalTextStyle
 import androidx.compose.material.RadioButton
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -38,10 +45,18 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextMotion
+import androidx.compose.ui.text.withStyle
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
@@ -62,6 +77,75 @@
         }
     }
 }
+@Composable
+fun TextColorAnimation() {
+    val anim = rememberInfiniteTransition("slow animation")
+    val color: State<Color> = anim.animateColor(
+        initialValue = Color.Black,
+        targetValue = Color.Gray,
+        animationSpec = InfiniteRepeatableSpec(
+            tween(
+                5_000,
+                50,
+                CubicBezierEasing(0.2f, 0.0f, 0.5f, 0.6f)
+            ),
+            repeatMode = RepeatMode.Reverse
+        ),
+        label = "slow gray"
+    )
+    Box(contentAlignment = Alignment.Center) {
+        Column(horizontalAlignment = Alignment.CenterHorizontally) {
+            BasicText(
+                "This text has animated color",
+                style = TextStyle.Default.copy(fontSize = 45.sp, textAlign = TextAlign.Center),
+                color = { color.value }
+            )
+            BasicText(
+                buildAnnotatedString {
+                    append("So does ")
+                    withStyle(SpanStyle(fontWeight = FontWeight.Black)) {
+                        append("this")
+                    }
+                },
+                style = TextStyle.Default.copy(fontSize = 30.sp, textAlign = TextAlign.Center),
+                color = { color.value },
+                modifier = Modifier.padding(top = 16.dp)
+            )
+        }
+    }
+}
+
+@Composable
+fun TextGradientAnimation() {
+    val anim = rememberInfiniteTransition("gradient")
+    val offset = anim.animateFloat(
+        initialValue = 0.0f,
+        targetValue = 0.8f,
+        animationSpec = InfiniteRepeatableSpec(
+            animation = tween(
+                durationMillis = 4000,
+                delayMillis = 500,
+                easing = CubicBezierEasing(0.2f, 0.0f, 0.5f, 1.0f)
+            ),
+            repeatMode = RepeatMode.Reverse),
+        label = "offset"
+    )
+
+    Box(contentAlignment = Alignment.Center) {
+        BasicText(
+            "Search Light",
+            style = TextStyle.Default.copy(fontSize = 45.sp, textAlign = TextAlign.Center),
+            brush = {
+                Brush.radialGradient(
+                    offset.value + 0.2f to Color.LightGray, /* grow and sharpen */
+                    offset.value + 0.8f to Color.Black,
+                    center = Offset(600f * offset.value, 100f)
+                )
+            },
+            alpha = { 1.0f }
+        )
+    }
+}
 
 class TextMotionState(initialTextStyle: TextStyle) {
     var isStatic by mutableStateOf(true)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 29d7a42..4ccb221 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -32,6 +32,14 @@
             )
         ),
         DemoCategory(
+            "Animation",
+            listOf(
+                ComposableDemo("color = { animated.value }") { TextColorAnimation() },
+                ComposableDemo("gradient = { animated.value } ") { TextGradientAnimation() },
+                ComposableDemo("GraphicsLayer (skew, scale, etc)") { TextAnimationDemo() },
+            )
+        ),
+        DemoCategory(
             "Text Layout",
             listOf(
                 ComposableDemo("Static text") { TextDemo() },
@@ -67,7 +75,6 @@
                 ComposableDemo("Layout Reuse") { TextReuseLayoutDemo() },
                 ComposableDemo("Multi paragraph") { MultiParagraphDemo() },
                 ComposableDemo("Interactive text") { InteractiveTextDemo() },
-                ComposableDemo("Text Animation") { TextAnimationDemo() },
             )
         ),
         DemoCategory(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt
index 566d0ad..42b948d 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfoTest.kt
@@ -36,6 +36,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.runBlocking
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -493,6 +494,33 @@
         }
     }
 
+    @Test
+    fun contentTypeIsCorrect() {
+        // no reasons to run the test in all variants
+        Assume.assumeTrue(vertical && !reverseLayout)
+
+        val state = LazyGridState()
+        rule.setContent {
+            LazyGrid(
+                cells = 1,
+                state = state,
+                modifier = Modifier.requiredSize(itemSizeDp * 3f)
+            ) {
+                items(2, contentType = { it }) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+                item {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.visibleItemsInfo.map { it.contentType })
+                .isEqualTo(listOf(0, 1, null))
+        }
+    }
+
     fun LazyGridLayoutInfo.assertVisibleItems(
         count: Int,
         cells: Int,
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index d3e9d7a..d0fc90e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -375,6 +375,28 @@
         }
     }
 
+    @Test
+    fun regularCompositionIsUsedInPrefetchTimeCalculation() {
+        val itemProvider = itemProvider({ 1 }) {
+            Box(Modifier.fillMaxSize())
+        }
+        val prefetchState = LazyLayoutPrefetchState()
+        rule.setContent {
+            LazyLayout(itemProvider, prefetchState = prefetchState) { constraint ->
+                val item = measure(0, constraint)[0]
+                layout(100, 100) {
+                    item.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            val timeTracker = requireNotNull(prefetchState.prefetcher?.timeTracker)
+            assertThat(timeTracker.compositionTimeNs).isGreaterThan(0L)
+            assertThat(timeTracker.measurementTimeNs).isGreaterThan(0L)
+        }
+    }
+
     private fun itemProvider(
         itemCount: () -> Int,
         itemContent: @Composable (Int) -> Unit
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt
index bfa6f36..09a592f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListLayoutInfoTest.kt
@@ -37,6 +37,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.runBlocking
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -432,6 +433,32 @@
     }
 
     @Test
+    fun contentTypeIsCorrect() {
+        // no reasons to run the test in all variants
+        assumeTrue(vertical && !reverseLayout)
+
+        lateinit var state: LazyListState
+        rule.setContent {
+            LazyColumnOrRow(
+                state = rememberLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(itemSizeDp * 3f)
+            ) {
+                items(2, contentType = { it }) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+                item {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.visibleItemsInfo.map { it.contentType })
+                .isEqualTo(listOf(0, 1, null))
+        }
+    }
+
+    @Test
     fun viewportOffsetsSmallContentReverseArrangement() {
         val state = LazyListState()
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt
new file mode 100644
index 0000000..f078506
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLayoutInfoTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.staggeredgrid
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Test
+
+@MediumTest
+class LazyStaggeredGridLayoutInfoTest : BaseLazyStaggeredGridWithOrientation(Orientation.Vertical) {
+
+    @Test
+    fun contentTypeIsCorrect() {
+        val state = LazyStaggeredGridState()
+        rule.setContent {
+            LazyStaggeredGrid(
+                lanes = 1,
+                state = state,
+                modifier = Modifier.requiredSize(30.dp)
+            ) {
+                items(2, contentType = { it }) {
+                    Box(Modifier.size(10.dp))
+                }
+                item {
+                    Box(Modifier.size(10.dp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(state.layoutInfo.visibleItemsInfo.map { it.contentType })
+                .isEqualTo(listOf(0, 1, null))
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt
index b10c06a..8bf92f2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/BasicTextScreenshotTest.kt
@@ -21,7 +21,6 @@
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -43,7 +42,6 @@
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-@OptIn(ExperimentalTestApi::class)
 class BasicTextScreenshotTest {
     @get:Rule
     val rule = createComposeRule()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/NodeInvalidationTestParent.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/NodeInvalidationTestParent.kt
index 4776166..efc06d7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/NodeInvalidationTestParent.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/NodeInvalidationTestParent.kt
@@ -18,7 +18,7 @@
 
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.createFontFamilyResolver
@@ -43,7 +43,53 @@
         assertThat(textChange).isFalse()
     }
 
-    @OptIn(ExperimentalTextApi::class)
+    @Test
+    fun colorChanged_usingLambda_doesInvalidateDraw() {
+        val params = generateParams()
+        val redFactory = { Color.Red }
+        val blueFactory = { Color.Blue }
+        val drawParams = DrawParams(params.style, redFactory)
+        val subject = createSubject(params, drawParams)
+        val drawChanged = subject.updateDrawArgs(drawParams.copy(color = blueFactory))
+        assertThat(drawChanged).isTrue()
+    }
+
+    @Test
+    fun colorChanged_usingStyle_doesInvalidateDraw() {
+        val params = generateParams()
+        val drawParams = DrawParams(params.style, { Color.Unspecified })
+        val subject = createSubject(params, drawParams)
+        val drawChanged = subject.updateDrawArgs(
+            drawParams = drawParams.copy(style = drawParams.style.copy(color = Color.Red))
+        )
+        assertThat(drawChanged).isTrue()
+    }
+
+    @Test
+    fun brushChanged_usingLambda_doesInvalidateDraw() {
+        val params = generateParams()
+        val redFactory = { Brush.linearGradient(listOf(Color.Red)) }
+        val blueFactory = { Brush.linearGradient(listOf(Color.Blue)) }
+        val alpha = { 1.0f }
+        val drawParams = DrawParams(params.style, null, redFactory, alpha)
+        val subject = createSubject(params, drawParams)
+        val drawChanged = subject.updateDrawArgs(drawParams.copy(brush = blueFactory))
+        assertThat(drawChanged).isTrue()
+    }
+
+    @Test
+    fun brushChanged_usingStyle_doesInvalidateDraw() {
+        val params = generateParams()
+        val drawParams = DrawParams(params.style, null, null, null)
+        val subject = createSubject(params, drawParams)
+        val drawChanged = subject.updateDrawArgs(
+            drawParams = drawParams.copy(
+                style = drawParams.style.copy(brush = Brush.linearGradient(listOf(Color.Blue)))
+            )
+        )
+        assertThat(drawChanged).isTrue()
+    }
+
     @Test
     fun brushChange_doesNotInvalidateLayout() {
         val params = generateParams()
@@ -121,8 +167,10 @@
         assertThat(textChange).isFalse()
     }
 
+    abstract fun Any.updateDrawArgs(drawParams: DrawParams): Boolean
     abstract fun Any.updateAll(params: Params): Pair<Boolean, Boolean>
     abstract fun createSubject(params: Params): Any
+    abstract fun createSubject(params: Params, drawParams: DrawParams): Any
     private fun generateParams(): Params {
         return Params(
             "text",
@@ -144,4 +192,11 @@
         val maxLines: Int,
         val minLines: Int
     )
+
+    data class DrawParams(
+        val style: TextStyle,
+        val color: ColorLambda? = null,
+        val brush: (() -> Brush)? = null,
+        val alpha: (() -> Float)? = null
+    )
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeInvalidationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeInvalidationTest.kt
index 3a9f492..227fe8e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeInvalidationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNodeInvalidationTest.kt
@@ -32,6 +32,16 @@
         )
     }
 
+    override fun Any.updateDrawArgs(drawParams: DrawParams): Boolean {
+        this as TextAnnotatedStringNode
+        return updateDraw(
+            drawParams.color,
+            drawParams.brush,
+            drawParams.alpha,
+            drawParams.style
+        )
+    }
+
     override fun createSubject(params: Params): Any {
         return TextAnnotatedStringNode(
             text = AnnotatedString(text = params.text),
@@ -41,7 +51,23 @@
             overflow = params.overflow,
             softWrap = params.softWrap,
             maxLines = params.maxLines,
-            minLines = params.minLines
+            minLines = params.minLines,
+        )
+    }
+
+    override fun createSubject(params: Params, drawParams: DrawParams): Any {
+        return TextAnnotatedStringNode(
+            text = AnnotatedString(text = params.text),
+            style = params.style,
+            fontFamilyResolver = params.fontFamilyResolver,
+            onTextLayout = null,
+            overflow = params.overflow,
+            softWrap = params.softWrap,
+            maxLines = params.maxLines,
+            minLines = params.minLines,
+            overrideColor = drawParams.color,
+            overrideBrush = drawParams.brush,
+            overrideAlpha = drawParams.alpha
         )
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeInvalidationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeInvalidationTest.kt
index 9453f41..d35be68 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeInvalidationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNodeInvalidationTest.kt
@@ -17,6 +17,16 @@
 package androidx.compose.foundation.text.modifiers
 
 class TextStringSimpleNodeInvalidationTest : NodeInvalidationTestParent() {
+    override fun Any.updateDrawArgs(drawParams: DrawParams): Boolean {
+        this as TextStringSimpleNode
+        return this.updateDraw(
+            drawParams.color,
+            drawParams.brush,
+            drawParams.alpha,
+            drawParams.style
+        )
+    }
+
     override fun Any.updateAll(params: Params): Pair<Boolean, Boolean> {
         this as TextStringSimpleNode
         return updateText(params.text) to updateLayoutRelatedArgs(
@@ -37,7 +47,22 @@
             params.overflow,
             params.softWrap,
             params.maxLines,
-            params.minLines
+            params.minLines,
+        )
+    }
+
+    override fun createSubject(params: Params, drawParams: DrawParams): Any {
+        return TextStringSimpleNode(
+            params.text,
+            params.style,
+            params.fontFamilyResolver,
+            params.overflow,
+            params.softWrap,
+            params.maxLines,
+            params.minLines,
+            drawParams.color,
+            drawParams.brush,
+            drawParams.alpha
         )
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
index 48c54d1..db7cb63 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
@@ -20,6 +20,7 @@
 import android.view.Display
 import android.view.View
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.AverageTimeTracker
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.RememberObserver
 import androidx.compose.runtime.collection.mutableVectorOf
@@ -71,26 +72,7 @@
  *    Frame 4 - prefetch [e], [f]
  *    Something similar is not possible with LazyColumn yet.
  *
- * 2) Prefetching time estimation only captured during the prefetch.
- *    We currently don't track the time of the regular subcompose call happened during the regular
- *    measure pass, only the ones which are done during the prefetching. The downside is we build
- *    our prefetch information only after scrolling has started and items are showing up. Your very
- *    first scroll won't know if it's safe to prefetch. Why:
- *    a) SubcomposeLayout is not exposing an API to understand if subcompose() call is going to
- *    do the real work. The work could be skipped if the same lambda was passed as for the
- *    previous invocation or if there were no recompositions scheduled. We could workaround it
- *    by keeping the extra state in LazyListState about what items we already composed and to
- *    only measure the first composition for the given slot, or consider exposing extra
- *    information in SubcomposeLayoutState API.
- *    b) It allows us to nicely decouple the logic, now the prefetching logic is build on
- *    top of the regular LazyColumn measuring functionallity and the main logic knows nothing
- *    about prefetch
- *    c) Maybe the better approach would be to wait till the low-level runtime infra is ready to
- *    do subcompositions on the different threads which illuminates the need to calculate the
- *    deadlines completely.
- *    Tracking bug: b/187393381.
- *
- * 3) Prefetch is not aware of item type.
+ * 2) Prefetch is not aware of item type.
  *    RecyclerView separates timing metadata about different item types. For example, in play
  *    store style UI, this allows RecyclerView to separately estimate the cost of a header,
  *    separator, and item row. In this implementation, all of these would be averaged together in
@@ -121,14 +103,6 @@
      */
     private val prefetchRequests = mutableVectorOf<PrefetchRequest>()
 
-    /**
-     * Average time the prefetching operations takes. Keeping it allows us to not start the work
-     * if in this frame we are most likely not going to finish the work in time to not delay the
-     * next frame.
-     */
-    private var averagePrecomposeTimeNs: Long = 0
-    private var averagePremeasureTimeNs: Long = 0
-
     private var prefetchScheduled = false
 
     private val choreographer = Choreographer.getInstance()
@@ -140,6 +114,10 @@
         calculateFrameIntervalIfNeeded(view)
     }
 
+    override val timeTracker: AverageTimeTracker = object : AverageTimeTracker() {
+        override fun currentTime(): Long = System.nanoTime()
+    }
+
     /**
      * Callback to be executed when the prefetching is needed.
      * [prefetchRequests] will be used as an input.
@@ -165,14 +143,13 @@
                     val beforeTimeNs = System.nanoTime()
                     // check if there is enough time left in this frame. otherwise, we schedule
                     // a next frame callback in which we will post the message in the handler again.
-                    if (enoughTimeLeft(beforeTimeNs, nextFrameNs, averagePrecomposeTimeNs)) {
+                    if (enoughTimeLeft(beforeTimeNs, nextFrameNs, timeTracker.compositionTimeNs)) {
                         val key = itemProvider.getKey(request.index)
                         val content = itemContentFactory.getContent(request.index, key)
-                        request.precomposeHandle = subcomposeLayoutState.precompose(key, content)
-                        averagePrecomposeTimeNs = calculateAverageTime(
-                            System.nanoTime() - beforeTimeNs,
-                            averagePrecomposeTimeNs
-                        )
+                        timeTracker.trackComposition {
+                            request.precomposeHandle =
+                                subcomposeLayoutState.precompose(key, content)
+                        }
                     } else {
                         scheduleForNextFrame = true
                     }
@@ -181,18 +158,16 @@
                 check(!request.measured)
                 trace("compose:lazylist:prefetch:measure") {
                     val beforeTimeNs = System.nanoTime()
-                    if (enoughTimeLeft(beforeTimeNs, nextFrameNs, averagePremeasureTimeNs)) {
+                    if (enoughTimeLeft(beforeTimeNs, nextFrameNs, timeTracker.measurementTimeNs)) {
                         val handle = request.precomposeHandle!!
-                        repeat(handle.placeablesCount) { placeableIndex ->
-                            handle.premeasure(
-                                placeableIndex,
-                                request.constraints
-                            )
+                        timeTracker.trackMeasurement {
+                            repeat(handle.placeablesCount) { placeableIndex ->
+                                handle.premeasure(
+                                    placeableIndex,
+                                    request.constraints
+                                )
+                            }
                         }
-                        averagePremeasureTimeNs = calculateAverageTime(
-                            System.nanoTime() - beforeTimeNs,
-                            averagePremeasureTimeNs
-                        )
                         // we finished this request
                         prefetchRequests.removeAt(0)
                     } else {
@@ -225,18 +200,6 @@
         }
     }
 
-    private fun calculateAverageTime(new: Long, current: Long): Long {
-        // Calculate a weighted moving average of time taken to compose an item. We use weighted
-        // moving average to bias toward more recent measurements, and to minimize storage /
-        // computation cost. (the idea is taken from RecycledViewPool)
-        return if (current == 0L) {
-            new
-        } else {
-            // dividing first to avoid a potential overflow
-            current / 4 * 3 + new / 4
-        }
-    }
-
     override fun schedulePrefetch(
         index: Int,
         constraints: Constraints
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 099246b..5ef5aa7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -36,7 +36,7 @@
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
@@ -839,7 +839,7 @@
     protected var interactionSource: MutableInteractionSource?,
     protected var onClick: () -> Unit,
     protected val interactionData: AbstractClickableNode.InteractionData
-) : DelegatingNode(), ModifierLocalNode, CompositionLocalConsumerModifierNode,
+) : DelegatingNode(), ModifierLocalModifierNode, CompositionLocalConsumerModifierNode,
     PointerInputModifierNode {
 
     private val delayPressInteraction = {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index bb1c694..ff1bcc1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -40,7 +40,7 @@
 import androidx.compose.ui.node.GlobalPositionAwareModifierNode
 import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ObserverNode
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.SemanticsModifierNode
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.node.invalidateSemantics
@@ -66,7 +66,6 @@
  * @param interactionSource [MutableInteractionSource] that will be used to emit
  * [FocusInteraction.Focus] when this element is being focused.
  */
-@OptIn(ExperimentalFoundationApi::class)
 fun Modifier.focusable(
     enabled: Boolean = true,
     interactionSource: MutableInteractionSource? = null,
@@ -316,7 +315,7 @@
 }
 
 private class FocusablePinnableContainerNode : Modifier.Node(),
-    CompositionLocalConsumerModifierNode, ObserverNode {
+    CompositionLocalConsumerModifierNode, ObserverModifierNode {
     private var pinnedHandle: PinnableContainer.PinnedHandle? = null
     private var isFocused: Boolean = false
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
index 5212dff..9ea3c65 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/FocusedBounds.kt
@@ -19,14 +19,13 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.modifier.ModifierLocalMap
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.modifierLocalMapOf
 import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.node.GlobalPositionAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
 
-@OptIn(ExperimentalFoundationApi::class)
 internal val ModifierLocalFocusedBoundsObserver =
     modifierLocalOf<((LayoutCoordinates?) -> Unit)?> { null }
 
@@ -71,12 +70,12 @@
 
 private class FocusedBoundsObserverNode(
     var onPositioned: (LayoutCoordinates?) -> Unit
-) : Modifier.Node(), ModifierLocalNode, (LayoutCoordinates?) -> Unit {
+) : Modifier.Node(), ModifierLocalModifierNode, (LayoutCoordinates?) -> Unit {
     private val parent: ((LayoutCoordinates?) -> Unit)?
         get() = if (isAttached) ModifierLocalFocusedBoundsObserver.current else null
 
-    override val providedValues: ModifierLocalMap
-        get() = modifierLocalMapOf(ModifierLocalFocusedBoundsObserver to this)
+    override val providedValues: ModifierLocalMap =
+        modifierLocalMapOf(entry = ModifierLocalFocusedBoundsObserver to this)
 
     /** Called when a child gains/loses focus or is focused and changes position. */
     override fun invoke(focusedBounds: LayoutCoordinates?) {
@@ -89,10 +88,10 @@
 /**
  * Modifier used by [Modifier.focusable] to publish the location of the focused element.
  * Should only be applied to the node when it is actually focused. Right now this will keep
- * this node around, but once the undelegate API lands we can remove this node entirely if it
+ * this node around, but once the un-delegate API lands we can remove this node entirely if it
  * is not focused. (b/276790428)
  */
-internal class FocusedBoundsNode : Modifier.Node(), ModifierLocalNode,
+internal class FocusedBoundsNode : Modifier.Node(), ModifierLocalModifierNode,
     GlobalPositionAwareModifierNode {
     private var isFocused: Boolean = false
 
@@ -135,4 +134,4 @@
             observer?.invoke(layoutCoordinates)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index e761a7d..4994b52 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -259,7 +259,7 @@
             isVertical,
             itemProvider,
             this
-        ) { index, key, placeables ->
+        ) { index, key, contentType, placeables ->
             // we add spaceBetweenItems as an extra spacing for all items apart from the last one so
             // the lazy list measuring logic will take it into account.
             val spacing = if (index.value == itemsCount - 1) 0 else spaceBetweenItems
@@ -275,7 +275,8 @@
                 afterContentPadding = afterContentPadding,
                 spacing = spacing,
                 visualOffset = visualItemOffset,
-                key = key
+                key = key,
+                contentType = contentType
             )
         }
         state.premeasureConstraints = measuredItemProvider.childConstraints
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
index 325d9c7..a1163fa 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
@@ -43,4 +43,9 @@
      * slot for the item then this size will be calculated as the sum of their sizes.
      */
     val size: Int
+
+    /**
+     * The content type of the item which was passed to the item() or items() function.
+     */
+    val contentType: Any? get() = null
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
index 443ccee..0c4b130 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItem.kt
@@ -49,6 +49,7 @@
      */
     private val visualOffset: IntOffset,
     val key: Any,
+    private val contentType: Any?
 ) {
     /**
      * Sum of the main axis sizes of all the inner placeables.
@@ -116,7 +117,8 @@
             wrappers = wrappers,
             visualOffset = visualOffset,
             reverseLayout = reverseLayout,
-            mainAxisLayoutSize = mainAxisLayoutSize
+            mainAxisLayoutSize = mainAxisLayoutSize,
+            contentType = contentType
         )
     }
 }
@@ -132,7 +134,8 @@
     private val wrappers: List<LazyListPlaceableWrapper>,
     private val visualOffset: IntOffset,
     private val reverseLayout: Boolean,
-    private val mainAxisLayoutSize: Int
+    private val mainAxisLayoutSize: Int,
+    override val contentType: Any?
 ) : LazyListItemInfo {
     val placeablesCount: Int get() = wrappers.size
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItemProvider.kt
index bbf587b..38baf57 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasuredItemProvider.kt
@@ -45,8 +45,9 @@
      */
     fun getAndMeasure(index: DataIndex): LazyListMeasuredItem {
         val key = itemProvider.getKey(index.value)
+        val contentType = itemProvider.getContentType(index.value)
         val placeables = measureScope.measure(index.value, childConstraints)
-        return measuredItemFactory.createItem(index, key, placeables)
+        return measuredItemFactory.createItem(index, key, contentType, placeables)
     }
 
     /**
@@ -61,6 +62,7 @@
     fun createItem(
         index: DataIndex,
         key: Any,
+        contentType: Any?,
         placeables: List<Placeable>
     ): LazyListMeasuredItem
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
index b60f6ed..c752ea4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
@@ -256,7 +256,7 @@
             itemProvider,
             this,
             spaceBetweenLines
-        ) { index, key, crossAxisSize, mainAxisSpacing, placeables ->
+        ) { index, key, contentType, crossAxisSize, mainAxisSpacing, placeables ->
             LazyGridMeasuredItem(
                 index = index,
                 key = key,
@@ -268,7 +268,8 @@
                 beforeContentPadding = beforeContentPadding,
                 afterContentPadding = afterContentPadding,
                 visualOffset = visualItemOffset,
-                placeables = placeables
+                placeables = placeables,
+                contentType = contentType
             )
         }
         val measuredLineProvider = LazyGridMeasuredLineProvider(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemInfo.kt
index b9f12a7..5eeae22 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemInfo.kt
@@ -60,6 +60,11 @@
      */
     val size: IntSize
 
+    /**
+     * The content type of the item which was passed to the item() or items() function.
+     */
+    val contentType: Any?
+
     companion object {
         /**
          * Possible value for [row], when they are unknown. This can happen when the item is
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
index 481564d..9db0fd0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItem.kt
@@ -46,7 +46,8 @@
      * The offset which shouldn't affect any calculations but needs to be applied for the final
      * value passed into the place() call.
      */
-    private val visualOffset: IntOffset
+    private val visualOffset: IntOffset,
+    private val contentType: Any?
 ) {
     /**
      * Main axis size of the item - the max main axis size of the placeables.
@@ -115,7 +116,8 @@
             placeables = placeables,
             visualOffset = visualOffset,
             mainAxisLayoutSize = mainAxisLayoutSize,
-            reverseLayout = reverseLayout
+            reverseLayout = reverseLayout,
+            contentType = contentType
         )
     }
 }
@@ -133,7 +135,8 @@
     private val placeables: List<Placeable>,
     private val visualOffset: IntOffset,
     private val mainAxisLayoutSize: Int,
-    private val reverseLayout: Boolean
+    private val reverseLayout: Boolean,
+    override val contentType: Any?
 ) : LazyGridItemInfo {
     val placeablesCount: Int get() = placeables.size
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
index b7a0b0e..ae9a1d8e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasuredItemProvider.kt
@@ -42,6 +42,7 @@
         constraints: Constraints
     ): LazyGridMeasuredItem {
         val key = itemProvider.getKey(index.value)
+        val contentType = itemProvider.getContentType(index.value)
         val placeables = measureScope.measure(index.value, constraints)
         val crossAxisSize = if (constraints.hasFixedWidth) {
             constraints.minWidth
@@ -52,6 +53,7 @@
         return measuredItemFactory.createItem(
             index,
             key,
+            contentType,
             crossAxisSize,
             mainAxisSpacing,
             placeables
@@ -70,6 +72,7 @@
     fun createItem(
         index: ItemIndex,
         key: Any,
+        contentType: Any?,
         crossAxisSize: Int,
         mainAxisSpacing: Int,
         placeables: List<Placeable>
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
index 97b8896..f43b274 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayout.kt
@@ -67,7 +67,11 @@
             modifier,
             remember(itemContentFactory, measurePolicy) {
                 { constraints ->
-                    with(LazyLayoutMeasureScopeImpl(itemContentFactory, this)) {
+                    with(LazyLayoutMeasureScopeImpl(
+                        itemContentFactory,
+                        this,
+                        prefetchState?.prefetcher?.timeTracker
+                    )) {
                         measurePolicy(constraints)
                     }
                 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
index b7dc915..fad7467 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutMeasureScope.kt
@@ -99,7 +99,8 @@
 @ExperimentalFoundationApi
 internal class LazyLayoutMeasureScopeImpl internal constructor(
     private val itemContentFactory: LazyLayoutItemContentFactory,
-    private val subcomposeMeasureScope: SubcomposeMeasureScope
+    private val subcomposeMeasureScope: SubcomposeMeasureScope,
+    private val timeTracker: LazyLayoutPrefetchState.AverageTimeTracker?
 ) : LazyLayoutMeasureScope, MeasureScope by subcomposeMeasureScope {
 
     /**
@@ -115,12 +116,32 @@
         } else {
             val key = itemContentFactory.itemProvider().getKey(index)
             val itemContent = itemContentFactory.getContent(index, key)
-            val measurables = subcomposeMeasureScope.subcompose(key, itemContent)
-            List(measurables.size) { i ->
-                measurables[i].measure(constraints)
-            }.also {
-                placeablesCache[index] = it
+            val measurables = trackComposition {
+                subcomposeMeasureScope.subcompose(key, itemContent)
             }
+            trackMeasurement {
+                List(measurables.size) { i ->
+                    measurables[i].measure(constraints)
+                }.also {
+                    placeablesCache[index] = it
+                }
+            }
+        }
+    }
+
+    private inline fun <T> trackComposition(block: () -> T): T {
+        return if (timeTracker != null) {
+            timeTracker.trackComposition(block)
+        } else {
+            block()
+        }
+    }
+
+    private inline fun <T> trackMeasurement(block: () -> T): T {
+        return if (timeTracker != null) {
+            timeTracker.trackMeasurement(block)
+        } else {
+            block()
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
index 4d77ac4..9a47017 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState.kt
@@ -51,6 +51,55 @@
 
     internal interface Prefetcher {
         fun schedulePrefetch(index: Int, constraints: Constraints): PrefetchHandle
+
+        val timeTracker: AverageTimeTracker
+    }
+
+    internal abstract class AverageTimeTracker {
+
+        /**
+         * Average time the prefetching operations takes. Keeping it allows us to not start the work
+         * if in this frame we are most likely not going to finish the work in time to not delay the
+         * next frame.
+         */
+        var compositionTimeNs: Long = 0
+            private set
+        var measurementTimeNs: Long = 0
+            private set
+
+        abstract fun currentTime(): Long
+
+        inline fun <T> trackComposition(block: () -> T): T {
+            val beforeTimeNs = currentTime()
+            val returnValue = block()
+            compositionTimeNs = calculateAverageTime(
+                currentTime() - beforeTimeNs,
+                compositionTimeNs
+            )
+            return returnValue
+        }
+
+        inline fun <T> trackMeasurement(block: () -> T): T {
+            val beforeTimeNs = currentTime()
+            val returnValue = block()
+            measurementTimeNs = calculateAverageTime(
+                currentTime() - beforeTimeNs,
+                measurementTimeNs
+            )
+            return returnValue
+        }
+
+        private fun calculateAverageTime(new: Long, current: Long): Long {
+            // Calculate a weighted moving average of time taken to compose an item. We use weighted
+            // moving average to bias toward more recent measurements, and to minimize storage /
+            // computation cost. (the idea is taken from RecycledViewPool)
+            return if (current == 0L) {
+                new
+            } else {
+                // dividing first to avoid a potential overflow
+                current / 4 * 3 + new / 4
+            }
+        }
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index b653fd7..62c4e2c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -186,7 +186,7 @@
         itemProvider = itemProvider,
         measureScope = measureScope,
         resolvedSlots = resolvedSlots,
-    ) { index, lane, span, key, placeables ->
+    ) { index, lane, span, key, contentType, placeables ->
         LazyStaggeredGridMeasuredItem(
             index = index,
             key = key,
@@ -196,7 +196,8 @@
             lane = lane,
             span = span,
             beforeContentPadding = beforeContentPadding,
-            afterContentPadding = afterContentPadding
+            afterContentPadding = afterContentPadding,
+            contentType = contentType
         )
     }
 
@@ -1013,8 +1014,16 @@
 
     fun getAndMeasure(index: Int, span: SpanRange): LazyStaggeredGridMeasuredItem {
         val key = itemProvider.getKey(index)
+        val contentType = itemProvider.getContentType(index)
         val placeables = measureScope.measure(index, childConstraints(span.start, span.size))
-        return measuredItemFactory.createItem(index, span.start, span.size, key, placeables)
+        return measuredItemFactory.createItem(
+            index,
+            span.start,
+            span.size,
+            key,
+            contentType,
+            placeables
+        )
     }
 
     val keyToIndexMap: LazyLayoutKeyIndexMap get() = itemProvider.keyToIndexMap
@@ -1027,6 +1036,7 @@
         lane: Int,
         span: Int,
         key: Any,
+        contentType: Any?,
         placeables: List<Placeable>
     ): LazyStaggeredGridMeasuredItem
 }
@@ -1041,6 +1051,7 @@
     val span: Int,
     private val beforeContentPadding: Int,
     private val afterContentPadding: Int,
+    private val contentType: Any?
 ) {
     var isVisible = true
 
@@ -1083,7 +1094,8 @@
             mainAxisLayoutSize = mainAxisLayoutSize,
             minMainAxisOffset = -beforeContentPadding,
             maxMainAxisOffset = mainAxisLayoutSize + afterContentPadding,
-            span = span
+            span = span,
+            contentType = contentType
         )
 }
 
@@ -1098,7 +1110,8 @@
     private val mainAxisLayoutSize: Int,
     private val minMainAxisOffset: Int,
     private val maxMainAxisOffset: Int,
-    val span: Int
+    val span: Int,
+    override val contentType: Any?
 ) : LazyStaggeredGridItemInfo {
 
     val placeablesCount: Int get() = placeables.size
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
index 01621e2..50121ed 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
@@ -52,6 +52,11 @@
      * their sizes.
      */
     val size: IntSize
+
+    /**
+     * The content type of the item which was passed to the item() or items() function.
+     */
+    val contentType: Any?
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
index 4c532f3..ca27e6f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoView.kt
@@ -18,7 +18,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
 import androidx.compose.ui.node.LayoutAwareModifierNode
@@ -69,7 +69,7 @@
  * calling [defaultBringIntoViewParent] to support platform-specific integration.
  */
 internal abstract class BringIntoViewChildNode : Modifier.Node(),
-    ModifierLocalNode, LayoutAwareModifierNode, CompositionLocalConsumerModifierNode {
+    ModifierLocalModifierNode, LayoutAwareModifierNode, CompositionLocalConsumerModifierNode {
     private val defaultParent = defaultBringIntoViewParent()
 
     private val localParent: BringIntoViewParent? get() = ModifierLocalBringIntoViewParent.current
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
index 1c91070..15c631e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
@@ -20,6 +20,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.modifier.ModifierLocalMap
 import androidx.compose.ui.modifier.modifierLocalMapOf
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
@@ -135,7 +136,8 @@
     var responder: BringIntoViewResponder
 ) : BringIntoViewChildNode(), BringIntoViewParent {
 
-    override val providedValues = modifierLocalMapOf(ModifierLocalBringIntoViewParent to this)
+    override val providedValues: ModifierLocalMap =
+        modifierLocalMapOf(entry = ModifierLocalBringIntoViewParent to this)
 
     /**
      * Responds to a child's request by first converting [boundsProvider] into this node's [LayoutCoordinates]
@@ -202,4 +204,4 @@
         top <= other.top &&
         right >= other.right &&
         bottom >= other.bottom
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
index a623181..1a72c7c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
@@ -32,6 +32,8 @@
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.Measurable
@@ -47,6 +49,7 @@
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.FloatLambda
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.util.fastForEach
 import kotlin.math.floor
@@ -73,8 +76,8 @@
  * [overflow] and [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
  * @param minLines The minimum height in terms of minimum number of visible lines. It is required
  * that 1 <= [minLines] <= [maxLines].
+ * @param color Overrides the text color provided in [style]
  */
-@OptIn(InternalFoundationTextApi::class)
 @Composable
 fun BasicText(
     text: String,
@@ -84,7 +87,8 @@
     overflow: TextOverflow = TextOverflow.Clip,
     softWrap: Boolean = true,
     maxLines: Int = Int.MAX_VALUE,
-    minLines: Int = 1
+    minLines: Int = 1,
+    color: ColorLambda? = null
 ) {
     validateMinMaxLines(
         minLines = minLines,
@@ -117,7 +121,10 @@
                 fontFamilyResolver = LocalFontFamilyResolver.current,
                 placeholders = null,
                 onPlaceholderLayout = null,
-                selectionController = selectionController
+                selectionController = selectionController,
+                color = color,
+                brush = null,
+                alpha = null
             )
     } else {
         modifier
@@ -129,7 +136,102 @@
             overflow = overflow,
             softWrap = softWrap,
             maxLines = maxLines,
-            minLines = minLines
+            minLines = minLines,
+            color = color,
+            brush = null,
+            alpha = null
+        )
+    }
+    Layout(finalModifier, EmptyMeasurePolicy)
+}
+
+/**
+ * Basic element that displays text and provides semantics / accessibility information.
+ * Typically you will instead want to use [androidx.compose.material.Text], which is
+ * a higher level Text element that contains semantics and consumes style information from a theme.
+ *
+ * @param text The text to be displayed.
+ * @param modifier [Modifier] to apply to this layout node.
+ * @param style Style configuration for the text such as color, font, line height etc.
+ * @param onTextLayout Callback that is executed when a new text layout is calculated. A
+ * [TextLayoutResult] object that callback provides contains paragraph information, size of the
+ * text, baselines and other details. The callback can be used to add additional decoration or
+ * functionality to the text. For example, to draw selection around the text.
+ * @param overflow How visual overflow should be handled.
+ * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
+ * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
+ * [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
+ * @param minLines The minimum height in terms of minimum number of visible lines. It is required
+ * that 1 <= [minLines] <= [maxLines].
+ * @param brush Overrides the brush provided in [style]
+ * @param alpha Overrides the brush alpha provided in [style]
+ */
+@Composable
+fun BasicText(
+    text: String,
+    modifier: Modifier = Modifier,
+    style: TextStyle = TextStyle.Default,
+    onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = 1,
+    brush: (() -> Brush)? = null,
+    alpha: FloatLambda? = null
+) {
+    validateMinMaxLines(
+        minLines = minLines,
+        maxLines = maxLines
+    )
+    val selectionRegistrar = LocalSelectionRegistrar.current
+    val selectionController = if (selectionRegistrar != null) {
+        val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
+        remember(selectionRegistrar, backgroundSelectionColor) {
+            SelectionController(
+                selectionRegistrar,
+                backgroundSelectionColor
+            )
+        }
+    } else {
+        null
+    }
+    val finalModifier = if (selectionController != null || onTextLayout != null) {
+        modifier
+            // TODO(b/274781644): Remove this graphicsLayer
+            .graphicsLayer()
+            .textModifier(
+                AnnotatedString(text = text),
+                style = style,
+                onTextLayout = onTextLayout,
+                overflow = overflow,
+                softWrap = softWrap,
+                maxLines = maxLines,
+                minLines = minLines,
+                fontFamilyResolver = LocalFontFamilyResolver.current,
+                placeholders = null,
+                onPlaceholderLayout = null,
+                selectionController = selectionController,
+                color = null,
+                brush = brush,
+                alpha = alpha
+            )
+    } else {
+        modifier
+            // TODO(b/274781644): Remove this graphicsLayer
+            .graphicsLayer() then TextStringSimpleElement(
+            text = text,
+            style = style,
+            fontFamilyResolver = LocalFontFamilyResolver.current,
+            overflow = overflow,
+            softWrap = softWrap,
+            maxLines = maxLines,
+            minLines = minLines,
+            color = null,
+            brush = brush,
+            alpha = alpha
         )
     }
     Layout(finalModifier, EmptyMeasurePolicy)
@@ -158,8 +260,8 @@
  * that 1 <= [minLines] <= [maxLines].
  * @param inlineContent A map store composables that replaces certain ranges of the text. It's
  * used to insert composables into text layout. Check [InlineTextContent] for more information.
+ * @param color Overrides the text color provided in [style]
  */
-@OptIn(InternalFoundationTextApi::class)
 @Composable
 fun BasicText(
     text: AnnotatedString,
@@ -170,7 +272,8 @@
     softWrap: Boolean = true,
     maxLines: Int = Int.MAX_VALUE,
     minLines: Int = 1,
-    inlineContent: Map<String, InlineTextContent> = mapOf()
+    inlineContent: Map<String, InlineTextContent> = mapOf(),
+    color: ColorLambda? = null
 ) {
     validateMinMaxLines(
         minLines = minLines,
@@ -205,7 +308,10 @@
                     fontFamilyResolver = LocalFontFamilyResolver.current,
                     placeholders = null,
                     onPlaceholderLayout = null,
-                    selectionController = selectionController
+                    selectionController = selectionController,
+                    color = color,
+                    brush = null,
+                    alpha = null
                 ),
             EmptyMeasurePolicy
         )
@@ -233,13 +339,130 @@
                 fontFamilyResolver = LocalFontFamilyResolver.current,
                 placeholders = placeholders,
                 onPlaceholderLayout = { measuredPlaceholderPositions.value = it },
-                selectionController = selectionController
+                selectionController = selectionController,
+                color = color,
+                brush = null,
+                alpha = null
             ),
             measurePolicy = TextMeasurePolicy { measuredPlaceholderPositions.value }
         )
     }
 }
 
+/**
+ * Basic element that displays text and provides semantics / accessibility information.
+ * Typically you will instead want to use [androidx.compose.material.Text], which is
+ * a higher level Text element that contains semantics and consumes style information from a theme.
+ *
+ * @param text The text to be displayed.
+ * @param modifier [Modifier] to apply to this layout node.
+ * @param style Style configuration for the text such as color, font, line height etc.
+ * @param onTextLayout Callback that is executed when a new text layout is calculated. A
+ * [TextLayoutResult] object that callback provides contains paragraph information, size of the
+ * text, baselines and other details. The callback can be used to add additional decoration or
+ * functionality to the text. For example, to draw selection around the text.
+ * @param overflow How visual overflow should be handled.
+ * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
+ * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
+ * [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. It is required that 1 <= [minLines] <= [maxLines].
+ * @param minLines The minimum height in terms of minimum number of visible lines. It is required
+ * that 1 <= [minLines] <= [maxLines].
+ * @param inlineContent A map store composables that replaces certain ranges of the text. It's
+ * used to insert composables into text layout. Check [InlineTextContent] for more information.
+ * @param brush Overrides the brush provided in [style]
+ * @param alpha Overrides the brush alpha provided in [style]
+ */
+@Composable
+fun BasicText(
+    text: AnnotatedString,
+    modifier: Modifier = Modifier,
+    style: TextStyle = TextStyle.Default,
+    onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = 1,
+    inlineContent: Map<String, InlineTextContent> = mapOf(),
+    brush: (() -> Brush)? = null,
+    alpha: FloatLambda? = null
+) {
+    validateMinMaxLines(
+        minLines = minLines,
+        maxLines = maxLines
+    )
+    val selectionRegistrar = LocalSelectionRegistrar.current
+    val selectionController = if (selectionRegistrar != null) {
+        val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
+        remember(selectionRegistrar, backgroundSelectionColor) {
+            SelectionController(
+                selectionRegistrar,
+                backgroundSelectionColor
+            )
+        }
+    } else {
+        null
+    }
+    if (!text.hasInlineContent()) {
+        // this is the same as text: String, use all the early exits
+        Layout(
+            modifier = modifier
+                // TODO(b/274781644): Remove this graphicsLayer
+                .graphicsLayer()
+                .textModifier(
+                    text = text,
+                    style = style,
+                    onTextLayout = onTextLayout,
+                    overflow = overflow,
+                    softWrap = softWrap,
+                    maxLines = maxLines,
+                    minLines = minLines,
+                    fontFamilyResolver = LocalFontFamilyResolver.current,
+                    placeholders = null,
+                    onPlaceholderLayout = null,
+                    selectionController = selectionController,
+                    color = null,
+                    brush = brush,
+                    alpha = alpha
+                ),
+            EmptyMeasurePolicy
+        )
+    } else {
+        // do the inline content allocs
+        val (placeholders, inlineComposables) = text.resolveInlineContent(
+            inlineContent = inlineContent
+        )
+        val measuredPlaceholderPositions = remember<MutableState<List<Rect?>?>> {
+            mutableStateOf(null)
+        }
+        Layout(
+            content = { InlineChildren(text = text, inlineContents = inlineComposables) },
+            modifier = modifier
+                // TODO(b/274781644): Remove this graphicsLayer
+                .graphicsLayer()
+                .textModifier(
+                    text = text,
+                    style = style,
+                    onTextLayout = onTextLayout,
+                    overflow = overflow,
+                    softWrap = softWrap,
+                    maxLines = maxLines,
+                    minLines = minLines,
+                    fontFamilyResolver = LocalFontFamilyResolver.current,
+                    placeholders = placeholders,
+                    onPlaceholderLayout = { measuredPlaceholderPositions.value = it },
+                    selectionController = selectionController,
+                    color = null,
+                    brush = brush,
+                    alpha = alpha
+                ),
+            measurePolicy = TextMeasurePolicy { measuredPlaceholderPositions.value }
+        )
+    }
+}
+
 @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
 @Composable
 fun BasicText(
@@ -288,6 +511,43 @@
     )
 }
 
+@Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN)
+@Composable
+fun BasicText(
+    text: String,
+    modifier: Modifier = Modifier,
+    style: TextStyle = TextStyle.Default,
+    onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = 1
+) = BasicText(text, modifier, style, onTextLayout, overflow, softWrap, maxLines, minLines)
+
+@Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN)
+@Composable
+fun BasicText(
+    text: AnnotatedString,
+    modifier: Modifier = Modifier,
+    style: TextStyle = TextStyle.Default,
+    onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = 1,
+    inlineContent: Map<String, InlineTextContent> = mapOf()
+) = BasicText(
+    text = text,
+    modifier = modifier,
+    style = style,
+    onTextLayout = onTextLayout,
+    overflow = overflow,
+    softWrap = softWrap,
+    maxLines = maxLines,
+    minLines = minLines,
+    inlineContent = inlineContent
+)
+
 /**
  * A custom saver that won't save if no selection is active.
  */
@@ -352,7 +612,10 @@
     fontFamilyResolver: FontFamily.Resolver,
     placeholders: List<AnnotatedString.Range<Placeholder>>?,
     onPlaceholderLayout: ((List<Rect?>) -> Unit)?,
-    selectionController: SelectionController?
+    selectionController: SelectionController?,
+    color: ColorLambda?,
+    brush: (() -> Brush)?,
+    alpha: FloatLambda?
 ): Modifier {
     if (selectionController == null) {
         val staticTextModifier = TextAnnotatedStringElement(
@@ -366,7 +629,10 @@
             minLines,
             placeholders,
             onPlaceholderLayout,
-            null
+            null,
+            color,
+            brush,
+            alpha
         )
         return this then Modifier /* selection position */ then staticTextModifier
     } else {
@@ -381,7 +647,10 @@
             minLines,
             placeholders,
             onPlaceholderLayout,
-            selectionController
+            selectionController,
+            color,
+            brush,
+            alpha
         )
         return this then selectionController.modifier then selectableTextModifier
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt
index 4d22aeb..7670002 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringElement.kt
@@ -18,6 +18,8 @@
 
 import androidx.compose.foundation.text.DefaultMinLines
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.text.AnnotatedString
@@ -26,6 +28,7 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.FloatLambda
 
 /**
  * Element for any text that is in a selection container.
@@ -41,7 +44,10 @@
     private val minLines: Int = DefaultMinLines,
     private val placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
     private val onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
-    private val selectionController: SelectionController? = null
+    private val selectionController: SelectionController? = null,
+    private val color: ColorLambda? = null,
+    private val brush: (() -> Brush)? = null,
+    private val alpha: FloatLambda? = null
 ) : ModifierNodeElement<SelectableTextAnnotatedStringNode>() {
 
     override fun create(): SelectableTextAnnotatedStringNode = SelectableTextAnnotatedStringNode(
@@ -55,7 +61,10 @@
         minLines,
         placeholders,
         onPlaceholderLayout,
-        selectionController
+        selectionController,
+        color,
+        brush,
+        alpha
     )
 
     override fun update(
@@ -72,7 +81,10 @@
             overflow = overflow,
             onTextLayout = onTextLayout,
             onPlaceholderLayout = onPlaceholderLayout,
-            selectionController = selectionController
+            selectionController = selectionController,
+            color = color,
+            brush = brush,
+            alpha = alpha
         )
     }
 
@@ -82,6 +94,9 @@
         if (other !is SelectableTextAnnotatedStringElement) return false
 
         // these three are most likely to actually change
+        if (color != other.color) return false
+        if (brush != other.brush) return false
+        if (alpha != other.alpha) return false
         if (text != other.text) return false
         if (style != other.style) return false
         if (placeholders != other.placeholders) return false
@@ -113,6 +128,9 @@
         result = 31 * result + (placeholders?.hashCode() ?: 0)
         result = 31 * result + (onPlaceholderLayout?.hashCode() ?: 0)
         result = 31 * result + (selectionController?.hashCode() ?: 0)
+        result = 31 * result + (color?.hashCode() ?: 0)
+        result = 31 * result + (brush?.hashCode() ?: 0)
+        result = 31 * result + (alpha?.hashCode() ?: 0)
         return result
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
index cac5e39..6ced651 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
@@ -18,6 +18,8 @@
 
 import androidx.compose.foundation.text.DefaultMinLines
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
@@ -39,6 +41,7 @@
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.FloatLambda
 
 /**
  * Node for any text that is in a selection container.
@@ -56,7 +59,10 @@
     minLines: Int = DefaultMinLines,
     placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
     onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
-    private val selectionController: SelectionController? = null
+    private val selectionController: SelectionController? = null,
+    overrideColor: ColorLambda? = null,
+    brush: (() -> Brush)? = null,
+    alpha: FloatLambda? = null,
 ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode, GlobalPositionAwareModifierNode,
     SemanticsModifierNode {
 
@@ -72,7 +78,10 @@
             minLines = minLines,
             placeholders = placeholders,
             onPlaceholderLayout = onPlaceholderLayout,
-            selectionController = selectionController
+            selectionController = selectionController,
+            overrideColor = overrideColor,
+            overrideBrush = brush,
+            overrideAlpha = alpha
         )
     )
 
@@ -127,9 +136,13 @@
         overflow: TextOverflow,
         onTextLayout: ((TextLayoutResult) -> Unit)?,
         onPlaceholderLayout: ((List<Rect?>) -> Unit)?,
-        selectionController: SelectionController?
+        selectionController: SelectionController?,
+        color: ColorLambda?,
+        brush: (() -> Brush)?,
+        alpha: FloatLambda?
     ) {
         delegate.doInvalidations(
+            drawChanged = delegate.updateDraw(color, brush, alpha, style),
             textChanged = delegate.updateText(
                 text = text
             ),
@@ -146,7 +159,7 @@
                 onTextLayout = onTextLayout,
                 onPlaceholderLayout = onPlaceholderLayout,
                 selectionController = selectionController
-            )
+            ),
         )
         // we always relayout when we're selectable
         invalidateMeasurement()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt
index 6232e52..3058dfa 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringElement.kt
@@ -18,6 +18,8 @@
 
 import androidx.compose.foundation.text.DefaultMinLines
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.text.AnnotatedString
@@ -26,6 +28,7 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.FloatLambda
 
 /**
  * Modifier element for any Text with [AnnotatedString] or [onTextLayout] parameters
@@ -43,7 +46,10 @@
     private val minLines: Int = DefaultMinLines,
     private val placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
     private val onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
-    private val selectionController: SelectionController? = null
+    private val selectionController: SelectionController? = null,
+    private val color: ColorLambda? = null,
+    private val brush: (() -> Brush)? = null,
+    private val alpha: FloatLambda? = null,
 ) : ModifierNodeElement<TextAnnotatedStringNode>() {
 
     override fun create(): TextAnnotatedStringNode = TextAnnotatedStringNode(
@@ -57,11 +63,15 @@
         minLines,
         placeholders,
         onPlaceholderLayout,
-        selectionController
+        selectionController,
+        color,
+        brush,
+        alpha
     )
 
     override fun update(node: TextAnnotatedStringNode) {
         node.doInvalidations(
+            drawChanged = node.updateDraw(color, brush, alpha, style),
             textChanged = node.updateText(
                 text = text
             ),
@@ -88,7 +98,10 @@
         if (other !is TextAnnotatedStringElement) return false
 
         // these three are most likely to actually change
-        if (text != other.text) return false
+        if (color != other.color) return false
+        if (brush != other.brush) return false
+        if (alpha != other.alpha) return false
+        if (text != other.text) return false /* expensive to check, do it after color */
         if (style != other.style) return false
         if (placeholders != other.placeholders) return false
 
@@ -119,6 +132,9 @@
         result = 31 * result + (placeholders?.hashCode() ?: 0)
         result = 31 * result + (onPlaceholderLayout?.hashCode() ?: 0)
         result = 31 * result + (selectionController?.hashCode() ?: 0)
+        result = 31 * result + (color?.hashCode() ?: 0)
+        result = 31 * result + (brush?.hashCode() ?: 0)
+        result = 31 * result + (alpha?.hashCode() ?: 0)
         return result
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
index 9416b99..e192fa6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
@@ -21,7 +21,9 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.Fill
@@ -54,6 +56,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.FloatLambda
 import kotlin.math.roundToInt
 
 /**
@@ -70,7 +73,10 @@
     private var minLines: Int = DefaultMinLines,
     private var placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
     private var onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
-    private var selectionController: SelectionController? = null
+    private var selectionController: SelectionController? = null,
+    private var overrideColor: ColorLambda? = null,
+    private var overrideBrush: (() -> Brush)? = null,
+    private var overrideAlpha: FloatLambda? = null
 ) : Modifier.Node(), LayoutModifierNode, DrawModifierNode, SemanticsModifierNode {
     private var baselineCache: Map<AlignmentLine, Int>? = null
 
@@ -97,6 +103,32 @@
     }
 
     /**
+     * Element has draw parameters to update
+     */
+    fun updateDraw(
+        color: ColorLambda?,
+        brush: (() -> Brush)?,
+        alpha: FloatLambda?,
+        style: TextStyle
+    ): Boolean {
+        var changed = false
+        if (color != this.overrideColor) {
+            changed = true
+        }
+        if (brush !== this.overrideBrush) {
+            changed = true
+        }
+        if (brush != null && alpha != this.overrideAlpha) {
+            changed = true
+        }
+        overrideBrush = brush
+        overrideAlpha = alpha
+        overrideColor = color
+        changed = changed || !style.hasSameDrawAffectingAttributes(this.style)
+        return changed
+    }
+
+    /**
      * Element has text parameters to update
      */
     fun updateText(text: AnnotatedString): Boolean {
@@ -186,6 +218,7 @@
      * Do appropriate invalidate calls based on the results of update above.
      */
     fun doInvalidations(
+        drawChanged: Boolean,
         textChanged: Boolean,
         layoutChanged: Boolean,
         callbacksChanged: Boolean
@@ -207,8 +240,11 @@
                 placeholders = placeholders
             )
             invalidateMeasurement()
+            invalidateDraw()
         }
-        invalidateDraw()
+        if (drawChanged) {
+            invalidateDraw()
+        }
     }
 
     private var _semanticsConfiguration: SemanticsConfiguration? = null
@@ -370,9 +406,10 @@
                 val textDecoration = style.textDecoration ?: TextDecoration.None
                 val shadow = style.shadow ?: Shadow.None
                 val drawStyle = style.drawStyle ?: Fill
-                val brush = style.brush
+                val brush = overrideBrush?.invoke() ?: style.brush
                 if (brush != null) {
-                    val alpha = style.alpha
+                    val localAlphaResolved = overrideAlpha?.invoke() ?: Float.NaN
+                    val alpha = if (!localAlphaResolved.isNaN()) localAlphaResolved else style.alpha
                     localParagraph.paint(
                         canvas = canvas,
                         brush = brush,
@@ -382,7 +419,10 @@
                         decoration = textDecoration
                     )
                 } else {
-                    val color = if (style.color.isSpecified) {
+                    val overrideColorVal = overrideColor?.invoke() ?: Color.Unspecified
+                    val color = if (overrideColorVal.isSpecified) {
+                        overrideColorVal
+                    } else if (style.color.isSpecified) {
                         style.color
                     } else {
                         Color.Black
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleElement.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleElement.kt
index bb846b3..3d9da8f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleElement.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleElement.kt
@@ -17,12 +17,15 @@
 package androidx.compose.foundation.text.modifiers
 
 import androidx.compose.foundation.text.DefaultMinLines
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.FloatLambda
 
 /**
  * Modifier element for any Text with [AnnotatedString] or [onTextLayout] parameters
@@ -37,6 +40,9 @@
     private val softWrap: Boolean = true,
     private val maxLines: Int = Int.MAX_VALUE,
     private val minLines: Int = DefaultMinLines,
+    private val color: ColorLambda? = null,
+    private val brush: (() -> Brush)? = null,
+    private val alpha: FloatLambda? = null,
 ) : ModifierNodeElement<TextStringSimpleNode>() {
 
     override fun create(): TextStringSimpleNode = TextStringSimpleNode(
@@ -46,11 +52,20 @@
         overflow,
         softWrap,
         maxLines,
-        minLines
+        minLines,
+        color,
+        brush,
+        alpha
     )
 
     override fun update(node: TextStringSimpleNode) {
         node.doInvalidations(
+            drawChanged = node.updateDraw(
+                color,
+                brush,
+                alpha,
+                style
+            ),
             textChanged = node.updateText(
                 text = text
             ),
@@ -71,7 +86,10 @@
         if (other !is TextStringSimpleElement) return false
 
         // these three are most likely to actually change
-        if (text != other.text) return false
+        if (color != other.color) return false
+        if (brush != other.brush) return false
+        if (alpha != other.alpha) return false
+        if (text != other.text) return false /* expensive to check, do after color */
         if (style != other.style) return false
 
         // these are equally unlikely to change
@@ -92,6 +110,9 @@
         result = 31 * result + softWrap.hashCode()
         result = 31 * result + maxLines
         result = 31 * result + minLines
+        result = 31 * result + (color?.hashCode() ?: 0)
+        result = 31 * result + (brush?.hashCode() ?: 0)
+        result = 31 * result + (alpha?.hashCode() ?: 0)
         return result
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index a3e87e0..cf5a5a1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -21,7 +21,9 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorLambda
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.Fill
@@ -53,6 +55,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.FloatLambda
 import kotlin.math.roundToInt
 
 /**
@@ -69,7 +72,10 @@
     private var overflow: TextOverflow = TextOverflow.Clip,
     private var softWrap: Boolean = true,
     private var maxLines: Int = Int.MAX_VALUE,
-    private var minLines: Int = DefaultMinLines
+    private var minLines: Int = DefaultMinLines,
+    private var overrideColor: ColorLambda? = null,
+    private var overrideBrush: (() -> Brush)? = null,
+    private var overrideAlpha: FloatLambda? = null
 ) : Modifier.Node(), LayoutModifierNode, DrawModifierNode, SemanticsModifierNode {
     private var baselineCache: Map<AlignmentLine, Int>? = null
 
@@ -94,6 +100,29 @@
         return layoutCache.also { it.density = density }
     }
 
+    fun updateDraw(
+        color: ColorLambda?,
+        brush: (() -> Brush)?,
+        alpha: FloatLambda?,
+        style: TextStyle
+    ): Boolean {
+        var changed = false
+        if (color != this.overrideColor) {
+            changed = true
+        }
+        if (brush !== this.overrideBrush) {
+            changed = true
+        }
+        if (brush != null && alpha != this.overrideAlpha) {
+            changed = true
+        }
+        overrideBrush = brush
+        overrideAlpha = alpha
+        overrideColor = color
+        changed = changed || !style.hasSameDrawAffectingAttributes(this.style)
+        return changed
+    }
+
     /**
      * Element has text params to update
      */
@@ -151,6 +180,7 @@
      * request invalidate based on the results of [updateText] and [updateLayoutRelatedArgs]
      */
     fun doInvalidations(
+        drawChanged: Boolean,
         textChanged: Boolean,
         layoutChanged: Boolean
     ) {
@@ -170,8 +200,11 @@
                 minLines = minLines
             )
             invalidateMeasurement()
+            invalidateDraw()
         }
-        invalidateDraw()
+        if (drawChanged) {
+            invalidateDraw()
+        }
     }
 
     private var _semanticsConfiguration: SemanticsConfiguration? = null
@@ -289,19 +322,23 @@
                 val textDecoration = style.textDecoration ?: TextDecoration.None
                 val shadow = style.shadow ?: Shadow.None
                 val drawStyle = style.drawStyle ?: Fill
-                val brush = style.brush
+                val brush = overrideBrush?.invoke() ?: style.brush
                 if (brush != null) {
-                    val alpha = style.alpha
+                    val localAlphaResolved = overrideAlpha?.invoke() ?: Float.NaN
+                    val alpha = if (!localAlphaResolved.isNaN()) localAlphaResolved else style.alpha
                     localParagraph.paint(
-                        canvas = canvas,
-                        brush = brush,
                         alpha = alpha,
+                        brush = brush,
+                        canvas = canvas,
                         shadow = shadow,
                         drawStyle = drawStyle,
                         textDecoration = textDecoration
                     )
                 } else {
-                    val color = if (style.color.isSpecified) {
+                    val overrideColorVal = overrideColor?.invoke() ?: Color.Unspecified
+                    val color = if (overrideColorVal.isSpecified) {
+                        overrideColorVal
+                    } else if (style.color.isSpecified) {
                         style.color
                     } else {
                         Color.Black
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
index 2141a88..9533450 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/BasicTextPaparazziTest.kt
@@ -25,9 +25,14 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
@@ -45,6 +50,54 @@
     val paparazzi = androidxPaparazzi()
 
     @Test
+    fun colorChangingState_changesColor() {
+        paparazzi.snapshot {
+            val color = remember { mutableStateOf(Color.Red) }
+            BasicText(
+                "ABCD",
+                color = { color.value }
+            )
+            SideEffect {
+                color.value = Color.Yellow
+            }
+        }
+    }
+
+    @Test
+    fun colorChangingState_changesColor_annotatedString() {
+        paparazzi.snapshot {
+            val color = remember { mutableStateOf(Color.Red) }
+            BasicText(
+                AnnotatedString("ABCD"),
+                color = { color.value }
+            )
+            SideEffect {
+                color.value = Color.Yellow
+            }
+        }
+    }
+
+    @Test
+    fun brushState_changesBrush() {
+        paparazzi.snapshot {
+            val brush = remember { mutableStateOf(Brush.linearGradient(listOf(Color.Gray))) }
+            Column {
+                BasicText(
+                    AnnotatedString("Annotated"),
+                    brush = { brush.value }
+                )
+                BasicText(
+                    "String",
+                    brush = { brush.value }
+                )
+            }
+            SideEffect {
+                brush.value = Brush.sweepGradient(listOf(Color.Gray, Color.Green, Color.Gray))
+            }
+        }
+    }
+
+    @Test
     fun overflowEllipsis_doesEllipsis_whenInPreferredWrapContent() {
         paparazzi.snapshot {
             // b/275369323
@@ -99,16 +152,21 @@
             CompositionLocalProvider(
                 LocalLayoutDirection provides LayoutDirection.Rtl
             ) {
-                ConstraintLayout(Modifier.fillMaxWidth().background(Color.Green)) {
+                ConstraintLayout(
+                    Modifier
+                        .fillMaxWidth()
+                        .background(Color.Green)) {
                     val (title, progressBar, expander) = createRefs()
                     BasicText(
                         text = "Locale-aware Text",
-                        modifier = Modifier.constrainAs(title) {
-                            top.linkTo(parent.top)
-                            start.linkTo(parent.start)
-                            end.linkTo(expander.start)
-                            width = Dimension.fillToConstraints
-                        }.border(2.dp, Color.Red)
+                        modifier = Modifier
+                            .constrainAs(title) {
+                                top.linkTo(parent.top)
+                                start.linkTo(parent.start)
+                                end.linkTo(expander.start)
+                                width = Dimension.fillToConstraints
+                            }
+                            .border(2.dp, Color.Red)
                     )
                     Box(
                         modifier = Modifier
diff --git a/compose/integration-tests/docs-snippets/build.gradle b/compose/integration-tests/docs-snippets/build.gradle
index b2e864f..20bc90f 100644
--- a/compose/integration-tests/docs-snippets/build.gradle
+++ b/compose/integration-tests/docs-snippets/build.gradle
@@ -42,14 +42,8 @@
     implementation(project(":compose:ui:ui-viewbinding"))
     implementation(project(":navigation:navigation-compose"))
     implementation("androidx.activity:activity-compose:1.3.1")
-    implementation(project(":lifecycle:lifecycle-viewmodel-compose"))
-    // old version of common-java8 conflicts with newer version, because both have
-    // DefaultLifecycleEventObserver.
-    // Outside of androidx this is resolved via constraint added to lifecycle-common,
-    // but it doesn't work in androidx.
-    // See aosp/1804059
-    implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
-    implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
     implementation(project(":paging:paging-compose"))
 
     implementation(libs.kotlinStdlib)
diff --git a/compose/integration-tests/macrobenchmark-target/build.gradle b/compose/integration-tests/macrobenchmark-target/build.gradle
index e1368a6..f521117 100644
--- a/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -34,6 +34,7 @@
     implementation(project(":compose:foundation:foundation-layout"))
     implementation(project(":compose:foundation:foundation"))
     implementation(project(":compose:material:material"))
+    implementation(project(":compose:material3:material3"))
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:runtime:runtime-tracing"))
     implementation(project(":compose:ui:ui"))
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index 8d6b34e..0b745aa 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -72,6 +72,14 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".BaselineProfileActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="androidx.compose.integration.macrobenchmark.target.BASELINE_PROFILE_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".NestedListsActivity"
             android:exported="true">
             <intent-filter>
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/BaselineProfileActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/BaselineProfileActivity.kt
new file mode 100644
index 0000000..a94f36b
--- /dev/null
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/BaselineProfileActivity.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.integration.macrobenchmark.target
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material.Card
+import androidx.compose.material.Checkbox
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+
+class BaselineProfileActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val itemCount = intent.getIntExtra(EXTRA_ITEM_COUNT, 3000)
+        val entries = List(itemCount) { Entry("Item $it") }
+
+        setContent {
+            LazyColumn(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .semantics { contentDescription = "IamLazy" }
+            ) {
+                itemsIndexed(entries) { index, item ->
+                    if (index % 2 == 0) {
+                        M3ListRow(entry = item)
+                    } else {
+                        ListRow(item)
+                    }
+                }
+            }
+        }
+
+        launchIdlenessTracking()
+    }
+
+    companion object {
+        const val EXTRA_ITEM_COUNT = "ITEM_COUNT"
+    }
+}
+
+@Composable
+private fun ListRow(entry: Entry) {
+    Card(modifier = Modifier.padding(8.dp)) {
+        Row {
+            Text(
+                text = entry.contents,
+                modifier = Modifier.padding(16.dp)
+            )
+            Spacer(modifier = Modifier.weight(1f, fill = true))
+            Checkbox(
+                checked = false,
+                onCheckedChange = {},
+                modifier = Modifier.padding(16.dp)
+            )
+        }
+    }
+}
+
+@Composable
+internal fun M3ListRow(entry: Entry) {
+    androidx.compose.material3.Card(modifier = Modifier.padding(8.dp)) {
+        Row {
+            androidx.compose.material3.Text(
+                text = entry.contents,
+                modifier = Modifier.padding(16.dp)
+            )
+            Spacer(modifier = Modifier.weight(1f, fill = true))
+            androidx.compose.material3.Checkbox(
+                checked = false,
+                onCheckedChange = {},
+                modifier = Modifier.padding(16.dp)
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt
index ad73439..b3b12d7 100644
--- a/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt
+++ b/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/compose/integration/macrobenchmark/SmallListBaselineProfile.kt
@@ -42,7 +42,7 @@
             intent.apply {
                 setPackage(packageName)
                 action =
-                    "androidx.compose.integration.macrobenchmark.target.LAZY_COLUMN_ACTIVITY"
+                    "androidx.compose.integration.macrobenchmark.target.BASELINE_PROFILE_ACTIVITY"
                 putExtra("ITEM_COUNT", 200)
             }
             startActivityAndWait(intent)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TextTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TextTest.kt
index 3c7b4da..5fea47d 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TextTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TextTest.kt
@@ -133,7 +133,6 @@
 
     @Test
     fun settingParametersExplicitly() {
-        var textColor: Color? = null
         var textAlign: TextAlign? = null
         var fontSize: TextUnit? = null
         var fontStyle: FontStyle? = null
@@ -155,7 +154,6 @@
                         fontStyle = expectedFontStyle,
                         letterSpacing = expectedLetterSpacing,
                         onTextLayout = {
-                            textColor = it.layoutInput.style.color
                             textAlign = it.layoutInput.style.textAlign
                             fontSize = it.layoutInput.style.fontSize
                             fontStyle = it.layoutInput.style.fontStyle
@@ -168,7 +166,6 @@
 
         rule.runOnIdle {
             // explicit parameters should override values from the style.
-            Truth.assertThat(textColor).isEqualTo(expectedColor)
             Truth.assertThat(textAlign).isEqualTo(expectedTextAlign)
             Truth.assertThat(fontSize).isEqualTo(expectedFontSize)
             Truth.assertThat(fontStyle).isEqualTo(expectedFontStyle)
@@ -179,7 +176,6 @@
     // Not really an expected use-case, but we should ensure the behavior here is consistent.
     @Test
     fun settingColorAndTextStyle() {
-        var textColor: Color? = null
         var textAlign: TextAlign? = null
         var fontSize: TextUnit? = null
         var fontStyle: FontStyle? = null
@@ -202,7 +198,6 @@
                         letterSpacing = expectedLetterSpacing,
                         style = ExpectedTextStyle,
                         onTextLayout = {
-                            textColor = it.layoutInput.style.color
                             textAlign = it.layoutInput.style.textAlign
                             fontSize = it.layoutInput.style.fontSize
                             fontStyle = it.layoutInput.style.fontStyle
@@ -215,7 +210,6 @@
 
         rule.runOnIdle {
             // explicit parameters should override values from the style.
-            Truth.assertThat(textColor).isEqualTo(expectedColor)
             Truth.assertThat(textAlign).isEqualTo(expectedTextAlign)
             Truth.assertThat(fontSize).isEqualTo(expectedFontSize)
             Truth.assertThat(fontStyle).isEqualTo(expectedFontStyle)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
index 618aff5d..aa559c9 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
@@ -24,7 +24,7 @@
 import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.graphics.isSpecified
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.Paragraph
 import androidx.compose.ui.text.TextLayoutResult
@@ -109,18 +109,21 @@
     onTextLayout: ((TextLayoutResult) -> Unit)? = null,
     style: TextStyle = LocalTextStyle.current
 ) {
-
-    val textColor = color.takeOrElse {
-        style.color.takeOrElse {
-            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
-        }
+    // weird coding to avoid groups
+    val localContentColor = LocalContentColor.current
+    val localContentAlpha = LocalContentAlpha.current
+    val overrideColorOrUnspecified: Color = if (color.isSpecified) {
+        color
+    } else if (style.color.isSpecified) {
+        style.color
+    } else {
+        localContentColor.copy(localContentAlpha)
     }
 
     BasicText(
         text = text,
         modifier = modifier,
         style = style.merge(
-            color = textColor,
             fontSize = fontSize,
             fontWeight = fontWeight,
             textAlign = textAlign,
@@ -134,7 +137,8 @@
         overflow = overflow,
         softWrap = softWrap,
         maxLines = maxLines,
-        minLines = minLines
+        minLines = minLines,
+        color = { overrideColorOrUnspecified }
     )
 }
 
@@ -257,17 +261,21 @@
     onTextLayout: (TextLayoutResult) -> Unit = {},
     style: TextStyle = LocalTextStyle.current
 ) {
-    val textColor = color.takeOrElse {
-        style.color.takeOrElse {
-            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
-        }
+    // weird coding to avoid groups
+    val localContentColor = LocalContentColor.current
+    val localContentAlpha = LocalContentAlpha.current
+    val overrideColorOrUnspecified = if (color.isSpecified) {
+        color
+    } else if (style.color.isSpecified) {
+        style.color
+    } else {
+        localContentColor.copy(localContentAlpha)
     }
 
     BasicText(
         text = text,
         modifier = modifier,
         style = style.merge(
-            color = textColor,
             fontSize = fontSize,
             fontWeight = fontWeight,
             textAlign = textAlign,
@@ -282,7 +290,8 @@
         softWrap = softWrap,
         maxLines = maxLines,
         minLines = minLines,
-        inlineContent = inlineContent
+        inlineContent = inlineContent,
+        color = { overrideColorOrUnspecified }
     )
 }
 
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index f2acb079..f300862 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -37,7 +37,7 @@
          */
         implementation(libs.kotlinStdlibCommon)
         implementation("androidx.activity:activity-compose:1.5.0")
-        implementation("androidx.compose.animation:animation-core:1.4.2")
+        implementation(project(":compose:animation:animation"))
         implementation(project(":compose:foundation:foundation-layout"))
         implementation("androidx.compose.ui:ui-util:1.4.2")
         api(project(":compose:foundation:foundation"))
diff --git a/compose/material3/material3/samples/build.gradle b/compose/material3/material3/samples/build.gradle
index 76cd811..075b5f4 100644
--- a/compose/material3/material3/samples/build.gradle
+++ b/compose/material3/material3/samples/build.gradle
@@ -35,7 +35,7 @@
     implementation(project(":compose:foundation:foundation"))
     implementation(project(":compose:foundation:foundation-layout"))
     implementation("androidx.compose.material:material:1.2.1")
-    implementation("androidx.compose.material:material-icons-extended:1.2.1")
+    implementation("androidx.compose.material:material-icons-extended:1.4.0")
     implementation(project(":compose:material3:material3"))
     implementation("androidx.compose.runtime:runtime:1.2.1")
     implementation("androidx.compose.ui:ui:1.2.1")
diff --git a/compose/material3/material3/src/androidMain/baseline-prof.txt b/compose/material3/material3/src/androidMain/baseline-prof.txt
new file mode 100644
index 0000000..6a2e2b7
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/baseline-prof.txt
@@ -0,0 +1,27 @@
+# Baseline profile rules for androidx.compose.material3
+# =============================================
+
+HSPLandroidx/compose/material3/CardColors;->**(**)**
+HSPLandroidx/compose/material3/CardElevation;->**(**)**
+HSPLandroidx/compose/material3/CardKt**->**(**)**
+HSPLandroidx/compose/material3/CheckDrawingCache;->**(**)**
+HSPLandroidx/compose/material3/CheckboxColors;->**(**)**
+HSPLandroidx/compose/material3/CheckboxKt**->**(**)**
+HSPLandroidx/compose/material3/ColorScheme**->**(**)**
+HSPLandroidx/compose/material3/ContentColorKt;->**(**)**
+HSPLandroidx/compose/material3/DefaultPlatformTextStyle_androidKt;->**(**)**
+HSPLandroidx/compose/material3/InteractiveComponentSizeKt;->**(**)**
+HSPLandroidx/compose/material3/MinimumInteractiveComponentSizeModifier**->**(**)**
+HSPLandroidx/compose/material3/ShapeDefaults;->**(**)**
+HSPLandroidx/compose/material3/Shapes;->**(**)**
+HSPLandroidx/compose/material3/ShapesKt**->**(**)**
+HSPLandroidx/compose/material3/SurfaceKt**->**(**)**
+HSPLandroidx/compose/material3/TextKt**->**(**)**
+HSPLandroidx/compose/material3/CheckboxTokens;->**(**)**
+HSPLandroidx/compose/material3/ColorDarkTokens;->**(**)**
+HSPLandroidx/compose/material3/ColorLightTokens;->**(**)**
+HSPLandroidx/compose/material3/ElevationTokens;->**(**)**
+HSPLandroidx/compose/material3/FilledCardTokens;->**(**)**
+HSPLandroidx/compose/material3/PaletteTokens;->**(**)**
+HSPLandroidx/compose/material3/ShapeTokens;->**(**)**
+HSPLandroidx/compose/material3/TypographyTokens;->**(**)**
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index 518f5d9..35bd561 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -16,7 +16,8 @@
 
 package androidx.compose.material3
 
-import androidx.compose.animation.Crossfade
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.SizeTransform
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.DecayAnimationSpec
 import androidx.compose.animation.core.Spring
@@ -27,6 +28,9 @@
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.FlingBehavior
@@ -1080,10 +1084,10 @@
     Column(
         modifier = modifier
             .sizeIn(minWidth = DatePickerModalTokens.ContainerWidth)
-        .semantics {
-            @Suppress("DEPRECATION")
-            isContainer = true
-        }
+            .semantics {
+                @Suppress("DEPRECATION")
+                isContainer = true
+            }
     ) {
         DatePickerHeader(
             modifier = Modifier,
@@ -1164,15 +1168,55 @@
     selectableDates: SelectableDates,
     colors: DatePickerColors
 ) {
-    // TODO(b/266480386): Apply the motion spec for this once we have it. Consider replacing this
-    //  with AnimatedContent when it's out of experimental.
-    Crossfade(
+    // Parallax effect offset that will slightly scroll in and out the navigation part of the picker
+    // when the display mode changes.
+    val parallaxTarget = with(LocalDensity.current) { -48.dp.roundToPx() }
+    AnimatedContent(
         targetState = displayMode,
-        animationSpec = spring(),
         modifier = Modifier.semantics {
             @Suppress("DEPRECATION")
             isContainer = true
-        }) { mode ->
+        },
+        transitionSpec = {
+            // When animating the input mode, fade out the calendar picker and slide in the text
+            // field from the bottom with a delay to show up after the picker is hidden.
+            if (targetState == DisplayMode.Input) {
+                slideInVertically { height -> height } + fadeIn(
+                    animationSpec = tween(
+                        durationMillis = MotionTokens.DurationShort2.toInt(),
+                        delayMillis = MotionTokens.DurationShort2.toInt()
+                    )
+                ) togetherWith fadeOut(
+                    tween(durationMillis = MotionTokens.DurationShort2.toInt())
+                ) + slideOutVertically(targetOffsetY = { _ -> parallaxTarget })
+            } else {
+                // When animating the picker mode, slide out text field and fade in calendar
+                // picker with a delay to show up after the text field is hidden.
+                slideInVertically(
+                    animationSpec = tween(
+                        delayMillis = MotionTokens.DurationShort1.toInt()
+                    ),
+                    initialOffsetY = { _ -> parallaxTarget }
+                ) + fadeIn(
+                    animationSpec = tween(
+                        durationMillis = MotionTokens.DurationShort2.toInt(),
+                        delayMillis = MotionTokens.DurationShort2.toInt()
+                    )
+                ) togetherWith slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }) +
+                    fadeOut(animationSpec = tween(MotionTokens.DurationShort2.toInt()))
+            }.using(
+                SizeTransform(
+                    clip = true,
+                    sizeAnimationSpec = { _, _ ->
+                        tween(
+                            MotionTokens.DurationLong2.toInt(),
+                            easing = MotionTokens.EasingEmphasizedDecelerateCubicBezier
+                        )
+                    })
+            )
+        },
+        label = "DatePickerDisplayModeAnimation"
+    ) { mode ->
         when (mode) {
             DisplayMode.Picker -> DatePickerContent(
                 selectedDateMillis = selectedDateMillis,
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 7b32178..ac18e8a 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -15,6 +15,11 @@
     method public static androidx.compose.ui.graphics.ColorFilter asComposeColorFilter(android.graphics.ColorFilter);
   }
 
+  public final class AndroidColorSpace_androidKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.colorspace.ColorSpace toComposeColorSpace(android.graphics.ColorSpace);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.ColorSpace toFrameworkColorSpace(androidx.compose.ui.graphics.colorspace.ColorSpace);
+  }
+
   public final class AndroidImageBitmap_androidKt {
     method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageBitmap);
     method public static androidx.compose.ui.graphics.ImageBitmap asImageBitmap(android.graphics.Bitmap);
@@ -377,6 +382,10 @@
     method @androidx.compose.runtime.Stable public static int toArgb(long);
   }
 
+  public fun interface ColorLambda {
+    method public long invoke();
+  }
+
   @kotlin.jvm.JvmInline public final value class ColorMatrix {
     ctor public ColorMatrix(optional float[] values);
     method public void convertRgbToYuv();
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index f7a4628..d3ac388 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -15,6 +15,11 @@
     method public static androidx.compose.ui.graphics.ColorFilter asComposeColorFilter(android.graphics.ColorFilter);
   }
 
+  public final class AndroidColorSpace_androidKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.colorspace.ColorSpace toComposeColorSpace(android.graphics.ColorSpace);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.ColorSpace toFrameworkColorSpace(androidx.compose.ui.graphics.colorspace.ColorSpace);
+  }
+
   public final class AndroidImageBitmap_androidKt {
     method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageBitmap);
     method public static androidx.compose.ui.graphics.ImageBitmap asImageBitmap(android.graphics.Bitmap);
@@ -377,6 +382,10 @@
     method @androidx.compose.runtime.Stable public static int toArgb(long);
   }
 
+  public fun interface ColorLambda {
+    method public long invoke();
+  }
+
   @kotlin.jvm.JvmInline public final value class ColorMatrix {
     ctor public ColorMatrix(optional float[] values);
     method public void convertRgbToYuv();
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 172d06a..ed16b10 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -45,6 +45,11 @@
     method public static androidx.compose.ui.graphics.ColorFilter asComposeColorFilter(android.graphics.ColorFilter);
   }
 
+  public final class AndroidColorSpace_androidKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.colorspace.ColorSpace toComposeColorSpace(android.graphics.ColorSpace);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.ColorSpace toFrameworkColorSpace(androidx.compose.ui.graphics.colorspace.ColorSpace);
+  }
+
   public final class AndroidImageBitmap_androidKt {
     method public static android.graphics.Bitmap asAndroidBitmap(androidx.compose.ui.graphics.ImageBitmap);
     method public static androidx.compose.ui.graphics.ImageBitmap asImageBitmap(android.graphics.Bitmap);
@@ -408,6 +413,10 @@
     method @androidx.compose.runtime.Stable public static int toArgb(long);
   }
 
+  public fun interface ColorLambda {
+    method public long invoke();
+  }
+
   @kotlin.jvm.JvmInline public final value class ColorMatrix {
     ctor public ColorMatrix(optional float[] values);
     method public void convertRgbToYuv();
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt
new file mode 100644
index 0000000..25fdfe8
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidColorSpaceTest.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import android.graphics.ColorSpace
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.graphics.AndroidColorSpaceTest.ColorSpaceHelper.Companion.colorSpaceTestHelper
+import androidx.compose.ui.graphics.colorspace.ColorSpaces
+import androidx.compose.ui.graphics.colorspace.Rgb
+import androidx.compose.ui.graphics.colorspace.TransferParameters
+import androidx.compose.ui.graphics.colorspace.WhitePoint
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AndroidColorSpaceTest {
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testSrgbColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.Srgb, // Compose
+            ColorSpace.get(ColorSpace.Named.SRGB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testAcesColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.Aces, // Compose
+            ColorSpace.get(ColorSpace.Named.ACES) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testAcescgColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.Acescg, // Compose
+            ColorSpace.get(ColorSpace.Named.ACESCG) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testAdobeRgbColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.AdobeRgb, // Compose
+            ColorSpace.get(ColorSpace.Named.ADOBE_RGB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testBt2020Colorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.Bt2020, // Compose
+            ColorSpace.get(ColorSpace.Named.BT2020) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testBt709Colorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.Bt709, // Compose
+            ColorSpace.get(ColorSpace.Named.BT709) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testCieLabColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.CieLab, // Compose
+            ColorSpace.get(ColorSpace.Named.CIE_LAB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testCieXyzColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.CieXyz, // Compose
+            ColorSpace.get(ColorSpace.Named.CIE_XYZ) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testDciP3Colorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.DciP3, // Compose
+            ColorSpace.get(ColorSpace.Named.DCI_P3) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testDisplayP3Colorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.DisplayP3, // Compose
+            ColorSpace.get(ColorSpace.Named.DISPLAY_P3) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testExtendedSrgbColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.ExtendedSrgb, // Compose
+            ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testLinearExtendedSrgbColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.LinearExtendedSrgb, // Compose
+            ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testLinearSrgbColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.LinearSrgb, // Compose
+            ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testNtsc1953Colorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.Ntsc1953, // Compose
+            ColorSpace.get(ColorSpace.Named.NTSC_1953) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testProPhotoRgbColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.ProPhotoRgb, // Compose
+            ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testSmpteCColorspace() {
+        colorSpaceTestHelper(
+            ColorSpaces.SmpteC, // Compose
+            ColorSpace.get(ColorSpace.Named.SMPTE_C) // Framework
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testUnknownColorspace3WhitePointValues() {
+        val name = "MyCustomColorSpace"
+        val whitePoint = floatArrayOf(1.0f, 2.0f, 3.0f)
+        val transferParameters = ColorSpace.Rgb.TransferParameters(
+            0.1, // a
+            0.2, // b
+            0.3, // c
+            0.4, // d
+            0.5, // e
+            0.6, // f
+            0.7 // g
+        )
+        val primaries = floatArrayOf(1f, 2f, 3f, 4f, 5f, 6f)
+        colorSpaceTestHelper(
+            androidx.compose.ui.graphics.colorspace.Rgb(
+                name = name,
+                primaries = primaries,
+                WhitePoint(1.0f, 2.0f, 3.0f),
+                TransferParameters(0.7, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
+            ),
+            ColorSpace.Rgb(
+                name,
+                primaries,
+                whitePoint,
+                transferParameters
+            )
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testUnknownColorspace2WhitePointValues() {
+        val name = "MyCustomColorSpace"
+        val whitePoint = floatArrayOf(1.0f, 2.0f)
+        val transferParameters = ColorSpace.Rgb.TransferParameters(
+            0.1, // a
+            0.2, // b
+            0.3, // c
+            0.4, // d
+            0.5, // e
+            0.6, // f
+            0.7 // g
+        )
+        val primaries = floatArrayOf(1f, 2f, 3f, 4f, 5f, 6f)
+
+        colorSpaceTestHelper(
+            Rgb(
+                name = name,
+                primaries = primaries,
+                WhitePoint(1.0f, 2.0f),
+                TransferParameters(0.7, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
+            ),
+            ColorSpace.Rgb(
+                name,
+                primaries,
+                whitePoint,
+                transferParameters
+            )
+        )
+    }
+
+    // Helper class to avoid NoSuchClassExceptions being thrown when tests are run on an older
+    // API level that does not understand ColorSpace APIs
+    internal class ColorSpaceHelper {
+        companion object {
+            @RequiresApi(Build.VERSION_CODES.O)
+            fun colorSpaceTestHelper(
+                composeColorSpace: androidx.compose.ui.graphics.colorspace.ColorSpace,
+                frameworkColorSpace: ColorSpace
+            ) {
+                Assert.assertEquals(composeColorSpace, frameworkColorSpace.toComposeColorSpace())
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt
index e081e68..ab73407 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/ImageBitmapTest.kt
@@ -16,22 +16,13 @@
 
 package androidx.compose.ui.graphics
 
-import android.graphics.ColorSpace
-import android.graphics.ColorSpace.Named
-import android.graphics.ColorSpace.Rgb
-import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.compose.ui.graphics.ImageBitmapTest.ColorSpaceHelper.Companion.colorSpaceTestHelper
 import androidx.compose.ui.graphics.colorspace.ColorSpaces
-import androidx.compose.ui.graphics.colorspace.TransferParameters
-import androidx.compose.ui.graphics.colorspace.WhitePoint
 import androidx.test.filters.SmallTest
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Test
 import org.junit.runner.RunWith
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -54,227 +45,4 @@
         assertFalse(image.hasAlpha)
         assertEquals(cs, image.colorSpace)
     }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testSrgbColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.Srgb, // Compose
-            ColorSpace.get(Named.SRGB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testAcesColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.Aces, // Compose
-            ColorSpace.get(Named.ACES) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testAcescgColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.Acescg, // Compose
-            ColorSpace.get(Named.ACESCG) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testAdobeRgbColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.AdobeRgb, // Compose
-            ColorSpace.get(Named.ADOBE_RGB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testBt2020Colorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.Bt2020, // Compose
-            ColorSpace.get(Named.BT2020) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testBt709Colorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.Bt709, // Compose
-            ColorSpace.get(Named.BT709) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testCieLabColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.CieLab, // Compose
-            ColorSpace.get(Named.CIE_LAB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testCieXyzColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.CieXyz, // Compose
-            ColorSpace.get(Named.CIE_XYZ) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testDciP3Colorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.DciP3, // Compose
-            ColorSpace.get(Named.DCI_P3) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testDisplayP3Colorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.DisplayP3, // Compose
-            ColorSpace.get(Named.DISPLAY_P3) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testExtendedSrgbColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.ExtendedSrgb, // Compose
-            ColorSpace.get(Named.EXTENDED_SRGB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testLinearExtendedSrgbColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.LinearExtendedSrgb, // Compose
-            ColorSpace.get(Named.LINEAR_EXTENDED_SRGB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testLinearSrgbColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.LinearSrgb, // Compose
-            ColorSpace.get(Named.LINEAR_SRGB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testNtsc1953Colorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.Ntsc1953, // Compose
-            ColorSpace.get(Named.NTSC_1953) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testProPhotoRgbColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.ProPhotoRgb, // Compose
-            ColorSpace.get(Named.PRO_PHOTO_RGB) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testSmpteCColorspace() {
-        colorSpaceTestHelper(
-            ColorSpaces.SmpteC, // Compose
-            ColorSpace.get(Named.SMPTE_C) // Framework
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testUnknownColorspace3WhitePointValues() {
-        val name = "MyCustomColorSpace"
-        val whitePoint = floatArrayOf(1.0f, 2.0f, 3.0f)
-        val transferParameters = Rgb.TransferParameters(
-            0.1, // a
-            0.2, // b
-            0.3, // c
-            0.4, // d
-            0.5, // e
-            0.6, // f
-            0.7 // g
-        )
-        val primaries = floatArrayOf(1f, 2f, 3f, 4f, 5f, 6f)
-        colorSpaceTestHelper(
-            androidx.compose.ui.graphics.colorspace.Rgb(
-                name = name,
-                primaries = primaries,
-                WhitePoint(1.0f, 2.0f, 3.0f),
-                TransferParameters(0.7, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
-            ),
-            Rgb(
-                name,
-                primaries,
-                whitePoint,
-                transferParameters
-            )
-        )
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    @Test
-    fun testUnknownColorspace2WhitePointValues() {
-        val name = "MyCustomColorSpace"
-        val whitePoint = floatArrayOf(1.0f, 2.0f)
-        val transferParameters = Rgb.TransferParameters(
-            0.1, // a
-            0.2, // b
-            0.3, // c
-            0.4, // d
-            0.5, // e
-            0.6, // f
-            0.7 // g
-        )
-        val primaries = floatArrayOf(1f, 2f, 3f, 4f, 5f, 6f)
-
-        colorSpaceTestHelper(
-            androidx.compose.ui.graphics.colorspace.Rgb(
-                name = name,
-                primaries = primaries,
-                WhitePoint(1.0f, 2.0f),
-                TransferParameters(0.7, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
-            ),
-            Rgb(
-                name,
-                primaries,
-                whitePoint,
-                transferParameters
-            )
-        )
-    }
-
-    // Helper class to avoid NoSuchClassExceptions being thrown when tests are run on an older
-    // API level that does not understand ColorSpace APIs
-    internal class ColorSpaceHelper {
-        companion object {
-            @RequiresApi(Build.VERSION_CODES.O)
-            fun colorSpaceTestHelper(
-                composeColorSpace: androidx.compose.ui.graphics.colorspace.ColorSpace,
-                frameworkColorSpace: ColorSpace
-            ) {
-                with(Api26Bitmap) {
-                    assertEquals(composeColorSpace, frameworkColorSpace.composeColorSpace())
-                }
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt
new file mode 100644
index 0000000..66041c1
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidColorSpace.android.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import android.os.Build
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.graphics.colorspace.ColorSpace
+import androidx.compose.ui.graphics.colorspace.ColorSpaces
+import androidx.compose.ui.graphics.colorspace.Rgb
+import androidx.compose.ui.graphics.colorspace.TransferParameters
+import androidx.compose.ui.graphics.colorspace.WhitePoint
+
+/**
+ * Convert the Compose [ColorSpace] into an Android framework [android.graphics.ColorSpace]
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+fun ColorSpace.toFrameworkColorSpace(): android.graphics.ColorSpace =
+        with(ColorSpaceVerificationHelper) {
+            frameworkColorSpace()
+        }
+
+/**
+ * Convert the [android.graphics.ColorSpace] into a Compose [ColorSpace]
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+fun android.graphics.ColorSpace.toComposeColorSpace() =
+    with(ColorSpaceVerificationHelper) {
+        composeColorSpace()
+    }
+
+@RequiresApi(Build.VERSION_CODES.O)
+private object ColorSpaceVerificationHelper {
+
+    @DoNotInline
+    @JvmStatic
+    @RequiresApi(Build.VERSION_CODES.O)
+    fun ColorSpace.frameworkColorSpace(): android.graphics.ColorSpace {
+        val frameworkNamedSpace = when (this) {
+            ColorSpaces.Srgb -> android.graphics.ColorSpace.Named.SRGB
+            ColorSpaces.Aces -> android.graphics.ColorSpace.Named.ACES
+            ColorSpaces.Acescg -> android.graphics.ColorSpace.Named.ACESCG
+            ColorSpaces.AdobeRgb -> android.graphics.ColorSpace.Named.ADOBE_RGB
+            ColorSpaces.Bt2020 -> android.graphics.ColorSpace.Named.BT2020
+            ColorSpaces.Bt709 -> android.graphics.ColorSpace.Named.BT709
+            ColorSpaces.CieLab -> android.graphics.ColorSpace.Named.CIE_LAB
+            ColorSpaces.CieXyz -> android.graphics.ColorSpace.Named.CIE_XYZ
+            ColorSpaces.DciP3 -> android.graphics.ColorSpace.Named.DCI_P3
+            ColorSpaces.DisplayP3 -> android.graphics.ColorSpace.Named.DISPLAY_P3
+            ColorSpaces.ExtendedSrgb -> android.graphics.ColorSpace.Named.EXTENDED_SRGB
+            ColorSpaces.LinearExtendedSrgb ->
+                android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB
+            ColorSpaces.LinearSrgb -> android.graphics.ColorSpace.Named.LINEAR_SRGB
+            ColorSpaces.Ntsc1953 -> android.graphics.ColorSpace.Named.NTSC_1953
+            ColorSpaces.ProPhotoRgb -> android.graphics.ColorSpace.Named.PRO_PHOTO_RGB
+            ColorSpaces.SmpteC -> android.graphics.ColorSpace.Named.SMPTE_C
+            else -> android.graphics.ColorSpace.Named.SRGB
+        }
+        return android.graphics.ColorSpace.get(frameworkNamedSpace)
+    }
+
+    @DoNotInline
+    @JvmStatic
+    @RequiresApi(Build.VERSION_CODES.O)
+    fun android.graphics.ColorSpace.composeColorSpace(): ColorSpace {
+        return when (this.id) {
+            android.graphics.ColorSpace.Named.SRGB.ordinal ->
+                ColorSpaces.Srgb
+            android.graphics.ColorSpace.Named.ACES.ordinal ->
+                ColorSpaces.Aces
+            android.graphics.ColorSpace.Named.ACESCG.ordinal ->
+                ColorSpaces.Acescg
+            android.graphics.ColorSpace.Named.ADOBE_RGB.ordinal ->
+                ColorSpaces.AdobeRgb
+            android.graphics.ColorSpace.Named.BT2020.ordinal ->
+                ColorSpaces.Bt2020
+            android.graphics.ColorSpace.Named.BT709.ordinal ->
+                ColorSpaces.Bt709
+            android.graphics.ColorSpace.Named.CIE_LAB.ordinal ->
+                ColorSpaces.CieLab
+            android.graphics.ColorSpace.Named.CIE_XYZ.ordinal ->
+                ColorSpaces.CieXyz
+            android.graphics.ColorSpace.Named.DCI_P3.ordinal ->
+                ColorSpaces.DciP3
+            android.graphics.ColorSpace.Named.DISPLAY_P3.ordinal ->
+                ColorSpaces.DisplayP3
+            android.graphics.ColorSpace.Named.EXTENDED_SRGB.ordinal ->
+                ColorSpaces.ExtendedSrgb
+            android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB.ordinal ->
+                ColorSpaces.LinearExtendedSrgb
+            android.graphics.ColorSpace.Named.LINEAR_SRGB.ordinal ->
+                ColorSpaces.LinearSrgb
+            android.graphics.ColorSpace.Named.NTSC_1953.ordinal ->
+                ColorSpaces.Ntsc1953
+            android.graphics.ColorSpace.Named.PRO_PHOTO_RGB.ordinal ->
+                ColorSpaces.ProPhotoRgb
+            android.graphics.ColorSpace.Named.SMPTE_C.ordinal ->
+                ColorSpaces.SmpteC
+            else -> {
+                if (this is android.graphics.ColorSpace.Rgb) {
+                    val transferParams = this.transferParameters
+                    val whitePoint = if (this.whitePoint.size == 3) {
+                        WhitePoint(this.whitePoint[0], this.whitePoint[1], this.whitePoint[2])
+                    } else {
+                        WhitePoint(this.whitePoint[0], this.whitePoint[1])
+                    }
+
+                    val composeTransferParams = if (transferParams != null) {
+                        TransferParameters(
+                            gamma = transferParams.g,
+                            a = transferParams.a,
+                            b = transferParams.b,
+                            c = transferParams.c,
+                            d = transferParams.d,
+                            e = transferParams.e,
+                            f = transferParams.f
+                        )
+                    } else {
+                        null
+                    }
+                    Rgb(
+                        name = this.name,
+                        primaries = this.primaries,
+                        whitePoint = whitePoint,
+                        transform = this.transform,
+                        oetf = { x -> this.oetf.applyAsDouble(x) },
+                        eotf = { x -> this.eotf.applyAsDouble(x) },
+                        min = this.getMinValue(0),
+                        max = this.getMaxValue(0),
+                        transferParameters = composeTransferParams,
+                        id = this.id
+                    )
+                } else {
+                    ColorSpaces.Srgb
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.android.kt
index 2e7700c..463d8f3 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidImageBitmap.android.kt
@@ -17,16 +17,12 @@
 package androidx.compose.ui.graphics
 
 import android.graphics.Bitmap
-import android.graphics.ColorSpace.Named
 import android.os.Build
 import android.util.DisplayMetrics
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.graphics.colorspace.ColorSpace
 import androidx.compose.ui.graphics.colorspace.ColorSpaces
-import androidx.compose.ui.graphics.colorspace.Rgb
-import androidx.compose.ui.graphics.colorspace.TransferParameters
-import androidx.compose.ui.graphics.colorspace.WhitePoint
 
 /**
  * Create an [ImageBitmap] from the given [Bitmap]. Note this does
@@ -212,92 +208,5 @@
     @DoNotInline
     @JvmStatic
     internal fun Bitmap.composeColorSpace() =
-        colorSpace?.composeColorSpace() ?: ColorSpaces.Srgb
-
-    @DoNotInline
-    @JvmStatic
-    internal fun ColorSpace.toFrameworkColorSpace(): android.graphics.ColorSpace {
-        val frameworkNamedSpace = when (this) {
-            ColorSpaces.Srgb -> Named.SRGB
-            ColorSpaces.Aces -> Named.ACES
-            ColorSpaces.Acescg -> Named.ACESCG
-            ColorSpaces.AdobeRgb -> Named.ADOBE_RGB
-            ColorSpaces.Bt2020 -> Named.BT2020
-            ColorSpaces.Bt709 -> Named.BT709
-            ColorSpaces.CieLab -> Named.CIE_LAB
-            ColorSpaces.CieXyz -> Named.CIE_XYZ
-            ColorSpaces.DciP3 -> Named.DCI_P3
-            ColorSpaces.DisplayP3 -> Named.DISPLAY_P3
-            ColorSpaces.ExtendedSrgb -> Named.EXTENDED_SRGB
-            ColorSpaces.LinearExtendedSrgb ->
-                Named.LINEAR_EXTENDED_SRGB
-            ColorSpaces.LinearSrgb -> Named.LINEAR_SRGB
-            ColorSpaces.Ntsc1953 -> Named.NTSC_1953
-            ColorSpaces.ProPhotoRgb -> Named.PRO_PHOTO_RGB
-            ColorSpaces.SmpteC -> Named.SMPTE_C
-            else -> Named.SRGB
-        }
-        return android.graphics.ColorSpace.get(frameworkNamedSpace)
-    }
-
-    @DoNotInline
-    @JvmStatic
-    fun android.graphics.ColorSpace.composeColorSpace(): ColorSpace {
-        return when (this.id) {
-            Named.SRGB.ordinal -> ColorSpaces.Srgb
-            Named.ACES.ordinal -> ColorSpaces.Aces
-            Named.ACESCG.ordinal -> ColorSpaces.Acescg
-            Named.ADOBE_RGB.ordinal -> ColorSpaces.AdobeRgb
-            Named.BT2020.ordinal -> ColorSpaces.Bt2020
-            Named.BT709.ordinal -> ColorSpaces.Bt709
-            Named.CIE_LAB.ordinal -> ColorSpaces.CieLab
-            Named.CIE_XYZ.ordinal -> ColorSpaces.CieXyz
-            Named.DCI_P3.ordinal -> ColorSpaces.DciP3
-            Named.DISPLAY_P3.ordinal -> ColorSpaces.DisplayP3
-            Named.EXTENDED_SRGB.ordinal -> ColorSpaces.ExtendedSrgb
-            Named.LINEAR_EXTENDED_SRGB.ordinal -> ColorSpaces.LinearExtendedSrgb
-            Named.LINEAR_SRGB.ordinal -> ColorSpaces.LinearSrgb
-            Named.NTSC_1953.ordinal -> ColorSpaces.Ntsc1953
-            Named.PRO_PHOTO_RGB.ordinal -> ColorSpaces.ProPhotoRgb
-            Named.SMPTE_C.ordinal -> ColorSpaces.SmpteC
-            else -> {
-                if (this is android.graphics.ColorSpace.Rgb) {
-                    val transferParams = this.transferParameters
-                    val whitePoint = if (this.whitePoint.size == 3) {
-                        WhitePoint(this.whitePoint[0], this.whitePoint[1], this.whitePoint[2])
-                    } else {
-                        WhitePoint(this.whitePoint[0], this.whitePoint[1])
-                    }
-
-                    val composeTransferParams = if (transferParams != null) {
-                        TransferParameters(
-                            gamma = transferParams.g,
-                            a = transferParams.a,
-                            b = transferParams.b,
-                            c = transferParams.c,
-                            d = transferParams.d,
-                            e = transferParams.e,
-                            f = transferParams.f
-                        )
-                    } else {
-                        null
-                    }
-                    Rgb(
-                        name = this.name,
-                        primaries = this.primaries,
-                        whitePoint = whitePoint,
-                        transform = this.transform,
-                        oetf = { x -> this.oetf.applyAsDouble(x) },
-                        eotf = { x -> this.eotf.applyAsDouble(x) },
-                        min = this.getMinValue(0),
-                        max = this.getMaxValue(0),
-                        transferParameters = composeTransferParams,
-                        id = this.id
-                    )
-                } else {
-                    ColorSpaces.Srgb
-                }
-            }
-        }
-    }
+        colorSpace?.toComposeColorSpace() ?: ColorSpaces.Srgb
 }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
index ff000d5..3b0d26d 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
@@ -656,3 +656,17 @@
  * is returned.
  */
 inline fun Color.takeOrElse(block: () -> Color): Color = if (isSpecified) this else block()
+
+/**
+ * Alternative to `() -> Color` that's useful for avoiding boxing.
+ *
+ * Can be used as:
+ *
+ * fun nonBoxedArgs(color: ColorLambda?)
+ */
+fun interface ColorLambda {
+    /**
+     * Return the color
+     */
+    fun invoke(): Color
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt
rename to compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParserTest.kt
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index ce34992..1f0ad8e 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -550,6 +550,7 @@
     method public androidx.compose.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
     method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
     method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
+    method public boolean hasSameDrawAffectingAttributes(androidx.compose.ui.text.TextStyle other);
     method public boolean hasSameLayoutAffectingAttributes(androidx.compose.ui.text.TextStyle other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional androidx.compose.ui.text.TextStyle? other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 78d1a82..509ae13 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -563,6 +563,7 @@
     method public androidx.compose.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
     method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
     method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
+    method public boolean hasSameDrawAffectingAttributes(androidx.compose.ui.text.TextStyle other);
     method public boolean hasSameLayoutAffectingAttributes(androidx.compose.ui.text.TextStyle other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional androidx.compose.ui.text.TextStyle? other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index ce34992..1f0ad8e 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -550,6 +550,7 @@
     method public androidx.compose.ui.text.style.TextGeometricTransform? getTextGeometricTransform();
     method public androidx.compose.ui.text.style.TextIndent? getTextIndent();
     method public androidx.compose.ui.text.style.TextMotion? getTextMotion();
+    method public boolean hasSameDrawAffectingAttributes(androidx.compose.ui.text.TextStyle other);
     method public boolean hasSameLayoutAffectingAttributes(androidx.compose.ui.text.TextStyle other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional androidx.compose.ui.text.TextStyle? other);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextStyle merge(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.TextMotion? textMotion);
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
index f024207..9a92316 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/SpanStyle.kt
@@ -650,7 +650,7 @@
         return true
     }
 
-    private fun hasSameNonLayoutAttributes(other: SpanStyle): Boolean {
+    internal fun hasSameNonLayoutAttributes(other: SpanStyle): Boolean {
         if (textForegroundStyle != other.textForegroundStyle) return false
         if (textDecoration != other.textDecoration) return false
         if (shadow != other.shadow) return false
@@ -889,7 +889,7 @@
 
     // any new vals should do a pre-merge check here
     val requiresAlloc = fontSize.isSpecified && fontSize != this.fontSize ||
-        brush == null && color != textForegroundStyle.color ||
+        brush == null && color.isSpecified && color != textForegroundStyle.color ||
         fontStyle != null && fontStyle != this.fontStyle ||
         fontWeight != null && fontWeight != this.fontWeight ||
         // ref check for font-family, since we don't want to compare lists in fast path
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
index 1b715f9..7660d83 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextStyle.kt
@@ -1143,6 +1143,10 @@
             spanStyle.hasSameLayoutAffectingAttributes(other.spanStyle))
     }
 
+    fun hasSameDrawAffectingAttributes(other: TextStyle): Boolean {
+        return (this === other) || (spanStyle.hasSameNonLayoutAttributes(other.spanStyle))
+    }
+
     override fun hashCode(): Int {
         var result = spanStyle.hashCode()
         result = 31 * result + paragraphStyle.hashCode()
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index d2fa594..923fb32 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -70,6 +70,10 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.unit.Density Density(float density, optional float fontScale);
   }
 
+  public fun interface DoubleLambda {
+    method public double invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Dp implements java.lang.Comparable<androidx.compose.ui.unit.Dp> {
     ctor public Dp(float value);
     method @androidx.compose.runtime.Stable public operator int compareTo(float other);
@@ -194,6 +198,14 @@
     property public final long Zero;
   }
 
+  public fun interface FloatLambda {
+    method public float invoke();
+  }
+
+  public fun interface IntLambda {
+    method public int invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
     method @androidx.compose.runtime.Stable public operator int component1();
     method @androidx.compose.runtime.Stable public operator int component2();
@@ -328,6 +340,14 @@
     enum_constant public static final androidx.compose.ui.unit.LayoutDirection Rtl;
   }
 
+  public fun interface LongLambda {
+    method public long invoke();
+  }
+
+  public fun interface ShortLambda {
+    method public short invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TextUnit {
     method public inline operator int compareTo(long other);
     method public inline operator long div(float other);
diff --git a/compose/ui/ui-unit/api/public_plus_experimental_current.txt b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
index 1c3e781..a31077b 100644
--- a/compose/ui/ui-unit/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-unit/api/public_plus_experimental_current.txt
@@ -70,6 +70,10 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.unit.Density Density(float density, optional float fontScale);
   }
 
+  public fun interface DoubleLambda {
+    method public double invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Dp implements java.lang.Comparable<androidx.compose.ui.unit.Dp> {
     ctor public Dp(float value);
     method @androidx.compose.runtime.Stable public operator int compareTo(float other);
@@ -197,6 +201,14 @@
   @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalUnitApi {
   }
 
+  public fun interface FloatLambda {
+    method public float invoke();
+  }
+
+  public fun interface IntLambda {
+    method public int invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
     method @androidx.compose.runtime.Stable public operator int component1();
     method @androidx.compose.runtime.Stable public operator int component2();
@@ -331,6 +343,14 @@
     enum_constant public static final androidx.compose.ui.unit.LayoutDirection Rtl;
   }
 
+  public fun interface LongLambda {
+    method public long invoke();
+  }
+
+  public fun interface ShortLambda {
+    method public short invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TextUnit {
     method public inline operator int compareTo(long other);
     method public inline operator long div(float other);
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index 8e27b25..73cba21 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -70,6 +70,10 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.unit.Density Density(float density, optional float fontScale);
   }
 
+  public fun interface DoubleLambda {
+    method public double invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class Dp implements java.lang.Comparable<androidx.compose.ui.unit.Dp> {
     ctor public Dp(float value);
     method @androidx.compose.runtime.Stable public operator int compareTo(float other);
@@ -194,6 +198,14 @@
     property public final long Zero;
   }
 
+  public fun interface FloatLambda {
+    method public float invoke();
+  }
+
+  public fun interface IntLambda {
+    method public int invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset {
     method @androidx.compose.runtime.Stable public operator int component1();
     method @androidx.compose.runtime.Stable public operator int component2();
@@ -328,6 +340,14 @@
     enum_constant public static final androidx.compose.ui.unit.LayoutDirection Rtl;
   }
 
+  public fun interface LongLambda {
+    method public long invoke();
+  }
+
+  public fun interface ShortLambda {
+    method public short invoke();
+  }
+
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class TextUnit {
     method public inline operator int compareTo(long other);
     method public inline operator long div(float other);
diff --git a/compose/ui/ui-unit/samples/src/main/java/androidx/compose/ui/unit/samples/UnboxedLambdasSample.kt b/compose/ui/ui-unit/samples/src/main/java/androidx/compose/ui/unit/samples/UnboxedLambdasSample.kt
new file mode 100644
index 0000000..c63ee6a
--- /dev/null
+++ b/compose/ui/ui-unit/samples/src/main/java/androidx/compose/ui/unit/samples/UnboxedLambdasSample.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UNUSED_PARAMETER")
+
+package androidx.compose.ui.unit.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.DoubleLambda
+import androidx.compose.ui.unit.FloatLambda
+import androidx.compose.ui.unit.IntLambda
+import androidx.compose.ui.unit.LongLambda
+import androidx.compose.ui.unit.ShortLambda
+
+@Sampled
+@Composable
+fun FloatLambdaSample() {
+    @Composable
+    fun UseAsArg(param: FloatLambda) {
+        // param does not box
+    }
+    UseAsArg { 1.0f }
+}
+
+@Sampled
+@Composable
+fun DoubleLambdaSample() {
+    @Composable
+    fun UseAsArg(param: DoubleLambda) {
+        // param does not box
+    }
+    UseAsArg { 1.0 }
+}
+
+@Sampled
+@Composable
+fun IntLambdaSample() {
+    @Composable
+    fun UseAsArg(param: IntLambda) {
+        // param does not box
+    }
+    UseAsArg { 1 }
+}
+
+@Sampled
+@Composable
+fun LongLambdaSample() {
+    @Composable
+    fun UseAsArg(param: LongLambda) {
+        // param does not box
+    }
+    UseAsArg { 1L }
+}
+
+@Sampled
+@Composable
+fun ShortLambdaSample() {
+    @Composable
+    fun UseAsArg(param: ShortLambda) {
+        // param does not box
+    }
+    UseAsArg { 1.toShort() }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/UnboxedLambdas.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/UnboxedLambdas.kt
new file mode 100644
index 0000000..1ef08a2
--- /dev/null
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/UnboxedLambdas.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.unit
+
+/**
+ * Alternative to `() -> Float` that's useful for avoiding boxing.
+ *
+ * @sample androidx.compose.ui.unit.samples.FloatLambdaSample
+ */
+fun interface FloatLambda {
+    fun invoke(): Float
+}
+
+/**
+ * Alternative to `() -> Double` that's useful for avoiding boxing.
+ *
+ * @sample androidx.compose.ui.unit.samples.DoubleLambdaSample
+ */
+fun interface DoubleLambda {
+    fun invoke(): Double
+}
+
+/**
+ * Alternative to `() -> Int` that's useful for avoiding boxing.
+ *
+ * @sample androidx.compose.ui.unit.samples.IntLambdaSample
+ */
+fun interface IntLambda {
+    fun invoke(): Int
+}
+
+/**
+ * Alternative to `() -> Long` that's useful for avoiding boxing.
+ *
+ * @sample androidx.compose.ui.unit.samples.LongLambdaSample
+ */
+fun interface LongLambda {
+    fun invoke(): Long
+}
+
+/**
+ * Alternative to `() -> Short` that's useful for avoiding boxing.
+ *
+ * @sample androidx.compose.ui.unit.samples.ShortLambdaSample
+ */
+fun interface ShortLambda {
+    @Suppress("NoByteOrShort")
+    fun invoke(): Short
+}
\ No newline at end of file
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
index adee0b1..fc531f7 100644
--- a/compose/ui/ui/api/current.ignore
+++ b/compose/ui/ui/api/current.ignore
@@ -29,3 +29,7 @@
 
 RemovedClass: androidx.compose.ui.platform.AndroidComposeView_androidKt:
     Removed class androidx.compose.ui.platform.AndroidComposeView_androidKt
+
+
+RemovedDeprecatedMethod: androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion#getRelocate():
+    Removed deprecated method androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.getRelocate()
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index e08cf51..2584370 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -427,7 +427,7 @@
     property public abstract boolean isFocused;
   }
 
-  public final class FocusTargetNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalNode androidx.compose.ui.node.ObserverNode {
+  public final class FocusTargetNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalModifierNode androidx.compose.ui.node.ObserverModifierNode {
     ctor public FocusTargetNode();
     method public androidx.compose.ui.focus.FocusState getFocusState();
     method public void onObservedReadsChanged();
@@ -1492,10 +1492,8 @@
   public static final class NestedScrollSource.Companion {
     method public int getDrag();
     method public int getFling();
-    method @Deprecated public int getRelocate();
     property public final int Drag;
     property public final int Fling;
-    property @Deprecated public final int Relocate;
   }
 
 }
@@ -2228,14 +2226,14 @@
   public abstract sealed class ModifierLocalMap {
   }
 
-  public interface ModifierLocalNode extends androidx.compose.ui.modifier.ModifierLocalReadScope androidx.compose.ui.node.DelegatableNode {
+  public interface ModifierLocalModifierNode extends androidx.compose.ui.modifier.ModifierLocalReadScope androidx.compose.ui.node.DelegatableNode {
     method public default <T> T getCurrent(androidx.compose.ui.modifier.ModifierLocal<T>);
     method public default androidx.compose.ui.modifier.ModifierLocalMap getProvidedValues();
     method public default <T> void provide(androidx.compose.ui.modifier.ModifierLocal<T> key, T value);
     property public default androidx.compose.ui.modifier.ModifierLocalMap providedValues;
   }
 
-  public final class ModifierLocalNodeKt {
+  public final class ModifierLocalModifierNodeKt {
     method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf();
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<T> key);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<T>,? extends T> entry);
@@ -2334,12 +2332,12 @@
     property public final Object? valueOverride;
   }
 
-  public interface ObserverNode extends androidx.compose.ui.node.DelegatableNode {
+  public interface ObserverModifierNode extends androidx.compose.ui.node.DelegatableNode {
     method public void onObservedReadsChanged();
   }
 
-  public final class ObserverNodeKt {
-    method public static <T extends androidx.compose.ui.Modifier.Node & androidx.compose.ui.node.ObserverNode> void observeReads(T, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  public final class ObserverModifierNodeKt {
+    method public static <T extends androidx.compose.ui.Modifier.Node & androidx.compose.ui.node.ObserverModifierNode> void observeReads(T, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public interface ParentDataModifierNode extends androidx.compose.ui.node.DelegatableNode {
@@ -2987,6 +2985,7 @@
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.text.AnnotatedString>> getText();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
+    method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Float> getTraversalIndex();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
@@ -3016,6 +3015,7 @@
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.text.AnnotatedString>> Text;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> TextSelectionRange;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> ToggleableState;
+    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Float> TraversalIndex;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> VerticalScrollAxisRange;
     field public static final androidx.compose.ui.semantics.SemanticsProperties INSTANCE;
   }
@@ -3048,6 +3048,7 @@
     method public static void getTextLayoutResult(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>? action);
     method public static long getTextSelectionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static androidx.compose.ui.state.ToggleableState getToggleableState(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+    method public static float getTraversalIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static androidx.compose.ui.semantics.ScrollAxisRange getVerticalScrollAxisRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void heading(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void indexForKey(androidx.compose.ui.semantics.SemanticsPropertyReceiver, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Integer> mapping);
@@ -3091,6 +3092,7 @@
     method public static void setTextSelectionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver, long);
     method public static void setToggleableState(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.state.ToggleableState);
     method public static void setTraversalGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver, boolean);
+    method public static void setTraversalIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, float);
     method public static void setVerticalScrollAxisRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.ScrollAxisRange);
   }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index aade2f4..8fb9ca0 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -546,7 +546,7 @@
     property public abstract boolean isFocused;
   }
 
-  public final class FocusTargetNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalNode androidx.compose.ui.node.ObserverNode {
+  public final class FocusTargetNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalModifierNode androidx.compose.ui.node.ObserverModifierNode {
     ctor public FocusTargetNode();
     method public androidx.compose.ui.focus.FocusState getFocusState();
     method public void onObservedReadsChanged();
@@ -1622,10 +1622,10 @@
   public static final class NestedScrollSource.Companion {
     method public int getDrag();
     method public int getFling();
-    method @Deprecated public int getRelocate();
+    method @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public int getRelocate();
     property public final int Drag;
     property public final int Fling;
-    property @Deprecated public final int Relocate;
+    property @Deprecated @androidx.compose.ui.ExperimentalComposeUiApi public final int Relocate;
   }
 
 }
@@ -2439,14 +2439,14 @@
   public abstract sealed class ModifierLocalMap {
   }
 
-  public interface ModifierLocalNode extends androidx.compose.ui.modifier.ModifierLocalReadScope androidx.compose.ui.node.DelegatableNode {
+  public interface ModifierLocalModifierNode extends androidx.compose.ui.modifier.ModifierLocalReadScope androidx.compose.ui.node.DelegatableNode {
     method public default <T> T getCurrent(androidx.compose.ui.modifier.ModifierLocal<T>);
     method public default androidx.compose.ui.modifier.ModifierLocalMap getProvidedValues();
     method public default <T> void provide(androidx.compose.ui.modifier.ModifierLocal<T> key, T value);
     property public default androidx.compose.ui.modifier.ModifierLocalMap providedValues;
   }
 
-  public final class ModifierLocalNodeKt {
+  public final class ModifierLocalModifierNodeKt {
     method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf();
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<T> key);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<T>,? extends T> entry);
@@ -2556,12 +2556,12 @@
     property public final Object? valueOverride;
   }
 
-  public interface ObserverNode extends androidx.compose.ui.node.DelegatableNode {
+  public interface ObserverModifierNode extends androidx.compose.ui.node.DelegatableNode {
     method public void onObservedReadsChanged();
   }
 
-  public final class ObserverNodeKt {
-    method public static <T extends androidx.compose.ui.Modifier.Node & androidx.compose.ui.node.ObserverNode> void observeReads(T, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  public final class ObserverModifierNodeKt {
+    method public static <T extends androidx.compose.ui.Modifier.Node & androidx.compose.ui.node.ObserverModifierNode> void observeReads(T, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public interface ParentDataModifierNode extends androidx.compose.ui.node.DelegatableNode {
@@ -3249,6 +3249,7 @@
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.text.AnnotatedString>> getText();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
+    method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Float> getTraversalIndex();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
@@ -3278,6 +3279,7 @@
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.text.AnnotatedString>> Text;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> TextSelectionRange;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> ToggleableState;
+    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Float> TraversalIndex;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> VerticalScrollAxisRange;
     field public static final androidx.compose.ui.semantics.SemanticsProperties INSTANCE;
   }
@@ -3316,6 +3318,7 @@
     method public static void getTextLayoutResult(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>? action);
     method public static long getTextSelectionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static androidx.compose.ui.state.ToggleableState getToggleableState(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+    method public static float getTraversalIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static androidx.compose.ui.semantics.ScrollAxisRange getVerticalScrollAxisRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void heading(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void indexForKey(androidx.compose.ui.semantics.SemanticsPropertyReceiver, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Integer> mapping);
@@ -3360,6 +3363,7 @@
     method public static void setTextSelectionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver, long);
     method public static void setToggleableState(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.state.ToggleableState);
     method public static void setTraversalGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver, boolean);
+    method public static void setTraversalIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, float);
     method public static void setVerticalScrollAxisRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.ScrollAxisRange);
   }
 
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
index adee0b1..fc531f7 100644
--- a/compose/ui/ui/api/restricted_current.ignore
+++ b/compose/ui/ui/api/restricted_current.ignore
@@ -29,3 +29,7 @@
 
 RemovedClass: androidx.compose.ui.platform.AndroidComposeView_androidKt:
     Removed class androidx.compose.ui.platform.AndroidComposeView_androidKt
+
+
+RemovedDeprecatedMethod: androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion#getRelocate():
+    Removed deprecated method androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.getRelocate()
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index a3e1bff..578a0d2 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -427,7 +427,7 @@
     property public abstract boolean isFocused;
   }
 
-  public final class FocusTargetNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalNode androidx.compose.ui.node.ObserverNode {
+  public final class FocusTargetNode extends androidx.compose.ui.Modifier.Node implements androidx.compose.ui.modifier.ModifierLocalModifierNode androidx.compose.ui.node.ObserverModifierNode {
     ctor public FocusTargetNode();
     method public androidx.compose.ui.focus.FocusState getFocusState();
     method public void onObservedReadsChanged();
@@ -1492,10 +1492,8 @@
   public static final class NestedScrollSource.Companion {
     method public int getDrag();
     method public int getFling();
-    method @Deprecated public int getRelocate();
     property public final int Drag;
     property public final int Fling;
-    property @Deprecated public final int Relocate;
   }
 
 }
@@ -2235,14 +2233,14 @@
   public abstract sealed class ModifierLocalMap {
   }
 
-  public interface ModifierLocalNode extends androidx.compose.ui.modifier.ModifierLocalReadScope androidx.compose.ui.node.DelegatableNode {
+  public interface ModifierLocalModifierNode extends androidx.compose.ui.modifier.ModifierLocalReadScope androidx.compose.ui.node.DelegatableNode {
     method public default <T> T getCurrent(androidx.compose.ui.modifier.ModifierLocal<T>);
     method public default androidx.compose.ui.modifier.ModifierLocalMap getProvidedValues();
     method public default <T> void provide(androidx.compose.ui.modifier.ModifierLocal<T> key, T value);
     property public default androidx.compose.ui.modifier.ModifierLocalMap providedValues;
   }
 
-  public final class ModifierLocalNodeKt {
+  public final class ModifierLocalModifierNodeKt {
     method public static androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf();
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(androidx.compose.ui.modifier.ModifierLocal<T> key);
     method public static <T> androidx.compose.ui.modifier.ModifierLocalMap modifierLocalMapOf(kotlin.Pair<? extends androidx.compose.ui.modifier.ModifierLocal<T>,? extends T> entry);
@@ -2382,12 +2380,12 @@
     property public final Object? valueOverride;
   }
 
-  public interface ObserverNode extends androidx.compose.ui.node.DelegatableNode {
+  public interface ObserverModifierNode extends androidx.compose.ui.node.DelegatableNode {
     method public void onObservedReadsChanged();
   }
 
-  public final class ObserverNodeKt {
-    method public static <T extends androidx.compose.ui.Modifier.Node & androidx.compose.ui.node.ObserverNode> void observeReads(T, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  public final class ObserverModifierNodeKt {
+    method public static <T extends androidx.compose.ui.Modifier.Node & androidx.compose.ui.node.ObserverModifierNode> void observeReads(T, kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public interface ParentDataModifierNode extends androidx.compose.ui.node.DelegatableNode {
@@ -3036,6 +3034,7 @@
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.text.AnnotatedString>> getText();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> getTextSelectionRange();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> getToggleableState();
+    method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Float> getTraversalIndex();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> getVerticalScrollAxisRange();
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionInfo> CollectionInfo;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.CollectionItemInfo> CollectionItemInfo;
@@ -3065,6 +3064,7 @@
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.util.List<androidx.compose.ui.text.AnnotatedString>> Text;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.text.TextRange> TextSelectionRange;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.state.ToggleableState> ToggleableState;
+    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Float> TraversalIndex;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.ScrollAxisRange> VerticalScrollAxisRange;
     field public static final androidx.compose.ui.semantics.SemanticsProperties INSTANCE;
   }
@@ -3097,6 +3097,7 @@
     method public static void getTextLayoutResult(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.ui.text.TextLayoutResult>,java.lang.Boolean>? action);
     method public static long getTextSelectionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static androidx.compose.ui.state.ToggleableState getToggleableState(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
+    method public static float getTraversalIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static androidx.compose.ui.semantics.ScrollAxisRange getVerticalScrollAxisRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void heading(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void indexForKey(androidx.compose.ui.semantics.SemanticsPropertyReceiver, kotlin.jvm.functions.Function1<java.lang.Object,java.lang.Integer> mapping);
@@ -3140,6 +3141,7 @@
     method public static void setTextSelectionRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver, long);
     method public static void setToggleableState(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.state.ToggleableState);
     method public static void setTraversalGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver, boolean);
+    method public static void setTraversalIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, float);
     method public static void setVerticalScrollAxisRange(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.ScrollAxisRange);
   }
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/accessibility/ComplexAccessibility.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/accessibility/ComplexAccessibility.kt
index c26bc71..e8aed9a 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/accessibility/ComplexAccessibility.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/accessibility/ComplexAccessibility.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.demos
 
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
@@ -43,6 +44,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.traversalIndex
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 
@@ -237,3 +239,114 @@
         }
     }
 }
+
+@Preview
+@Composable
+fun OverlaidNodeTraversalIndexDemo() {
+    LastElementOverLaidColumn(
+        Modifier
+            .semantics { isTraversalGroup = true }
+            .padding(8.dp)) {
+        Row {
+            Column(modifier = Modifier.testTag("Text1")) {
+                Row { Text("text1\n") }
+                Row { Text("text2\n") }
+                Row { Text("text3\n") }
+            }
+        }
+        // Since default traversalIndex is 0, `traversalIndex = -1f` here means that the overlaid
+        // node is read first, even though visually it's below the other text.
+        // Container needs to be true, otherwise we only read/register significant
+        Row(Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
+            Text("overlaid node")
+        }
+    }
+}
+
+@Composable
+fun FloatingBox() {
+    Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) {
+        FloatingActionButton(onClick = {}) {
+            Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon")
+        }
+    }
+}
+
+@Composable
+fun ContentColumn(padding: PaddingValues) {
+    var counter = 0
+    var sampleText = "Sample text in column"
+    Column(
+        Modifier
+            .verticalScroll(rememberScrollState())
+            .padding(padding)
+            .testTag("Test Tag")
+    ) {
+        // every other value has an explicitly set `traversalIndex`
+        Text(text = sampleText + counter++)
+        Text(text = sampleText + counter++,
+            modifier = Modifier.semantics { traversalIndex = 1f })
+        Text(text = sampleText + counter++)
+        Text(text = sampleText + counter++,
+            modifier = Modifier.semantics { traversalIndex = 1f })
+        Text(text = sampleText + counter++)
+        Text(text = sampleText + counter++,
+            modifier = Modifier.semantics { traversalIndex = 1f })
+        Text(text = sampleText + counter++)
+    }
+}
+
+/**
+ * Example of how `traversalIndex` and traversal groups can be used to customize TalkBack
+ * ordering. The example below puts the FAB into a box (with `isTraversalGroup = true` and a
+ * custom traversal index) to have it appear first when TalkBack is turned on. The
+ * text in the column also has been modified. See go/traversal-index-changes for more detail
+ */
+@Preview
+@Composable
+fun NestedTraversalIndexInheritanceDemo() {
+    val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
+    Scaffold(
+        scaffoldState = scaffoldState,
+        topBar = { TopAppBar() },
+        floatingActionButtonPosition = FabPosition.End,
+        floatingActionButton = { FloatingBox() },
+        drawerContent = { Text(text = "Drawer Menu 1") },
+        content = { padding -> ContentColumn(padding = padding) },
+        bottomBar = { BottomAppBar(backgroundColor = MaterialTheme.colors.primary) {
+            Text("Bottom App Bar") } }
+    )
+}
+
+@Preview
+@Composable
+fun NestedAndPeerTraversalIndexDemo() {
+    Column(
+        Modifier
+            // Having a traversal index here as 8f shouldn't affect anything; this column
+            // has no other peers that its compared to
+            .semantics { traversalIndex = 8f; isTraversalGroup = true }
+            .padding(8.dp)
+    ) {
+        Row(
+            Modifier.semantics { traversalIndex = 3f; isTraversalGroup = true }
+        ) {
+            Column(modifier = Modifier.testTag("Text1")) {
+                Row { Text("text 3\n") }
+                Row {
+                    Text(text = "text 5\n", modifier = Modifier.semantics { traversalIndex = 1f })
+                }
+                Row { Text("text 4\n") }
+            }
+        }
+        Row {
+            Text(text = "text 2\n", modifier = Modifier.semantics { traversalIndex = 2f })
+        }
+        Row {
+            Text(text = "text 1\n", modifier = Modifier.semantics { traversalIndex = 1f })
+        }
+        Row {
+            Text(text = "text 0\n")
+        }
+    }
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt
index 05eb8f0..8245a01 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt
@@ -33,16 +33,16 @@
 @Sampled
 @Composable
 fun CompositionLocalConsumingModifierSample() {
-    val LocalBackgroundColor = compositionLocalOf { Color.White }
+    val localBackgroundColor = compositionLocalOf { Color.White }
     class BackgroundColor : Modifier.Node(), DrawModifierNode,
         CompositionLocalConsumerModifierNode {
         override fun ContentDrawScope.draw() {
-            val backgroundColor = currentValueOf(LocalBackgroundColor)
+            val backgroundColor = currentValueOf(localBackgroundColor)
             drawRect(backgroundColor)
             drawContent()
         }
     }
-    val BackgroundColorModifierElement = object : ModifierNodeElement<BackgroundColor>() {
+    val backgroundColorElement = object : ModifierNodeElement<BackgroundColor>() {
         override fun create() = BackgroundColor()
         override fun update(node: BackgroundColor) {}
         override fun hashCode() = System.identityHashCode(this)
@@ -51,7 +51,7 @@
             name = "backgroundColor"
         }
     }
-    fun Modifier.backgroundColor() = this then BackgroundColorModifierElement
+    fun Modifier.backgroundColor() = this then backgroundColorElement
     Box(Modifier.backgroundColor()) {
         Text("Hello, world!")
     }
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt
index 1039565..a61a41e 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierSamples.kt
@@ -39,7 +39,7 @@
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.modifierLocalMapOf
 import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.node.DelegatingNode
@@ -209,7 +209,7 @@
 @Sampled
 @Composable
 fun LazyDelegationExample() {
-    class ExpensivePositionHandlingOnPointerEvents() : PointerInputModifierNode, DelegatingNode() {
+    class ExpensivePositionHandlingOnPointerEvents : PointerInputModifierNode, DelegatingNode() {
 
         val globalAwareNode = object : GlobalPositionAwareModifierNode, Modifier.Node() {
             override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
@@ -466,9 +466,9 @@
 fun JustReadingOrProvidingModifierLocalNodeSample() {
     class Logger { fun log(string: String) { println(string) } }
 
-    val loggerLocal = modifierLocalOf<Logger> { Logger() }
+    val loggerLocal = modifierLocalOf { Logger() }
 
-    class ProvideLoggerNode(logger: Logger) : ModifierLocalNode, Modifier.Node() {
+    class ProvideLoggerNode(logger: Logger) : ModifierLocalModifierNode, Modifier.Node() {
         override val providedValues = modifierLocalMapOf(loggerLocal to logger)
     }
 
@@ -487,7 +487,7 @@
 
     class SizeLoggerNode(
         var id: String
-    ) : ModifierLocalNode, LayoutAwareModifierNode, Modifier.Node() {
+    ) : ModifierLocalModifierNode, LayoutAwareModifierNode, Modifier.Node() {
         override fun onRemeasured(size: IntSize) {
             loggerLocal.current.log("The size of $id was $size")
         }
@@ -510,7 +510,6 @@
     fun Modifier.provideLogger(logger: Logger) = this then ProvideLoggerElement(logger)
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 @Sampled
 @Composable
 fun ModifierNodeResetSample() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 5a731f7..e2ebcd9 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -128,6 +128,7 @@
 import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.semantics.testTagsAsResourceId
 import androidx.compose.ui.semantics.textSelectionRange
+import androidx.compose.ui.semantics.traversalIndex
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.test.assert
@@ -744,6 +745,55 @@
     }
 
     @Test
+    fun testSortedAccessibilityNodeInfo_peerTraversalGroups_traversalIndex() {
+        var topSampleText = "Top text in column "
+        var bottomSampleText = "Bottom text in column "
+        container.setContent {
+            Column(
+                Modifier
+                    .testTag("Test Tag")
+                    .semantics { isTraversalGroup = false }
+            ) {
+                Row() { Modifier.semantics { isTraversalGroup = false }
+                    CardRow(
+                        // Setting a bigger traversalIndex here means that this CardRow will be
+                        // read second, even though it is visually to the left of the other CardRow
+                        Modifier
+                            .semantics { isTraversalGroup = true }
+                            .semantics { traversalIndex = 1f },
+                        1,
+                        topSampleText,
+                        bottomSampleText)
+                    CardRow(
+                        Modifier.semantics { isTraversalGroup = true },
+                        2,
+                        topSampleText,
+                        bottomSampleText)
+                }
+            }
+        }
+
+        val topText1 = rule.onNodeWithText(topSampleText + 1).fetchSemanticsNode()
+        val topText2 = rule.onNodeWithText(topSampleText + 2).fetchSemanticsNode()
+        val bottomText1 = rule.onNodeWithText(bottomSampleText + 1).fetchSemanticsNode()
+        val bottomText2 = rule.onNodeWithText(bottomSampleText + 2).fetchSemanticsNode()
+
+        val topText1ANI = provider.createAccessibilityNodeInfo(topText1.id)
+        val topText2ANI = provider.createAccessibilityNodeInfo(topText2.id)
+        val bottomText2ANI = provider.createAccessibilityNodeInfo(bottomText2.id)
+
+        val topText1Before = topText1ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+        val topText2Before = topText2ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+        val bottomText2Before = bottomText2ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+
+        // Expected behavior: "Top text in column 2" -> "Bottom text in column 2" ->
+        // "Top text in column 1" -> "Bottom text in column 1"
+        assertThat(topText2Before).isAtMost(bottomText2.id)
+        assertThat(bottomText2Before).isAtMost(topText1.id)
+        assertThat(topText1Before).isAtMost(bottomText1.id)
+    }
+
+    @Test
     fun testSortedAccessibilityNodeInfo_nestedTraversalGroups_outerFalse() {
         var topSampleText = "Top text in column "
         var bottomSampleText = "Bottom text in column "
@@ -779,53 +829,13 @@
         val topText1Before = topText1ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
         val topText2Before = topText2ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
 
-        // Here we have the following hierarchy of traversal groups:
+        // Here we have the following hierarchy of containers:
         // `isTraversalGroup = false`
         //    `isTraversalGroup = false`
         //       `isTraversalGroup = true`
         //       `isTraversalGroup = true`
-        // meaning the behavior should be as if the first two `isTraversalGroup = false` are not present
-        // and all of column 1 should be read before column 2.
-        assertEquals(topText1Before, bottomText1.id)
-        assertEquals(topText2Before, bottomText2.id)
-    }
-
-    @Test
-    fun testSortedAccessibilityNodeInfo_nestedContainers_outerFalse() {
-        var topSampleText = "Top text in column "
-        var bottomSampleText = "Bottom text in column "
-        container.setContent {
-            Column(
-                Modifier
-                    .testTag("Test Tag")
-                    .semantics { isTraversalGroup = false }
-            ) {
-                Row() { Modifier.semantics { isTraversalGroup = false }
-                    CardRow(
-                        Modifier.semantics { isTraversalGroup = true },
-                        1,
-                        topSampleText,
-                        bottomSampleText)
-                    CardRow(
-                        Modifier.semantics { isTraversalGroup = true },
-                        2,
-                        topSampleText,
-                        bottomSampleText)
-                }
-            }
-        }
-
-        val topText1 = rule.onNodeWithText(topSampleText + 1).fetchSemanticsNode()
-        val topText2 = rule.onNodeWithText(topSampleText + 2).fetchSemanticsNode()
-        val bottomText1 = rule.onNodeWithText(bottomSampleText + 1).fetchSemanticsNode()
-        val bottomText2 = rule.onNodeWithText(bottomSampleText + 2).fetchSemanticsNode()
-
-        val topText1ANI = provider.createAccessibilityNodeInfo(topText1.id)
-        val topText2ANI = provider.createAccessibilityNodeInfo(topText2.id)
-
-        val topText1Before = topText1ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
-        val topText2Before = topText2ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
-
+        // meaning the behavior should be as if the first two `isTraversalGroup = false` are not
+        // present and all of column 1 should be read before column 2.
         assertEquals(topText1Before, bottomText1.id)
         assertEquals(topText2Before, bottomText2.id)
     }
@@ -963,6 +973,201 @@
     }
 
     @Test
+    fun testSortedAccessibilityNodeInfo_traversalIndex() {
+        val overlaidText = "Overlaid node text"
+        val text1 = "Text 1\n"
+        val text2 = "Text 2\n"
+        val text3 = "Text 3\n"
+        container.setContent {
+            LastElementOverLaidColumn(
+                // None of the elements below should inherit `traversalIndex = 5f`
+                modifier = Modifier.padding(8.dp).semantics { traversalIndex = 5f }
+            ) {
+                Row {
+                    Column {
+                        Row { Text(text1) }
+                        Row { Text(text2) }
+                        Row { Text(text3) }
+                    }
+                }
+                // Since default traversalIndex is 0, `traversalIndex = -1f` here means that the
+                // overlaid node is read first, even though visually it's below the other text.
+                Row {
+                    Text(
+                        text = overlaidText,
+                        modifier = Modifier.semantics { traversalIndex = -1f }
+                    )
+                }
+            }
+        }
+
+        val node1 = rule.onNodeWithText(text1).fetchSemanticsNode()
+        val overlaidNode = rule.onNodeWithText(overlaidText).fetchSemanticsNode()
+
+        val overlaidANI = provider.createAccessibilityNodeInfo(overlaidNode.id)
+        val overlaidTraversalBefore =
+            overlaidANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+
+        // Because the overlaid node has a smaller traversal index, it should be read before node 1
+        assertThat(overlaidTraversalBefore).isAtMost(node1.id)
+    }
+
+    @Test
+    fun testSortedAccessibilityNodeInfo_nestedAndPeerTraversalIndex() {
+        val text0 = "Text 0\n"
+        val text1 = "Text 1\n"
+        val text2 = "Text 2\n"
+        val text3 = "Text 3\n"
+        val text4 = "Text 4\n"
+        val text5 = "Text 5\n"
+        container.setContent {
+            Column(
+                Modifier
+                    // Having a traversal index here as 8f shouldn't affect anything; this column
+                    // has no other peers that its compared to
+                    .semantics { traversalIndex = 8f; isTraversalGroup = true }
+                    .padding(8.dp)
+            ) {
+                Row(
+                    Modifier.semantics { traversalIndex = 3f; isTraversalGroup = true }
+                ) {
+                    Column(modifier = Modifier.testTag("Tag1")) {
+                        Row { Text(text3) }
+                        Row { Text(
+                            text = text5, modifier = Modifier.semantics { traversalIndex = 1f })
+                        }
+                        Row { Text(text4) }
+                    }
+                }
+                Row {
+                    Text(text = text2, modifier = Modifier.semantics { traversalIndex = 2f })
+                }
+                Row {
+                    Text(text = text1, modifier = Modifier.semantics { traversalIndex = 1f })
+                }
+                Row {
+                    Text(text = text0)
+                }
+            }
+        }
+
+        val node0 = rule.onNodeWithText(text0).fetchSemanticsNode()
+        val node1 = rule.onNodeWithText(text1).fetchSemanticsNode()
+        val node2 = rule.onNodeWithText(text2).fetchSemanticsNode()
+        val node3 = rule.onNodeWithText(text3).fetchSemanticsNode()
+        val node4 = rule.onNodeWithText(text4).fetchSemanticsNode()
+        val node5 = rule.onNodeWithText(text5).fetchSemanticsNode()
+
+        val ANI0 = provider.createAccessibilityNodeInfo(node0.id)
+        val ANI1 = provider.createAccessibilityNodeInfo(node1.id)
+        val ANI2 = provider.createAccessibilityNodeInfo(node2.id)
+        val ANI3 = provider.createAccessibilityNodeInfo(node3.id)
+        val ANI4 = provider.createAccessibilityNodeInfo(node4.id)
+
+        val traverseBefore0 =
+            ANI0?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+        val traverseBefore1 =
+            ANI1?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+        val traverseBefore2 =
+            ANI2?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+        val traverseBefore3 =
+            ANI3?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+        val traverseBefore4 =
+            ANI4?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+
+        // We want to read the texts in order: 0 -> 1 -> 2 -> 3 -> 4 -> 5
+        assertThat(traverseBefore0).isAtMost(node1.id)
+        assertThat(traverseBefore1).isAtMost(node2.id)
+        assertThat(traverseBefore2).isAtMost(node3.id)
+        assertThat(traverseBefore3).isAtMost(node4.id)
+        assertThat(traverseBefore4).isAtMost(node5.id)
+    }
+
+    @Test
+    fun testSortedAccessibilityNodeInfo_traversalIndexInherited_indexFirst() {
+        val overlaidText = "Overlaid node text"
+        val text1 = "Text 1\n"
+        val text2 = "Text 2\n"
+        val text3 = "Text 3\n"
+        container.setContent {
+            LastElementOverLaidColumn(
+                modifier = Modifier
+                    .semantics { traversalIndex = -1f }
+                    .semantics { isTraversalGroup = true }
+            ) {
+                Row {
+                    Column {
+                        Row { Text(text1) }
+                        Row { Text(text2) }
+                        Row { Text(text3) }
+                    }
+                }
+                Row {
+                    Text(
+                        text = overlaidText,
+                        modifier = Modifier
+                            .semantics { traversalIndex = 1f }
+                            .semantics { isTraversalGroup = true }
+                    )
+                }
+            }
+        }
+
+        val node3 = rule.onNodeWithText(text3).fetchSemanticsNode()
+        val overlaidNode = rule.onNodeWithText(overlaidText).fetchSemanticsNode()
+
+        val node3ANI = provider.createAccessibilityNodeInfo(node3.id)
+        val node3TraverseBefore =
+            node3ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+
+        // Nodes 1 through 3 are read, and then overlaid node is read last
+        assertThat(node3TraverseBefore).isAtMost(overlaidNode.id)
+    }
+
+    @Test
+    fun testSortedAccessibilityNodeInfo_traversalIndexInherited_indexSecond() {
+        val overlaidText = "Overlaid node text"
+        val text1 = "Text 1\n"
+        val text2 = "Text 2\n"
+        val text3 = "Text 3\n"
+        // This test is identical to the one above, except with `isTraversalGroup` coming first in
+        // the modifier chain. Behavior-wise, this shouldn't change anything.
+        container.setContent {
+            LastElementOverLaidColumn(
+                modifier = Modifier
+                    .semantics { isTraversalGroup = true }
+                    .semantics { traversalIndex = -1f }
+            ) {
+                Row {
+                    Column {
+                        Row { Text(text1) }
+                        Row { Text(text2) }
+                        Row { Text(text3) }
+                    }
+                }
+                Row {
+                    Text(
+                        text = overlaidText,
+                        modifier = Modifier
+                            .semantics { isTraversalGroup = true }
+                            .semantics { traversalIndex = 1f }
+                    )
+                }
+            }
+        }
+
+        val node3 = rule.onNodeWithText(text3).fetchSemanticsNode()
+        val overlaidNode = rule.onNodeWithText(overlaidText).fetchSemanticsNode()
+
+        val node3ANI = provider.createAccessibilityNodeInfo(node3.id)
+        val node3TraverseBefore =
+            node3ANI?.extras?.getInt(EXTRA_DATA_TEST_TRAVERSALBEFORE_VAL)
+
+        // Nodes 1 through 3 are read, and then overlaid node is read last
+        assertThat(node3TraverseBefore).isAtMost(overlaidNode.id)
+    }
+
+    @Test
     fun testSortedAccessibilityNodeInfo_SimpleTopAppBar() {
         val topAppBarText = "Top App Bar"
         val textBoxTag = "Text Box"
@@ -1295,17 +1500,21 @@
             Box(
                 Modifier.testTag(rootTag)
             ) {
+                // Layouts need to have `.clickable` on them in order to make the nodes
+                // speakable and therefore sortable
                 SimpleTestLayout(
                     Modifier
                         .requiredSize(50.dp)
                         .offset(x = 20.dp, y = 0.dp)
                         .testTag(childTag1)
+                        .clickable(onClick = {})
                 ) {}
                 SimpleTestLayout(
                     Modifier
                         .requiredSize(50.dp)
                         .offset(x = 0.dp, y = 20.dp)
                         .testTag(childTag2)
+                        .clickable(onClick = {})
                 ) {}
             }
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index b4cc4f9..4e95ffd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -39,7 +39,7 @@
 import androidx.compose.ui.platform.SemanticsNodeWithAdjustedBounds
 import androidx.compose.ui.platform.getAllUncoveredSemanticsNodesToMap
 import androidx.compose.ui.semantics.CustomAccessibilityAction
-import androidx.compose.ui.semantics.EmptySemanticsModifierNodeElement
+import androidx.compose.ui.semantics.EmptySemanticsElement
 import androidx.compose.ui.semantics.LiveRegionMode
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.Role
@@ -709,7 +709,7 @@
     @FlakyTest(bugId = 195287742)
     fun sendScrollEvent_byStateObservation() {
         var scrollValue by mutableStateOf(0f, structuralEqualityPolicy())
-        var scrollMaxValue by mutableStateOf(100f, structuralEqualityPolicy())
+        val scrollMaxValue = 100f
 
         val semanticsNode = createSemanticsNodeWithProperties(1, false) {
             verticalScrollAxisRange = ScrollAxisRange({ scrollValue }, { scrollMaxValue })
@@ -956,12 +956,11 @@
         )
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun testUncoveredNodes_notPlacedNodes_notIncluded() {
         val nodes = SemanticsOwner(
             LayoutNode().also {
-                it.modifier = EmptySemanticsModifierNodeElement
+                it.modifier = EmptySemanticsElement
             }
         ).getAllUncoveredSemanticsNodesToMap()
         assertEquals(0, nodes.size)
@@ -1594,7 +1593,6 @@
         accessibilityDelegate.sendSemanticsPropertyChangeEvents(mapOf(nodeId to newTextNode))
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     private fun createSemanticsNodeWithProperties(
         id: Int,
         mergeDescendants: Boolean,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ZIndexModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ZIndexNodeTest.kt
similarity index 97%
rename from compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ZIndexModifierTest.kt
rename to compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ZIndexNodeTest.kt
index 028803b..c81d465 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ZIndexModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ZIndexNodeTest.kt
@@ -23,7 +23,7 @@
 import org.junit.Before
 import org.junit.Test
 
-class ZIndexModifierTest {
+class ZIndexNodeTest {
     @Before
     fun before() {
         isDebugInspectorInfoEnabled = true
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt
index 3b20a82..609463e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/CombinedFocusModifierNodeTest.kt
@@ -42,7 +42,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
 
         // Act.
@@ -61,7 +61,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
         rule.runOnIdle {
             combinedFocusNode.requestFocus()
@@ -84,7 +84,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
         rule.runOnIdle {
             combinedFocusNode.requestFocus()
@@ -108,7 +108,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget).apply { canFocus = true }
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
 
         // Act.
@@ -127,7 +127,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget).apply { canFocus = false }
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
 
         // Act.
@@ -150,7 +150,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
         rule.runOnIdle {
             combinedFocusNode.requestFocus()
@@ -172,7 +172,7 @@
         // Arrange.
         val combinedFocusNode = CombinedFocusNode(delegatedFocusTarget)
         rule.setFocusableContent {
-            Box(Modifier.combinedFocusNode(combinedFocusNode))
+            Box(Modifier.combinedFocusElement(combinedFocusNode))
         }
         rule.runOnIdle {
             combinedFocusNode.requestFocus()
@@ -190,13 +190,13 @@
         }
     }
 
-    private fun Modifier.combinedFocusNode(combinedFocusNode: CombinedFocusNode): Modifier {
+    private fun Modifier.combinedFocusElement(combinedFocusNode: CombinedFocusNode): Modifier {
         return this
-            .then(CombinedFocusNodeElement(combinedFocusNode))
+            .then(CombinedFocusElement(combinedFocusNode))
             .then(if (delegatedFocusTarget) Modifier else Modifier.focusTarget())
     }
 
-    private data class CombinedFocusNodeElement(
+    private data class CombinedFocusElement(
         val combinedFocusNode: CombinedFocusNode
     ) : ModifierNodeElement<CombinedFocusNode>() {
         override fun create(): CombinedFocusNode = combinedFocusNode
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
index 4f8d561..1d3593d 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilterTest.kt
@@ -492,8 +492,7 @@
         rule.setContent {
             val pointerInputHandler: suspend PointerInputScope.() -> Unit = {}
             val modifier =
-                Modifier.pointerInput(Unit, pointerInputHandler)
-                    as SuspendPointerInputModifierNodeElement
+                Modifier.pointerInput(Unit, pointerInputHandler) as SuspendPointerInputElement
 
             assertThat(modifier.nameFallback).isEqualTo("pointerInput")
             assertThat(modifier.valueOverride).isNull()
@@ -716,6 +715,7 @@
             }
         }
         val node = object : DelegatingNode() {
+            @Suppress("unused")
             val pointer = delegate(suspendingPointerInputModifierNode)
         }
 
@@ -744,12 +744,14 @@
         val tag = "input rect"
 
         val node = object : DelegatingNode() {
+            @Suppress("unused")
             val piNode1 = delegate(SuspendingPointerInputModifierNode {
                 awaitPointerEventScope {
                     events += awaitPointerEvent()
                 }
             })
 
+            @Suppress("unused")
             val piNode2 = delegate(SuspendingPointerInputModifierNode {
                 awaitPointerEventScope {
                     events += awaitPointerEvent()
@@ -833,7 +835,7 @@
     override fun update(node: Modifier.Node) {}
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
-        if (other !is SuspendPointerInputModifierNodeElement) return false
+        if (other !is SuspendPointerInputElement) return false
         if (key1 != other.key1) return false
         return true
     }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 221597d..302768a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -1929,6 +1929,60 @@
     }
 
     @Test
+    fun forceMeasureSubtreeWhileLookaheadMeasureRequestedFromSubtree() {
+        var iterations by mutableStateOf(0)
+        rule.setContent {
+            Box(Modifier.fillMaxSize()) {
+                LookaheadScope {
+                    // Fill max size will cause the remeasure requests to go down the
+                    // forceMeasureSubtree code path.
+                    CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                        Column(Modifier.fillMaxSize()) {
+                            // This box will get a remeasure request when `iterations` changes.
+                            // Subsequently this Box's size change will trigger a measurement pass
+                            // from Column.
+                            Box(Modifier.intermediateLayout { measurable, _ ->
+                                // Force a state-read, so that this node is the node where
+                                // remeasurement starts.
+                                @Suppress("UNUSED_EXPRESSION")
+                                iterations
+                                measurable.measure(Constraints.fixed(200, 200))
+                                    .run {
+                                        layout(width, height) {
+                                            place(0, 0)
+                                        }
+                                    }
+                            }) {
+                                // Swap modifiers. If lookahead re-measurement from this node isn't
+                                // handled before parent's non-lookahead remeasurement, this would
+                                // lead to a crash.
+                                Box(
+                                    if (iterations % 2 == 0)
+                                        Modifier.size(100.dp)
+                                    else
+                                        Modifier.intermediateLayout { measurable, constraints ->
+                                            measurable.measure(constraints).run {
+                                                layout(width, height) {
+                                                    place(5, 5)
+                                                }
+                                            }
+                                        }.padding(5.dp)
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        repeat(4) {
+            rule.runOnIdle {
+                iterations++
+            }
+        }
+    }
+
+    @Test
     fun multiMeasureLayoutInLookahead() {
         var horizontal by mutableStateOf(true)
         rule.setContent {
@@ -2069,13 +2123,19 @@
                     // Also check that localPositionOf with non-zero offset works
                     // correctly for lookahead coordinates and LayoutCoordinates.
                     val randomOffset = Offset(
-                        Random.nextInt(0, 1000).toFloat(),
-                        Random.nextInt(0, 1000).toFloat()
+                        Random
+                            .nextInt(0, 1000)
+                            .toFloat(),
+                        Random
+                            .nextInt(0, 1000)
+                            .toFloat()
                     )
                     assertEquals(
-                        lookaheadLayoutCoordinates!!.toLookaheadCoordinates().localPositionOf(
-                            onPlacedCoordinates!!.toLookaheadCoordinates(),
-                            randomOffset
+                        lookaheadLayoutCoordinates!!
+                            .toLookaheadCoordinates()
+                            .localPositionOf(
+                                onPlacedCoordinates!!.toLookaheadCoordinates(),
+                                randomOffset
                             ),
                         lookaheadLayoutCoordinates!!.localPositionOf(
                             onPlacedCoordinates!!,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/ResizingComposeViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/ResizingComposeViewTest.kt
index 7dd9220..d8d73e7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/ResizingComposeViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/ResizingComposeViewTest.kt
@@ -274,7 +274,7 @@
             composeView.setContent {
                 ResizingChild(
                     layoutHeight = { childHeight },
-                    modifier = RemeasurementModifierElement { remeasurement = it }
+                    modifier = RemeasurementElement { remeasurement = it }
                 )
             }
         }
@@ -302,7 +302,7 @@
             composeView.setContent {
                 ResizingChild(
                     layoutHeight = { 10 },
-                    modifier = RemeasurementModifierElement { remeasurement = it }
+                    modifier = RemeasurementElement { remeasurement = it }
                 )
             }
         }
@@ -390,7 +390,7 @@
     ViewGroup.LayoutParams.WRAP_CONTENT
 )
 
-private class RemeasurementModifierElement(
+private class RemeasurementElement(
     private val onRemeasurementAvailable: (Remeasurement) -> Unit
 ) : ModifierNodeElement<RemeasurementModifierNode>() {
     override fun create() = RemeasurementModifierNode(onRemeasurementAvailable)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt
index a38a249..b677ada 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/CompositionLocalMapInjectionTest.kt
@@ -41,7 +41,7 @@
 import androidx.compose.ui.node.DrawModifierNode
 import androidx.compose.ui.node.LayoutModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ObserverNode
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.platform.LocalDensity
@@ -264,7 +264,8 @@
     }
 }
 
-class ConsumeInAttachNode : CompositionLocalConsumerModifierNode, ObserverNode, Modifier.Node() {
+class ConsumeInAttachNode :
+    CompositionLocalConsumerModifierNode, ObserverModifierNode, Modifier.Node() {
     var view: View? = null
     var int: Int? = null
     private fun readLocals() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
index 1c7d152..0b0714d 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
@@ -43,7 +43,7 @@
 import androidx.compose.ui.node.LayoutModifierNode
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ObserverNode
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
@@ -339,7 +339,7 @@
             ReusableContentHost(active) {
                 ReusableContent(reuseKey) {
                     Layout(
-                        modifier = StatelessModifierElement(onInvalidate),
+                        modifier = StatelessElement(onInvalidate),
                         measurePolicy = MeasurePolicy
                     )
                 }
@@ -376,7 +376,7 @@
             ReusableContentHost(active) {
                 ReusableContent(reuseKey) {
                     Layout(
-                        modifier = StatelessModifierElement(onInvalidate, size),
+                        modifier = StatelessElement(onInvalidate, size),
                         measurePolicy = MeasurePolicy
                     )
                 }
@@ -411,7 +411,7 @@
         rule.setContent {
             ReusableContent(reuseKey) {
                 Layout(
-                    modifier = DelegatingModifierElement(onReset),
+                    modifier = DelegatingElement(onReset),
                     measurePolicy = MeasurePolicy
                 )
             }
@@ -443,7 +443,7 @@
             ReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
-                        modifier = LayerModifierElement(layerBlock),
+                        modifier = LayerElement(layerBlock),
                         measurePolicy = MeasurePolicy
                     )
                 }
@@ -490,7 +490,7 @@
             ReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
-                        modifier = LayoutModifierElement(measureBlock),
+                        modifier = LayoutElement(measureBlock),
                         measurePolicy = MeasurePolicy
                     )
                 }
@@ -620,7 +620,7 @@
             ReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
-                        modifier = DrawModifierElement(drawBlock),
+                        modifier = DrawElement(drawBlock),
                         measurePolicy = MeasurePolicy
                     )
                 }
@@ -750,7 +750,7 @@
             ReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
-                        modifier = ObserverModifierElement(observedBlock),
+                        modifier = ObserverElement(observedBlock),
                         measurePolicy = MeasurePolicy
                     )
                 }
@@ -923,10 +923,10 @@
     layout(100, 100) { }
 }
 
-private data class StatelessModifierElement(
+private data class StatelessElement(
     private val onInvalidate: () -> Unit,
     private val size: Int = 10
-) : ModifierNodeElement<StatelessModifierElement.Node>() {
+) : ModifierNodeElement<StatelessElement.Node>() {
     override fun create() = Node(size, onInvalidate)
 
     override fun update(node: Node) {
@@ -948,9 +948,9 @@
     }
 }
 
-private data class DelegatingModifierElement(
+private data class DelegatingElement(
     private val onDelegatedNodeReset: () -> Unit,
-) : ModifierNodeElement<DelegatingModifierElement.Node>() {
+) : ModifierNodeElement<DelegatingElement.Node>() {
     override fun create() = Node(onDelegatedNodeReset)
 
     override fun update(node: Node) {
@@ -968,9 +968,9 @@
     }
 }
 
-private data class LayerModifierElement(
+private data class LayerElement(
     private val layerBlock: () -> Unit,
-) : ModifierNodeElement<LayerModifierElement.Node>() {
+) : ModifierNodeElement<LayerElement.Node>() {
     override fun create() = Node(layerBlock)
 
     override fun update(node: Node) {
@@ -992,16 +992,16 @@
     }
 }
 
-private data class ObserverModifierElement(
+private data class ObserverElement(
     private val observedBlock: () -> Unit,
-) : ModifierNodeElement<ObserverModifierElement.Node>() {
+) : ModifierNodeElement<ObserverElement.Node>() {
     override fun create() = Node(observedBlock)
 
     override fun update(node: Node) {
         node.observedBlock = observedBlock
     }
 
-    class Node(var observedBlock: () -> Unit) : Modifier.Node(), ObserverNode {
+    class Node(var observedBlock: () -> Unit) : Modifier.Node(), ObserverModifierNode {
 
         override fun onAttach() {
             observe()
@@ -1019,9 +1019,9 @@
     }
 }
 
-private data class LayoutModifierElement(
+private data class LayoutElement(
     private val measureBlock: () -> Unit,
-) : ModifierNodeElement<LayoutModifierElement.Node>() {
+) : ModifierNodeElement<LayoutElement.Node>() {
     override fun create() = Node(measureBlock)
 
     override fun update(node: Node) {
@@ -1057,9 +1057,9 @@
     }
 }
 
-private data class DrawModifierElement(
+private data class DrawElement(
     private val drawBlock: () -> Unit,
-) : ModifierNodeElement<DrawModifierElement.Node>() {
+) : ModifierNodeElement<DrawElement.Node>() {
     override fun create() = Node(drawBlock)
 
     override fun update(node: Node) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNodeTest.kt
index 451605f..efa7400 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNodeTest.kt
@@ -95,7 +95,7 @@
             }
         }
         rule.setContent {
-            testLayout(modifierNodeElementOf { node })
+            testLayout(elementOf { node })
         }
 
         rule.runOnIdle {
@@ -115,7 +115,7 @@
         }
         rule.setContent {
             CompositionLocalProvider(localInt provides 2) {
-                testLayout(modifierNodeElementOf { node })
+                testLayout(elementOf { node })
             }
         }
 
@@ -138,7 +138,7 @@
         }
         rule.setContent {
             CompositionLocalProvider(localInt provides providedValue) {
-                testLayout(modifierNodeElementOf { node })
+                testLayout(elementOf { node })
             }
         }
 
@@ -165,7 +165,7 @@
             }
         }
         rule.setContent {
-            testLayout(modifierNodeElementOf { node })
+            testLayout(elementOf { node })
         }
 
         rule.runOnIdle {
@@ -185,7 +185,7 @@
         }
         rule.setContent {
             CompositionLocalProvider(staticLocalInt provides 2) {
-                testLayout(modifierNodeElementOf { node })
+                testLayout(elementOf { node })
             }
         }
 
@@ -208,7 +208,7 @@
         }
         rule.setContent {
             CompositionLocalProvider(staticLocalInt provides providedValue) {
-                testLayout(modifierNodeElementOf { node })
+                testLayout(elementOf { node })
             }
         }
 
@@ -232,7 +232,7 @@
         var providedValue by mutableStateOf(42)
 
         var contentKey by mutableStateOf(1)
-        val modifier = modifierNodeElementOf {
+        val modifier = elementOf {
             object : Modifier.Node(), DrawModifierNode,
                 CompositionLocalConsumerModifierNode {
                 override fun ContentDrawScope.draw() {
@@ -270,7 +270,7 @@
         var providedValue by mutableStateOf(32)
 
         var contentKey by mutableStateOf(1)
-        val modifier = modifierNodeElementOf {
+        val modifier = elementOf {
             object : Modifier.Node(), DrawModifierNode,
                 CompositionLocalConsumerModifierNode {
                 override fun ContentDrawScope.draw() {
@@ -300,7 +300,7 @@
         }
     }
 
-    private inline fun <reified T : Modifier.Node> modifierNodeElementOf(
+    private inline fun <reified T : Modifier.Node> elementOf(
         crossinline create: () -> T
     ): ModifierNodeElement<T> = object : ModifierNodeElement<T>() {
         override fun create(): T = create()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
index cddab8a..e69de29 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/DelegatableNodeTest.kt
@@ -1,608 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.node
-
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusTargetNode
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalComposeUiApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DelegatableNodeTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    @Test
-    fun visitChildren_noChildren() {
-        // Arrange.
-        val testNode = object : Modifier.Node() {}
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(modifier = modifierElementOf { testNode })
-        }
-
-        // Act.
-        rule.runOnIdle {
-            testNode.visitChildren(Nodes.Any) {
-                visitedChildren.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren).isEmpty()
-    }
-
-    @Test
-    fun visitChildWithinCurrentLayoutNode_immediateChild() {
-        // Arrange.
-        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-                    .then(modifierElementOf { node3 })
-            )
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node1.visitChildren(Nodes.Any) {
-                visitedChildren.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren).containsExactly(node2)
-    }
-
-    @Test
-    fun visitChildWithinCurrentLayoutNode_nonContiguousChild() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .otherModifier()
-                    .then(modifierElementOf { node2 })
-            )
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node1.visitChildren(Nodes.Any) {
-                visitedChildren.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren).containsExactly(node2)
-    }
-
-    @Test
-    fun visitChildrenInOtherLayoutNodes() {
-        // Arrange.
-        val (node1, node2, node3, node4, node5) = List(5) { object : Modifier.Node() {} }
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box(modifier = modifierElementOf { node2 }) {
-                    Box(modifier = modifierElementOf { node3 })
-                }
-                Box(modifier = modifierElementOf { node4 }) {
-                    Box(modifier = modifierElementOf { node5 })
-                }
-            }
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node1.visitChildren(Nodes.Any) {
-                visitedChildren.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren).containsExactly(node2, node4).inOrder()
-    }
-
-    @Test
-    fun visitSubtreeIf_stopsIfWeReturnFalse() {
-        // Arrange.
-        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
-        val (node4, node5, node6) = List(3) { object : Modifier.Node() {} }
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box(modifier = modifierElementOf { node2 }) {
-                    Box(modifier = modifierElementOf { node3 })
-                    Box(modifier = modifierElementOf { node4 }) {
-                        Box(modifier = modifierElementOf { node6 })
-                    }
-                    Box(modifier = modifierElementOf { node5 })
-                }
-            }
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node1.visitSubtreeIf(Nodes.Any) {
-                visitedChildren.add(it)
-                // return false if we encounter node4
-                it != node4
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren).containsExactly(node2, node3, node4, node5).inOrder()
-    }
-
-    @Test
-    fun visitSubtreeIf_continuesIfWeReturnTrue() {
-        // Arrange.
-        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
-        val (node4, node5, node6) = List(3) { object : Modifier.Node() {} }
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box(modifier = modifierElementOf { node2 }) {
-                    Box(modifier = modifierElementOf { node3 })
-                    Box(modifier = modifierElementOf { node4 }) {
-                        Box(modifier = modifierElementOf { node6 })
-                    }
-                    Box(modifier = modifierElementOf { node5 })
-                }
-            }
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node1.visitSubtreeIf(Nodes.Any) {
-                visitedChildren.add(it)
-                true
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren).containsExactly(node2, node3, node4, node6, node5).inOrder()
-    }
-
-    @Test
-    fun visitSubtree_visitsItemsInCurrentLayoutNode() {
-        // Arrange.
-        val (node1, node2, node3, node4, node5) = List(6) { object : Modifier.Node() {} }
-        val (node6, node7, node8, node9, node10) = List(6) { object : Modifier.Node() {} }
-        val visitedChildren = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-            ) {
-                Box(
-                    modifier = modifierElementOf { node3 }
-                        .then(modifierElementOf { node4 })
-                ) {
-                    Box(
-                        modifier = modifierElementOf { node7 }
-                            .then(modifierElementOf { node8 })
-                    )
-                }
-                Box(
-                    modifier = modifierElementOf { node5 }
-                        .then(modifierElementOf { node6 })
-                ) {
-                    Box(
-                        modifier = modifierElementOf { node9 }
-                            .then(modifierElementOf { node10 })
-                    )
-                }
-            }
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node1.visitSubtreeIf(Nodes.Any) {
-                visitedChildren.add(it)
-                true
-            }
-        }
-
-        // Assert.
-        assertThat(visitedChildren)
-            .containsExactly(node2, node3, node4, node7, node8, node5, node6, node9, node10)
-            .inOrder()
-    }
-
-    @Test
-    fun visitAncestorWithinCurrentLayoutNode_immediateParent() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        val visitedAncestors = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-            )
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node2.visitAncestors(Nodes.Any) {
-                visitedAncestors.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedAncestors.first()).isEqualTo(node1)
-    }
-
-    @Test
-    fun visitAncestorWithinCurrentLayoutNode_nonContiguousAncestor() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        val visitedAncestors = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .border(10.dp, Color.Red)
-                    .then(modifierElementOf { node2 })
-            )
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node2.visitAncestors(Nodes.Any) {
-                visitedAncestors.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedAncestors).contains(node1)
-    }
-
-    @Test
-    fun visitAncestorsInOtherLayoutNodes() {
-        // Arrange.
-        val (node1, node2, node3, node4, node5) = List(5) { object : Modifier.Node() {} }
-        val visitedAncestors = mutableListOf<Modifier.Node>()
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box(
-                    modifier = Modifier
-                        .then(modifierElementOf { node2 })
-                        .then(modifierElementOf { node3 })
-                ) {
-                    Box {
-                        Box(
-                            modifier = Modifier
-                                .then(modifierElementOf { node4 })
-                                .then(modifierElementOf { node5 })
-                        )
-                    }
-                }
-            }
-        }
-
-        // Act.
-        rule.runOnIdle {
-            node5.visitAncestors(Nodes.Any) {
-                visitedAncestors.add(it)
-            }
-        }
-
-        // Assert.
-        assertThat(visitedAncestors)
-            .containsAtLeastElementsIn(arrayOf(node4, node3, node2, node1))
-            .inOrder()
-    }
-
-    @Test
-    fun nearestAncestorInDifferentLayoutNode_nonContiguousParentLayoutNode() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box {
-                    Box(modifier = modifierElementOf { node2 })
-                }
-            }
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            node2.nearestAncestor(Nodes.Any)
-        }
-
-        // Assert.
-        assertThat(parent).isEqualTo(node1)
-    }
-
-    @Test
-    fun visitAncestors_sameLayoutNode_calledDuringOnDetach() {
-        // Arrange.
-        val (node1, node2) = List(5) { object : Modifier.Node() {} }
-        val visitedAncestors = mutableListOf<Modifier.Node>()
-        val detachableNode = DetachableNode { node ->
-            node.visitAncestors(Nodes.Any) { visitedAncestors.add(it) }
-        }
-        val removeNode = mutableStateOf(false)
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-                    .then(if (removeNode.value) Modifier else modifierElementOf { detachableNode })
-            )
-        }
-
-        // Act.
-        rule.runOnIdle { removeNode.value = true }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(visitedAncestors)
-                .containsAtLeastElementsIn(arrayOf(node2, node1))
-                .inOrder()
-        }
-    }
-
-    @Test
-    fun visitAncestors_multipleLayoutNodes_calledDuringOnDetach() {
-        // Arrange.
-        val (node1, node2) = List(5) { object : Modifier.Node() {} }
-        val visitedAncestors = mutableListOf<Modifier.Node>()
-        val detachableNode = DetachableNode { node ->
-            node.visitAncestors(Nodes.Any) { visitedAncestors.add(it) }
-        }
-        val removeNode = mutableStateOf(false)
-        rule.setContent {
-            Box(modifierElementOf { node1 }) {
-                Box(modifierElementOf { node2 }) {
-                    Box(if (removeNode.value) Modifier else modifierElementOf { detachableNode })
-                }
-            }
-        }
-
-        // Act.
-        rule.runOnIdle { removeNode.value = true }
-
-        // Assert.
-        rule.runOnIdle {
-            assertThat(visitedAncestors)
-                .containsAtLeastElementsIn(arrayOf(node2, node1))
-                .inOrder()
-        }
-    }
-
-    @Test
-    fun nearestAncestorWithinCurrentLayoutNode_immediateParent() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-            )
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            node2.nearestAncestor(Nodes.Any)
-        }
-
-        // Assert.
-        assertThat(parent).isEqualTo(node1)
-    }
-
-    @Test
-    fun nearestAncestorWithinCurrentLayoutNode_nonContiguousAncestor() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .otherModifier()
-                    .then(modifierElementOf { node2 })
-            )
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            node2.nearestAncestor(Nodes.Any)
-        }
-
-        // Assert.
-        assertThat(parent).isEqualTo(node1)
-    }
-
-    @Test
-    fun nearestAncestorInDifferentLayoutNode_immediateParentLayoutNode() {
-        // Arrange.
-        val (node1, node2) = List(2) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box(modifier = modifierElementOf { node2 })
-            }
-        }
-
-        // Act.
-        val parent = rule.runOnIdle {
-            node2.nearestAncestor(Nodes.Any)
-        }
-
-        // Assert.
-        assertThat(parent).isEqualTo(node1)
-    }
-
-    @Test
-    fun findAncestors() {
-        // Arrange.
-        val (node1, node2, node3, node4) = List(4) { FocusTargetNode() }
-        val (node5, node6, node7, node8) = List(4) { FocusTargetNode() }
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-            ) {
-                Box {
-                    Box(modifier = modifierElementOf { node3 })
-                    Box(
-                        modifier = modifierElementOf { node4 }
-                            .then(modifierElementOf { node5 })
-                    ) {
-                        Box(
-                            modifier = modifierElementOf { node6 }
-                                .then(modifierElementOf { node7 })
-                        )
-                    }
-                    Box(modifier = modifierElementOf { node8 })
-                }
-            }
-        }
-
-        // Act.
-        val ancestors = rule.runOnIdle {
-            node6.ancestors(Nodes.FocusTarget)
-        }
-
-        // Assert.
-        // This test returns all ancestors, even the root focus node. so we drop that one.
-        assertThat(ancestors?.dropLast(1)).containsExactly(node5, node4, node2, node1).inOrder()
-    }
-
-    @Test
-    fun firstChild_currentLayoutNode() {
-        // Arrange.
-        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .then(modifierElementOf { node2 })
-                    .then(modifierElementOf { node3 })
-            )
-        }
-
-        // Act.
-        val child = rule.runOnIdle {
-            node1.child
-        }
-
-        // Assert.
-        assertThat(child).isEqualTo(node2)
-    }
-
-    @Test
-    fun firstChild_currentLayoutNode_nonContiguousChild() {
-        // Arrange.
-        val (node1, node2) = List(3) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(
-                modifier = modifierElementOf { node1 }
-                    .otherModifier()
-                    .then(modifierElementOf { node2 })
-            )
-        }
-
-        // Act.
-        val child = rule.runOnIdle {
-            node1.child
-        }
-
-        // Assert.
-        assertThat(child).isEqualTo(node2)
-    }
-
-    fun firstChild_differentLayoutNode_nonContiguousChild() {
-        // Arrange.
-        val (node1, node2) = List(3) { object : Modifier.Node() {} }
-        rule.setContent {
-            Box(modifier = modifierElementOf { node1 }) {
-                Box {
-                    Box(modifier = Modifier.otherModifier()) {
-                        Box(modifier = modifierElementOf { node2 })
-                    }
-                }
-            }
-        }
-
-        // Act.
-        val child = rule.runOnIdle {
-            node1.child
-        }
-
-        // Assert.
-        assertThat(child).isEqualTo(node2)
-    }
-
-    @Test
-    fun delegatedNodeGetsCoordinator() {
-        val node = object : DelegatingNode() {
-            val inner = delegate(
-                object : Modifier.Node() { }
-            )
-        }
-
-        rule.setContent {
-            Box(modifier = modifierElementOf { node })
-        }
-
-        rule.runOnIdle {
-            assertThat(node.isAttached).isTrue()
-            assertThat(node.coordinator).isNotNull()
-            assertThat(node.inner.isAttached).isTrue()
-            assertThat(node.inner.coordinator).isNotNull()
-            assertThat(node.inner.coordinator).isEqualTo(node.coordinator)
-        }
-    }
-
-    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
-
-    private inline fun <reified T : Modifier.Node> modifierElementOf(noinline create: () -> T) =
-        ModifierElementOf(create)
-
-    private data class ModifierElementOf<T : Modifier.Node>(
-        val factory: () -> T
-    ) : ModifierNodeElement<T>() {
-        override fun create(): T = factory()
-        override fun update(node: T) {}
-        override fun InspectorInfo.inspectableProperties() {
-            name = "testNode"
-        }
-    }
-
-    private class DetachableNode(val onDetach: (DetachableNode) -> Unit) : Modifier.Node() {
-        override fun onDetach() {
-            onDetach.invoke(this)
-            super.onDetach()
-        }
-    }
-}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt
index 2e3d335..e416db9 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/InvalidateSubtreeTest.kt
@@ -17,7 +17,6 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -37,7 +36,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class InvalidateSubtreeTest {
@@ -50,7 +48,7 @@
         val counter1 = LayoutAndDrawCounter()
         val counter2 = LayoutAndDrawCounter()
         val counter3 = LayoutAndDrawCounter()
-        val captureInvalidate = CaptureInvalidateCounter { node ->
+        val captureInvalidate = CaptureInvalidateCounterElement { node ->
             invalidate = { node.invalidateSubtree() }
         }
         rule.setContent {
@@ -95,7 +93,7 @@
         val counter2 = LayoutAndDrawCounter()
         val counter3 = LayoutAndDrawCounter()
         val counter4 = LayoutAndDrawCounter()
-        val captureInvalidate = CaptureInvalidateCounter { node ->
+        val captureInvalidate = CaptureInvalidateCounterElement { node ->
             invalidate = { node.invalidateSubtree() }
         }
         rule.setContent {
@@ -146,7 +144,7 @@
         }
     }
 
-    private class CaptureInvalidateCounter(
+    private class CaptureInvalidateCounterElement(
         private val onCreate: (node: Modifier.Node) -> Unit
     ) : ModifierNodeElement<Modifier.Node>() {
         override fun create() = object : Modifier.Node() {}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAncestorsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAncestorsTest.kt
new file mode 100644
index 0000000..79f3158
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAncestorsTest.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusTargetNode
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeAncestorsTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noAncestors() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        val ancestors = rule.runOnIdle {
+            node.ancestors(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(ancestors?.trimRootModifierNodes()).isEmpty()
+    }
+
+    @Test
+    fun noMatchingAncestors() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        val ancestors = rule.runOnIdle {
+            node.ancestors(Nodes.GlobalPositionAware)
+        }
+
+        // Assert.
+        assertThat(ancestors).isNull()
+    }
+
+    @Test
+    fun returnsLocalAncestors() {
+        // Arrange.
+        val (node, localAncestor1, localAncestor2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .elementOf(localAncestor2)
+                    .elementOf(localAncestor1)
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.ancestors(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result?.trimRootModifierNodes())
+            .containsExactly(localAncestor1, localAncestor2)
+            .inOrder()
+    }
+
+    @Test
+    fun returnsAncestors() {
+        // Arrange.
+        val (node, ancestor1, ancestor2, other) = List(4) { object : Modifier.Node() {} }
+        val (localAncestor1, localAncestor2) = List(2) { object : Modifier.Node() {} }
+
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .elementOf(ancestor2)
+                    .elementOf(ancestor1)
+            ) {
+                Box {
+                    Box(
+                        modifier = Modifier
+                            .elementOf(localAncestor2)
+                            .elementOf(localAncestor1)
+                            .elementOf(node)
+                    )
+                }
+            }
+            Box(Modifier.elementOf(other))
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.ancestors(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result?.trimRootModifierNodes())
+            .containsExactly(localAncestor1, localAncestor2, ancestor1, ancestor2)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun doesNotReturnUnattachedAncestors() {
+        // Arrange.
+        val (ancestor4, ancestor2, ancestor3, ancestor1) = List(4) { object : Modifier.Node() {} }
+        val (node, localAncestor1) = List(3) { object : Modifier.Node() {} }
+        val (localAncestor2, localAncestor3) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .elementOf(ancestor4)
+                    .elementOf(ancestor3)
+                    .elementOf(ancestor2)
+            ) {
+                Box(Modifier.elementOf(ancestor1)) {
+                    Box(
+                        modifier = Modifier
+                            .elementOf(localAncestor3)
+                            .elementOf(localAncestor2)
+                            .elementOf(localAncestor1)
+                            .elementOf(node)
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            ancestor3.detach()
+            ancestor4.detach()
+            localAncestor1.detach()
+            localAncestor3.detach()
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.ancestors(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result?.trimRootModifierNodes())
+            .containsExactly(localAncestor2, ancestor1, ancestor2)
+            .inOrder()
+    }
+
+    @Test
+    fun findAncestorsOfType() {
+        // Arrange.
+        val (ancestor1, ancestor2, ancestor3, ancestor4) = List(4) { FocusTargetNode() }
+        val (node, other1, other2, other3) = List(4) { FocusTargetNode() }
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor4)
+                    .elementOf(ancestor3)
+            ) {
+                Box {
+                    Box(Modifier.elementOf(other1))
+                    Box(
+                        Modifier
+                            .elementOf(ancestor2)
+                            .elementOf(ancestor1)
+                    ) {
+                        Box(
+                            Modifier
+                                .elementOf(node)
+                                .elementOf(other3)
+                        )
+                    }
+                    Box(Modifier.elementOf(other2))
+                }
+            }
+        }
+
+        // Act.
+        val ancestors = rule.runOnIdle {
+            node.ancestors(Nodes.FocusTarget)
+        }
+
+        // Assert.
+        // This test returns all ancestors, even the root focus node. so we drop that one.
+        assertThat(ancestors?.dropLast(1))
+            .containsExactly(ancestor1, ancestor2, ancestor3, ancestor4)
+            .inOrder()
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeChildTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeChildTest.kt
new file mode 100644
index 0000000..2442af3
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeChildTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeChildTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noChildren() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.child
+        }
+
+        // Assert.
+        assertThat(result.toString()).isEqualTo("<tail>")
+    }
+
+    @Test
+    fun localChild() {
+        // Arrange.
+        val (node, localChild1, localChild2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier.elementOf(node)
+                    .elementOf(localChild1)
+                    .elementOf(localChild2)
+            )
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.child
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(localChild1)
+    }
+
+    @Test
+    fun nonContiguousChild() {
+        // Arrange.
+        val (node, localChild) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier.elementOf(node)
+                    .otherModifier()
+                    .elementOf(localChild)
+            )
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.child
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(localChild)
+    }
+
+    @Test
+    fun doesNotReturnChildInDifferentLayoutNode() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(
+                    Modifier
+                        .elementOf(child1)
+                        .elementOf(child2)
+                )
+            }
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.child
+        }
+
+        // Assert.
+        assertThat(result.toString()).isEqualTo("<tail>")
+    }
+
+    fun differentLayoutNode_nonContiguousChild() {
+        // Arrange.
+        val (node, child) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box {
+                    Box(Modifier.otherModifier()) {
+                        Box(Modifier.elementOf(child))
+                    }
+                }
+            }
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.child
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(child)
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun withinCurrentLayoutNode_skipsUnAttachedChild() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+            )
+        }
+        rule.runOnIdle {
+            child1.detach()
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.child
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(child2)
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeNearestAncestorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeNearestAncestorTest.kt
new file mode 100644
index 0000000..cbf1f92
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeNearestAncestorTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeNearestAncestorTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noAncestors() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.GlobalPositionAware)
+        }
+
+        // Assert.
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun nearestAncestorInDifferentLayoutNode_nonContiguousParentLayoutNode() {
+        // Arrange.
+        val (ancestor, node) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(Modifier.elementOf(ancestor)) {
+                Box {
+                    Box(Modifier.elementOf(node))
+                }
+            }
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor)
+    }
+
+    @Test
+    fun nearestAncestorWithinCurrentLayoutNode_immediateParent() {
+        // Arrange.
+        val (ancestor, node) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor)
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor)
+    }
+
+    @Test
+    fun nearestAncestorWithinCurrentLayoutNode_nonContiguousAncestor() {
+        // Arrange.
+        val (ancestor, node) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier.elementOf(ancestor)
+                    .otherModifier()
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor)
+    }
+
+    @Test
+    fun nearestAncestorInDifferentLayoutNode_immediateParentLayoutNode() {
+        // Arrange.
+        val (ancestor, node) = List(2) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(Modifier.elementOf(ancestor)) {
+                Box(Modifier.elementOf(node))
+            }
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor)
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun unattachedLocalAncestorIsSkipped() {
+        // Arrange.
+        val (ancestor1, ancestor2, node) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor2)
+                    .elementOf(ancestor1)
+                    .elementOf(node)
+            )
+        }
+        rule.runOnIdle {
+            ancestor1.detach()
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor2)
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun unattachedLocalAncestor_returnsAncestorInParentNode() {
+        // Arrange.
+        val (ancestor, localAncestor, node) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(Modifier.elementOf(ancestor)) {
+                Box {
+                    Box(
+                        Modifier
+                            .elementOf(localAncestor)
+                            .elementOf(node)
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            localAncestor.detach()
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor)
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun unattachedAncestorInParentNodeIsSkipped() {
+        // Arrange.
+        val (ancestor1, ancestor2, node) = List(3) { object : Modifier.Node() {} }
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor2)
+                    .elementOf(ancestor1)
+            ) {
+                Box(Modifier.elementOf(node))
+            }
+        }
+        rule.runOnIdle {
+            ancestor1.detach()
+        }
+
+        // Act.
+        val result = rule.runOnIdle {
+            node.nearestAncestor(Nodes.Any)
+        }
+
+        // Assert.
+        assertThat(result).isEqualTo(ancestor2)
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitAncestorsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitAncestorsTest.kt
new file mode 100644
index 0000000..5ff5683
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitAncestorsTest.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitAncestorsTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noAncestors() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors.trimRootModifierNodes()).isEmpty()
+    }
+
+    @Test
+    fun localAncestors() {
+        // Arrange.
+        val (node, localParent1, localParent2) = List(3) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(localParent2)
+                    .elementOf(localParent1)
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors.trimRootModifierNodes())
+            .containsExactly(localParent1, localParent2)
+            .inOrder()
+    }
+
+    @Test
+    fun nonContiguousLocalAncestors() {
+        // Arrange.
+        val (node, localParent1, localParent2) = List(3) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(localParent2)
+                    .otherModifier()
+                    .elementOf(localParent1)
+                    .otherModifier()
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors.trimRootModifierNodes())
+            .containsExactly(localParent1, localParent2)
+            .inOrder()
+    }
+
+    @Test
+    fun ancestorsInOtherLayoutNodes() {
+        // Arrange.
+        val (node, localParent) = List(2) { object : Modifier.Node() {} }
+        val (ancestor1, ancestor2, ancestor3) = List(3) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(ancestor3)) {
+                Box(
+                    Modifier
+                        .elementOf(ancestor2)
+                        .elementOf(ancestor1)
+                ) {
+                    Box {
+                        Box(
+                            Modifier
+                                .elementOf(localParent)
+                                .elementOf(node)
+                        )
+                    }
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors.trimRootModifierNodes())
+            .containsExactly(localParent, ancestor1, ancestor2, ancestor3)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun unattachedAncestorsAreSkipped() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        val (localParent1, localParent2, localParent3) = List(3) { object : Modifier.Node() {} }
+        val (ancestor1, ancestor2, ancestor3, ancestor4) = List(4) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(ancestor4)) {
+                Box(Modifier.elementOf(ancestor3)) {
+                    Box(
+                        Modifier
+                            .elementOf(ancestor2)
+                            .elementOf(ancestor1)
+                    ) {
+                        Box {
+                            Box(
+                                Modifier
+                                    .elementOf(localParent3)
+                                    .elementOf(localParent2)
+                                    .elementOf(localParent1)
+                                    .elementOf(node)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            ancestor2.detach()
+            ancestor3.detach()
+            localParent1.detach()
+            localParent3.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors.trimRootModifierNodes())
+            .containsExactly(localParent2, ancestor1, ancestor4)
+            .inOrder()
+    }
+
+    @Test
+    fun localAncestorsAreAvailableDuringOnDetach() {
+        // Arrange.
+        val (localParent1, localParent2) = List(2) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        val detachableNode = DetachableNode { node ->
+            node.visitAncestors(Nodes.Any) { visitedAncestors.add(it) }
+        }
+        var removeNode by mutableStateOf(false)
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .elementOf(localParent2)
+                    .elementOf(localParent1)
+                    .then(if (removeNode) Modifier else Modifier.elementOf(detachableNode))
+            )
+        }
+
+        // Act.
+        rule.runOnIdle { removeNode = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(visitedAncestors)
+                .containsAtLeastElementsIn(arrayOf(localParent1, localParent2))
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun ancestorsAcrossMultipleLayoutNodesAreAvailableDuringOnDetach() {
+        // Arrange.
+        val (ancestor1, ancestor2, ancestor3, ancestor4) = List(4) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        val detachableNode = DetachableNode { node ->
+            node.visitAncestors(Nodes.Any) { visitedAncestors.add(it) }
+        }
+        var removeNode by mutableStateOf(false)
+        rule.setContent {
+            Box(Modifier.elementOf(ancestor4)) {
+                Box(
+                    Modifier
+                        .elementOf(ancestor3)
+                        .elementOf(ancestor2)
+                ) {
+                    Box(
+                        Modifier
+                            .elementOf(ancestor1)
+                            .then(
+                                if (removeNode) Modifier else Modifier.elementOf(detachableNode)
+                            )
+                    )
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle { removeNode = true }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(visitedAncestors)
+                .containsAtLeastElementsIn(arrayOf(ancestor1, ancestor2, ancestor3, ancestor4))
+                .inOrder()
+        }
+    }
+
+    private class DetachableNode(val onDetach: (DetachableNode) -> Unit) : Modifier.Node() {
+        override fun onDetach() {
+            onDetach.invoke(this)
+            super.onDetach()
+        }
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitChildrenTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitChildrenTest.kt
new file mode 100644
index 0000000..51e7f41
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitChildrenTest.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitChildrenTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noChildren() {
+        // Arrange.
+        val testNode = object : Modifier.Node() {}
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(testNode))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            testNode.visitChildren(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).isEmpty()
+    }
+
+    @Test
+    fun localChildren() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitChildren(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).containsExactly(child1)
+    }
+
+    @Test
+    fun nonContiguousLocalChild() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .otherModifier()
+                    .elementOf(child1)
+                    .otherModifier()
+                    .elementOf(child2)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitChildren(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).containsExactly(child1)
+    }
+
+    @Test
+    fun visitChildrenInOtherLayoutNodes() {
+        // Arrange.
+        val (node, child1, child2, child3) = List(4) { object : Modifier.Node() {} }
+        val (grandchild1, grandchild2) = List(2) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(Modifier.elementOf(child1)) {
+                    Box(Modifier.elementOf(grandchild1))
+                }
+                Box(Modifier.elementOf(child2)) {
+                    Box(Modifier.elementOf(grandchild2))
+                }
+                Box(Modifier.elementOf(child3))
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitChildren(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(child1, child2, child3)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattachedLocalChild() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+            )
+        }
+        rule.runOnIdle {
+            child1.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitChildren(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(child2)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattachedChild() {
+        // Arrange.
+        val (node, child1, child2, child3) = List(4) { object : Modifier.Node() {} }
+        val (child4, child5, child6) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(Modifier.elementOf(child1))
+                Box(Modifier.elementOf(child2))
+                Box(Modifier.elementOf(child3)) {
+                    Box(Modifier.elementOf(child5))
+                    Box(Modifier.elementOf(child6))
+                }
+                Box(Modifier.elementOf(child4))
+            }
+        }
+        rule.runOnIdle {
+            child1.detach()
+            child3.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitChildren(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(child2, child4)
+            .inOrder()
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitLocalAncestorsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitLocalAncestorsTest.kt
new file mode 100644
index 0000000..d4ae52b
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitLocalAncestorsTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitLocalAncestorsTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noParents() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors).isEmpty()
+    }
+
+    @Test
+    fun doesNotVisitOtherLayoutNodes() {
+        // Arrange.
+        val (node, parent) = List(2) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(parent)) {
+                Box(Modifier.elementOf(node))
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors).isEmpty()
+    }
+
+    @Test
+    fun multipleAncestors() {
+        // Arrange.
+        val (node, ancestor1, ancestor2) = List(3) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor2)
+                    .elementOf(ancestor1)
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors)
+            .containsExactly(ancestor1, ancestor2)
+            .inOrder()
+    }
+
+    @Test
+    fun doesNotVisitChild() {
+        // Arrange.
+        val (node, child, parent) = List(3) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(parent)
+                    .elementOf(node)
+                    .elementOf(child)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors).containsExactly(parent)
+    }
+
+    @Test
+    fun nonContiguousAncestors() {
+        // Arrange.
+        val (node, ancestor1, ancestor2) = List(3) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor2)
+                    .otherModifier()
+                    .elementOf(ancestor1)
+                    .otherModifier()
+                    .elementOf(node)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors)
+            .containsExactly(ancestor1, ancestor2)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattachedAncestors() {
+        // Arrange.
+        val (node, ancestor1, ancestor2, ancestor3) = List(4) { object : Modifier.Node() {} }
+        val visitedAncestors = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(ancestor3)
+                    .elementOf(ancestor2)
+                    .elementOf(ancestor1)
+                    .elementOf(node)
+            )
+        }
+        rule.runOnIdle {
+            ancestor1.detach()
+            ancestor3.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalAncestors(Nodes.Any) {
+                visitedAncestors.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedAncestors).containsExactly(ancestor2)
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitLocalDescendantsTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitLocalDescendantsTest.kt
new file mode 100644
index 0000000..b6b7b5f
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitLocalDescendantsTest.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitLocalDescendantsTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noChildren() {
+        // Arrange.
+        val testNode = object : Modifier.Node() {}
+        val visitedDescendants = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(testNode))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            testNode.visitLocalDescendants(Nodes.Any) {
+                visitedDescendants.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedDescendants).isEmpty()
+    }
+
+    @Test
+    fun doesNotVisitOtherLayoutNodes() {
+        // Arrange.
+        val (node, child) = List(3) { object : Modifier.Node() {} }
+        val visitedDescendants = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(Modifier.elementOf(child))
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalDescendants(Nodes.Any) {
+                visitedDescendants.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedDescendants).isEmpty()
+    }
+
+    @Test
+    fun multipleDescendants() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedDescendants = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalDescendants(Nodes.Any) {
+                visitedDescendants.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedDescendants)
+            .containsExactly(child1, child2)
+            .inOrder()
+    }
+
+    @Test
+    fun doesNotVisitParent() {
+        // Arrange.
+        val (node, child, parent) = List(3) { object : Modifier.Node() {} }
+        val visitedDescendants = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(parent)
+                    .elementOf(node)
+                    .elementOf(child)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalDescendants(Nodes.Any) {
+                visitedDescendants.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedDescendants).containsExactly(child)
+    }
+
+    @Test
+    fun nonContiguousChildren() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedDescendants = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .otherModifier()
+                    .elementOf(child1)
+                    .otherModifier()
+                    .elementOf(child2)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalDescendants(Nodes.Any) {
+                visitedDescendants.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedDescendants)
+            .containsExactly(child1, child2)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattachedChild() {
+        // Arrange.
+        val (node, child1, child2, child3, child4) = List(5) { object : Modifier.Node() {} }
+        val visitedDescendants = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+                    .elementOf(child3)
+                    .elementOf(child4)
+            )
+        }
+        rule.runOnIdle {
+            child1.detach()
+            child3.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitLocalDescendants(Nodes.Any) {
+                visitedDescendants.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedDescendants)
+            .containsExactly(child2)
+            .inOrder()
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSelfAndChildrenTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSelfAndChildrenTest.kt
new file mode 100644
index 0000000..89c7f63
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSelfAndChildrenTest.kt
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitSelfAndChildrenTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noChildren() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes).containsExactly(node)
+    }
+
+    @Test
+    fun oneChild() {
+        // Arrange.
+        val (node, child) = List(2) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child)
+            .inOrder()
+    }
+
+    @Test
+    fun multipleNodesInModifierChain() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child1)
+            .inOrder()
+    }
+
+    @Test
+    fun nonContiguousChild() {
+        // Arrange.
+        val (node, child) = List(2) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .otherModifier()
+                    .elementOf(child)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child)
+            .inOrder()
+    }
+
+    @Test
+    fun childInDifferentLayoutNode() {
+        // Arrange.
+        val (node, child) = List(2) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(Modifier.elementOf(child))
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child)
+            .inOrder()
+    }
+
+    @Test
+    fun childrenInDifferentLayoutNode() {
+        // Arrange.
+        val (node, child1, child2) = List(3) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(Modifier.elementOf(child1))
+                Box(Modifier.elementOf(child2))
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child1, child2)
+            .inOrder()
+    }
+
+    @Test
+    fun childInDifferentLayoutNodeNonContiguous() {
+        // Arrange.
+        val (node, child) = List(2) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box(Modifier)
+                Box(Modifier.otherModifier()) {
+                    Box(Modifier.elementOf(child))
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child)
+            .inOrder()
+    }
+
+    @Test
+    fun childrenInDifferentLayoutNodeNonContiguous() {
+        // Arrange.
+        val (node, child1, child2, child3) = List(4) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node)) {
+                Box {
+                    Box(Modifier.elementOf(child1))
+                }
+                Box(Modifier.otherModifier()) {
+                    Box(Modifier.elementOf(child2))
+                }
+                Box(Modifier.elementOf(child3))
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child1, child2, child3)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattachedItems() {
+        val (node, child1, child2, child3) = List(4) { object : Modifier.Node() {} }
+        val (child4, child5, child6, child7) = List(4) { object : Modifier.Node() {} }
+        val visitedNodes = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+            ) {
+                Box {
+                    Box(
+                        Modifier
+                            .elementOf(child2)
+                            .elementOf(child3)
+                    )
+                }
+                Box(Modifier.otherModifier()) {
+                    Box(Modifier.elementOf(child4))
+                }
+                Box(
+                    Modifier
+                        .elementOf(child5)
+                        .elementOf(child6)
+                        .elementOf(child7)
+                )
+            }
+        }
+        rule.runOnIdle {
+            child1.detach()
+            child2.detach()
+            child6.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSelfAndChildren(Nodes.Any) {
+                visitedNodes.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedNodes)
+            .containsExactly(node, child3, child4, child5, child7)
+            .inOrder()
+    }
+
+    private fun Modifier.otherModifier(): Modifier = this.then(Modifier)
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSubtreeIfTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSubtreeIfTest.kt
new file mode 100644
index 0000000..4508164
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSubtreeIfTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitSubtreeIfTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noChildren() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).isEmpty()
+    }
+
+    @Test
+    fun stopsIfWeReturnFalse() {
+        // Arrange.
+        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
+        val (node4, node5, node6) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node1)) {
+                Box(Modifier.elementOf(node2)) {
+                    Box(Modifier.elementOf(node3))
+                    Box(Modifier.elementOf(node4)) {
+                        Box(Modifier.elementOf(node6))
+                    }
+                    Box(Modifier.elementOf(node5))
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node1.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                // return false if we encounter node4
+                it != node4
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(node2, node3, node4, node5)
+            .inOrder()
+    }
+
+    @Test
+    fun continuesIfWeReturnTrue() {
+        // Arrange.
+        val (node1, node2, node3) = List(3) { object : Modifier.Node() {} }
+        val (node4, node5, node6) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node1)) {
+                Box(Modifier.elementOf(node2)) {
+                    Box(Modifier.elementOf(node3))
+                    Box(Modifier.elementOf(node4)) {
+                        Box(Modifier.elementOf(node6))
+                    }
+                    Box(Modifier.elementOf(node5))
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node1.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                true
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(node2, node3, node4, node6, node5)
+            .inOrder()
+    }
+
+    @Test
+    fun visitsItemsAcrossLayoutNodes() {
+        // Arrange.
+        val (node1, node2, node3, node4, node5) = List(5) { object : Modifier.Node() {} }
+        val (node6, node7, node8, node9, node10) = List(5) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node1)
+                    .elementOf(node2)
+            ) {
+                Box(
+                    Modifier
+                        .elementOf(node3)
+                        .elementOf(node4)
+                ) {
+                    Box(
+                        Modifier
+                            .elementOf(node7)
+                            .elementOf(node8)
+                    )
+                }
+                Box(
+                    Modifier
+                        .elementOf(node5)
+                        .elementOf(node6)
+                ) {
+                    Box(
+                        Modifier
+                            .elementOf(node9)
+                            .elementOf(node10)
+                    )
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node1.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                true
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(node2, node3, node4, node7, node8, node5, node6, node9, node10)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattachedItems() {
+        // Arrange.
+        val (parent, node, node1, node2, node3) = List(5) { object : Modifier.Node() {} }
+        val (node4, node5, node6, node7, node8) = List(5) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(parent)
+                    .elementOf(node)
+            ) {
+                Box(
+                    Modifier
+                        .elementOf(node1)
+                        .elementOf(node2)
+                ) {
+                    Box(Modifier.elementOf(node3)) {
+                        Box(Modifier.elementOf(node4))
+                    }
+                    Box(Modifier.elementOf(node5))
+                }
+                Box(Modifier.elementOf(node6)) {
+                    Box(
+                        Modifier
+                            .elementOf(node7)
+                            .elementOf(node8)
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            node2.detach()
+            node6.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSubtreeIf(Nodes.Any) {
+                visitedChildren.add(it)
+                true
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(node1, node3, node4, node5)
+            .inOrder()
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSubtreeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSubtreeTest.kt
new file mode 100644
index 0000000..1a45eb5f
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeVisitSubtreeTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ModifierNodeVisitSubtreeTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun noChildren() {
+        // Arrange.
+        val node = object : Modifier.Node() {}
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(Modifier.elementOf(node))
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSubtree(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).isEmpty()
+    }
+
+    @Test
+    fun localChildren() {
+        // Arrange.
+        val (parent, node, localChild1, localChild2) = List(4) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(parent)
+                    .elementOf(node)
+                    .elementOf(localChild1)
+                    .elementOf(localChild2)
+            )
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSubtree(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).containsExactly(localChild1, localChild2).inOrder()
+    }
+
+    // TODO(ralu): I feel that this order of visiting children is incorrect, and we should
+    //  visit children in the order of composition. So instead of a stack, we probably need
+    //  to use a queue to hold the intermediate nodes.
+    @Test
+    fun differentLayoutNodes() {
+        // Arrange.
+        val (node, child1, child2, child3, child4) = List(5) { object : Modifier.Node() {} }
+        val (child5, child6, child7, child8) = List(4) { object : Modifier.Node() {} }
+
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(child1)
+                    .elementOf(child2)
+            ) {
+                Box(
+                    Modifier
+                        .elementOf(child5)
+                        .elementOf(child6)
+                ) {
+                    Box(
+                        Modifier
+                            .elementOf(child7)
+                            .elementOf(child8)
+                    )
+                }
+                Box {
+                    Box(
+                        Modifier
+                            .elementOf(child3)
+                            .elementOf(child4)
+                    )
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSubtree(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren)
+            .containsExactly(child1, child2, child3, child4, child5, child6, child7, child8)
+            .inOrder()
+    }
+
+    @Ignore("b/278765590")
+    @Test
+    fun skipsUnattached() {
+        // Arrange.
+        val (node, localChild1, localChild2) = List(3) { object : Modifier.Node() {} }
+        val visitedChildren = mutableListOf<Modifier.Node>()
+        rule.setContent {
+            Box(
+                Modifier
+                    .elementOf(node)
+                    .elementOf(localChild1)
+                    .elementOf(localChild2)
+            )
+        }
+        rule.runOnIdle {
+            localChild1.detach()
+        }
+
+        // Act.
+        rule.runOnIdle {
+            node.visitSubtree(Nodes.Any) {
+                visitedChildren.add(it)
+            }
+        }
+
+        // Assert.
+        assertThat(visitedChildren).containsExactly(localChild2)
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
index a875d116..c96f1d2 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalComposeUiApi::class)
-
 package androidx.compose.ui.node
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.InspectorInfo
 import com.google.common.base.Objects
@@ -26,8 +23,8 @@
 
 internal fun chainTester() = NodeChainTester()
 
-class DiffLog() {
-    val oplog = mutableListOf<DiffOp>()
+class DiffLog {
+    private val oplog = mutableListOf<DiffOp>()
     fun op(op: DiffOp) = oplog.add(op)
     fun clear() = oplog.clear()
 
@@ -48,12 +45,9 @@
 }
 
 internal class NodeChainTester : NodeChain.Logger {
-    val layoutNode = LayoutNode()
+    private val layoutNode = LayoutNode()
     val chain = layoutNode.nodes.also { it.useLogger(this) }
-    val log = DiffLog()
-
-    val tail get() = chain.tail
-    val head get() = chain.head
+    private val log = DiffLog()
     val nodes: List<Modifier.Node>
         get() {
             val result = mutableListOf<Modifier.Node>()
@@ -136,8 +130,8 @@
 }
 
 sealed class DiffOp(
-    val element: Modifier.Element,
-    val opChar: String,
+    private val element: Modifier.Element,
+    private val opChar: String,
     val opString: String,
 ) {
     fun elementDiffString(): String {
@@ -146,12 +140,12 @@
 
     abstract fun debug(): String
     class Same(
-        val oldIndex: Int,
-        val newIndex: Int,
-        val beforeEl: Modifier.Element,
-        val afterEl: Modifier.Element,
-        val beforeEntity: Modifier.Node,
-        val afterEntity: Modifier.Node,
+        private val oldIndex: Int,
+        private val newIndex: Int,
+        private val beforeEl: Modifier.Element,
+        private val afterEl: Modifier.Element,
+        private val beforeEntity: Modifier.Node,
+        private val afterEntity: Modifier.Node,
         val updated: Boolean,
     ) : DiffOp(beforeEl, if (updated) "*" else " ", "Same") {
         override fun debug() = """
@@ -165,11 +159,11 @@
     }
 
     class Insert(
-        val oldIndex: Int,
-        val newIndex: Int,
-        val afterEl: Modifier.Element,
+        private val oldIndex: Int,
+        private val newIndex: Int,
+        private val afterEl: Modifier.Element,
         val child: Modifier.Node,
-        val inserted: Modifier.Node,
+        private val inserted: Modifier.Node,
     ) : DiffOp(afterEl, "+", "Insert") {
         override fun debug() = """
             <$opString>
@@ -181,9 +175,9 @@
     }
 
     class Remove(
-        val oldIndex: Int,
-        val beforeEl: Modifier.Element,
-        val beforeEntity: Modifier.Node,
+        private val oldIndex: Int,
+        private val beforeEl: Modifier.Element,
+        private val beforeEntity: Modifier.Node,
     ) : DiffOp(beforeEl, "-", "Remove") {
         override fun debug() = """
             <$opString>
@@ -215,7 +209,7 @@
 }
 
 fun modifierA(params: Any? = null): Modifier.Element {
-    return object : TestModifierElement<A>("a", params, A()) {}
+    return object : TestElement<A>("a", params, A()) {}
 }
 
 class B : Modifier.Node() {
@@ -223,7 +217,7 @@
 }
 
 fun modifierB(params: Any? = null): Modifier.Element {
-    return object : TestModifierElement<B>("b", params, B()) {}
+    return object : TestElement<B>("b", params, B()) {}
 }
 
 class C : Modifier.Node() {
@@ -231,11 +225,11 @@
 }
 
 fun modifierC(params: Any? = null): Modifier.Element {
-    return object : TestModifierElement<C>("c", params, C()) {}
+    return object : TestElement<C>("c", params, C()) {}
 }
 
 fun modifierD(params: Any? = null): Modifier.Element {
-    return object : TestModifierElement<Modifier.Node>("d", params,
+    return object : TestElement<Modifier.Node>("d", params,
         object : Modifier.Node() {}
     ) {}
 }
@@ -243,11 +237,11 @@
 fun managedModifier(
     name: String,
     params: Any? = null
-): ModifierNodeElement<*> = object : TestModifierElement<Modifier.Node>(name, params,
+): ModifierNodeElement<*> = object : TestElement<Modifier.Node>(name, params,
     object : Modifier.Node() {}
 ) {}
 
-private abstract class TestModifierElement<T : Modifier.Node>(
+private abstract class TestElement<T : Modifier.Node>(
     val modifierName: String,
     val param: Any? = null,
     val node: T
@@ -262,7 +256,7 @@
 
     override fun equals(other: Any?): Boolean {
         if (other === this) return true
-        return other is TestModifierElement<*> &&
+        return other is TestElement<*> &&
             javaClass == other.javaClass &&
             modifierName == other.modifierName &&
             param == other.param
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTests.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTests.kt
index 523ffc7..958ec8f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTests.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeChainTests.kt
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalComposeUiApi::class)
 package androidx.compose.ui.node
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.areObjectsOfSameType
 import com.google.common.truth.Truth.assertThat
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt
index d4d1129..339a671 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeCoordinatorInitializationTest.kt
@@ -97,4 +97,25 @@
             assertThat(pointerInputModifier.pointerInputFilter.layoutCoordinates).isNotNull()
         }
     }
+
+    @Test
+    fun delegatedNodeGetsCoordinator() {
+        val node = object : DelegatingNode() {
+            val inner = delegate(
+                object : Modifier.Node() { }
+            )
+        }
+
+        rule.setContent {
+            Box(modifier = Modifier.elementOf(node))
+        }
+
+        rule.runOnIdle {
+            assertThat(node.isAttached).isTrue()
+            assertThat(node.coordinator).isNotNull()
+            assertThat(node.inner.isAttached).isTrue()
+            assertThat(node.inner.coordinator).isNotNull()
+            assertThat(node.inner.coordinator).isEqualTo(node.coordinator)
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeUtils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeUtils.kt
new file mode 100644
index 0000000..b23f263
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/NodeUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.InspectorInfo
+
+/**
+ * Remove the root modifier nodes as they are not relevant from the perspective of the tests.
+ * There are 4 nodes: KeyInputNode, FocusTargetNode, RotaryInputNode and SemanticsNode.
+ */
+internal fun <T> List<T>.trimRootModifierNodes(): List<T> = dropLast(4)
+
+internal fun Modifier.elementOf(node: Modifier.Node): Modifier {
+    return this.then(ElementOf { node })
+}
+private data class ElementOf<T : Modifier.Node>(
+    val factory: () -> T
+) : ModifierNodeElement<T>() {
+    override fun create(): T = factory()
+    override fun update(node: T) {}
+    override fun InspectorInfo.inspectableProperties() { name = "testNode" }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverModifierNodeTest.kt
similarity index 82%
rename from compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt
rename to compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverModifierNodeTest.kt
index 620cc57..c6e8ea8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ObserverModifierNodeTest.kt
@@ -20,9 +20,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -31,10 +29,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-class ObserverNodeTest {
+class ObserverModifierNodeTest {
     @get:Rule
     val rule = createComposeRule()
 
@@ -45,7 +42,7 @@
         var callbackInvoked = false
         val observerNode = TestObserverNode { callbackInvoked = true }
         rule.setContent {
-            Box(Modifier.modifierElementOf(observerNode))
+            Box(Modifier.elementOf(observerNode))
         }
 
         // Act.
@@ -67,7 +64,7 @@
         var callbackInvoked = false
         val observerNode = TestObserverNode { callbackInvoked = true }
         rule.setContent {
-            Box(Modifier.modifierElementOf(observerNode))
+            Box(Modifier.elementOf(observerNode))
         }
 
         // Act.
@@ -115,7 +112,7 @@
         val observerNode = TestObserverNode { callbackInvoked = true }
         var attached by mutableStateOf(true)
         rule.setContent {
-            Box(if (attached) Modifier.modifierElementOf(observerNode) else Modifier)
+            Box(if (attached) Modifier.elementOf(observerNode) else Modifier)
         }
 
         // Act.
@@ -140,7 +137,7 @@
         val observerNode = TestObserverNode { callbackInvoked = true }
         var attached by mutableStateOf(true)
         rule.setContent {
-            Box(if (attached) Modifier.modifierElementOf(observerNode) else Modifier)
+            Box(if (attached) Modifier.elementOf(observerNode) else Modifier)
         }
 
         // Act.
@@ -161,24 +158,9 @@
         }
     }
 
-    @ExperimentalComposeUiApi
-    private inline fun <reified T : Modifier.Node> Modifier.modifierElementOf(node: T): Modifier {
-        return this then ModifierElementOf(node)
-    }
-
-    private data class ModifierElementOf<T : Modifier.Node>(
-        val node: T
-    ) : ModifierNodeElement<T>() {
-        override fun create(): T = node
-        override fun update(node: T) {}
-        override fun InspectorInfo.inspectableProperties() {
-            name = "testNode"
-        }
-    }
-
     class TestObserverNode(
         private val onObserveReadsChanged: () -> Unit,
-    ) : ObserverNode, Modifier.Node() {
+    ) : ObserverModifierNode, Modifier.Node() {
         override fun onObservedReadsChanged() {
             this.onObserveReadsChanged()
         }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt
index 99b94bb..3893b7b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DepthSortedSetTest.kt
@@ -162,4 +162,6 @@
         override val root: LayoutNode
             get() = LayoutNode()
     }
-}
\ No newline at end of file
+}
+
+private fun DepthSortedSet() = DepthSortedSet(extraAssertions = true)
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
index 90fc4fc..5731018 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
@@ -137,6 +137,42 @@
     }
 
     @Test
+    fun traversalIndexProperty() {
+        rule.setContent {
+            Surface {
+                Box(Modifier
+                    .semantics { traversalIndex = 0f }
+                    .testTag(TestTag)
+                ) {
+                    Text("Hello World", modifier = Modifier.padding(8.dp))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TestTag)
+            .assert(SemanticsMatcher.expectValue(
+                SemanticsProperties.TraversalIndex, 0f))
+    }
+
+    @Test
+    fun traversalIndexPropertyNull() {
+        rule.setContent {
+            Surface {
+                Box(Modifier
+                    .testTag(TestTag)
+                ) {
+                    Text("Hello World", modifier = Modifier.padding(8.dp))
+                }
+            }
+        }
+
+        // If traversalIndex is not explicitly set, the default value is zero, but
+        // only considered so when sorting in the DelegateCompat file
+        rule.onNodeWithTag(TestTag)
+            .assertDoesNotHaveProperty(SemanticsProperties.TraversalIndex)
+    }
+
+    @Test
     fun depthFirstPropertyConcat() {
         val root = "root"
         val child1 = "child1"
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 6260f99..497be6e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -124,7 +124,7 @@
 import androidx.compose.ui.node.OwnerSnapshotObserver
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.platform.MotionEventVerifierApi29.isValidMotionEvent
-import androidx.compose.ui.semantics.EmptySemanticsModifierNodeElement
+import androidx.compose.ui.semantics.EmptySemanticsElement
 import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.semantics.findClosestParentNode
 import androidx.compose.ui.text.ExperimentalTextApi
@@ -187,7 +187,7 @@
     override var density = Density(context)
         private set
 
-    private val semanticsModifier = EmptySemanticsModifierNodeElement
+    private val semanticsModifier = EmptySemanticsElement
 
     override val focusOwner: FocusOwner = FocusOwnerImpl { registerOnEndApplyChangesListener(it) }
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 46ae4e5..909c4a5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -650,11 +650,27 @@
         rowGroupings.fastForEach { row ->
             // Sort each individual row's parent nodes
             row.second.sortWith(semanticComparator(layoutIsRtl))
-            row.second.fastForEach { node ->
-                // If a parent node is a container, then add its children
-                // Otherwise, simply add the parent node
-                returnList.addAll(containerChildrenMapping[node.id] ?: mutableListOf(node))
+            returnList.addAll(row.second)
+        }
+
+        // Kotlin `sortWith` should just pull out the highest traversal indices, but keep everything
+        // else in place
+        returnList.sortWith(compareBy { it.getTraversalIndex })
+
+        var i = 0
+        // Afterwards, go in and add the containers' children.
+        while (i <= returnList.lastIndex) {
+            val currNodeId = returnList[i].id
+            // If a parent node is a container, then add its children.
+            // Add all container's children after the container itself.
+            // Because we've already recursed on the containers children, the children should
+            // also be sorted by their traversal index
+            containerChildrenMapping[currNodeId]?.let {
+                returnList.removeAt(i) // Container is removed
+                returnList.addAll(i, it) // and its children are added
             }
+            // Move pointer to end of children if they exist, otherwise, += 1
+            i += containerChildrenMapping[currNodeId]?.size ?: 1
         }
 
         return returnList
@@ -677,8 +693,12 @@
         val geometryList = mutableListOf<SemanticsNode>()
 
         fun depthFirstSearch(currNode: SemanticsNode) {
-            // Add this node to the list we will eventually sort
-            geometryList.add(currNode)
+            // We only want to add children that are either traversalGroups or are
+            // screen reader focusable.
+            if (currNode.isTraversalGroup == true ||
+                isScreenReaderFocusable(currNode)) {
+                geometryList.add(currNode)
+            }
             if (currNode.isTraversalGroup == true) {
                 // Recurse and record the container's children, sorted
                 containerMapToChildren[currNode.id] = subtreeSortedByGeometryGrouping(
@@ -710,7 +730,7 @@
         val layoutIsRtl = hostSemanticsNode.isRtl
 
         val semanticsOrderList = subtreeSortedByGeometryGrouping(
-            layoutIsRtl, hostSemanticsNode.children.toMutableList()
+            layoutIsRtl, mutableListOf(hostSemanticsNode)
         )
 
         // Iterate through our ordered list, and creating a mapping of current node to next node ID
@@ -3257,6 +3277,14 @@
 private val SemanticsNode.isRtl get() = layoutInfo.layoutDirection == LayoutDirection.Rtl
 private val SemanticsNode.isTraversalGroup get() =
     config.getOrNull(SemanticsProperties.IsTraversalGroup)
+private val SemanticsNode.getTraversalIndex: Float
+    get() {
+        if (this.config.contains(SemanticsProperties.TraversalIndex)) {
+            return config[SemanticsProperties.TraversalIndex]
+        }
+        // If the traversal index has not been set, default to zero
+        return 0f
+    }
 
 private val SemanticsNode.infoContentDescriptionOrNull get() = this.unmergedConfig.getOrNull(
     SemanticsProperties.ContentDescription)?.firstOrNull()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
index 919acfe..440914e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
@@ -20,7 +20,7 @@
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
 import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.ModifierNodeOwnerScope
+import androidx.compose.ui.node.ObserverNodeOwnerScope
 import androidx.compose.ui.node.NodeCoordinator
 import androidx.compose.ui.node.NodeKind
 import androidx.compose.ui.node.invalidateDraw
@@ -159,7 +159,7 @@
      * @see androidx.compose.ui.node.DrawModifierNode
      * @see androidx.compose.ui.node.SemanticsModifierNode
      * @see androidx.compose.ui.node.PointerInputModifierNode
-     * @see androidx.compose.ui.modifier.ModifierLocalNode
+     * @see androidx.compose.ui.modifier.ModifierLocalModifierNode
      * @see androidx.compose.ui.node.ParentDataModifierNode
      * @see androidx.compose.ui.node.LayoutAwareModifierNode
      * @see androidx.compose.ui.node.GlobalPositionAwareModifierNode
@@ -199,7 +199,7 @@
         internal var aggregateChildKindSet: Int = 0
         internal var parent: Node? = null
         internal var child: Node? = null
-        internal var ownerScope: ModifierNodeOwnerScope? = null
+        internal var ownerScope: ObserverNodeOwnerScope? = null
         internal var coordinator: NodeCoordinator? = null
             private set
         internal var insertedNodeAwaitingAttachForInvalidation = false
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt
index 30f3372..4468b73 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ZIndexModifier.kt
@@ -40,9 +40,9 @@
 @Stable
 fun Modifier.zIndex(zIndex: Float): Modifier = this then ZIndexElement(zIndex = zIndex)
 
-internal data class ZIndexElement(val zIndex: Float) : ModifierNodeElement<ZIndexModifier>() {
-    override fun create() = ZIndexModifier(zIndex)
-    override fun update(node: ZIndexModifier) {
+internal data class ZIndexElement(val zIndex: Float) : ModifierNodeElement<ZIndexNode>() {
+    override fun create() = ZIndexNode(zIndex)
+    override fun update(node: ZIndexNode) {
         node.zIndex = zIndex
     }
     override fun InspectorInfo.inspectableProperties() {
@@ -51,7 +51,7 @@
     }
 }
 
-internal class ZIndexModifier(var zIndex: Float) : LayoutModifierNode, Modifier.Node() {
+internal class ZIndexNode(var zIndex: Float) : LayoutModifierNode, Modifier.Node() {
     override fun MeasureScope.measure(
         measurable: Measurable,
         constraints: Constraints
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
index aecd204..aff7952 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.draw
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
@@ -26,7 +25,7 @@
 import androidx.compose.ui.node.DrawModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.Nodes
-import androidx.compose.ui.node.ObserverNode
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.invalidateDraw
 import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.node.requireCoordinator
@@ -94,7 +93,6 @@
     onDraw: DrawScope.() -> Unit
 ) = this then DrawBehindElement(onDraw)
 
-@OptIn(ExperimentalComposeUiApi::class)
 private data class DrawBehindElement(
     val onDraw: DrawScope.() -> Unit
 ) : ModifierNodeElement<DrawBackgroundModifier>() {
@@ -110,7 +108,6 @@
     }
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 internal class DrawBackgroundModifier(
     var onDraw: DrawScope.() -> Unit
 ) : Modifier.Node(), DrawModifierNode {
@@ -136,7 +133,6 @@
  * @sample androidx.compose.ui.samples.DrawWithCacheModifierStateParameterSample
  * @sample androidx.compose.ui.samples.DrawWithCacheContentSample
  */
-@OptIn(ExperimentalComposeUiApi::class)
 fun Modifier.drawWithCache(
     onBuildDrawCache: CacheDrawScope.() -> DrawResult
 ) = this then DrawWithCacheElement(onBuildDrawCache)
@@ -161,7 +157,7 @@
 private class CacheDrawNode(
     private val cacheDrawScope: CacheDrawScope,
     block: CacheDrawScope.() -> DrawResult
-) : Modifier.Node(), DrawModifierNode, ObserverNode, BuildDrawCacheParams {
+) : Modifier.Node(), DrawModifierNode, ObserverModifierNode, BuildDrawCacheParams {
 
     private var isCacheValid = false
     var block: CacheDrawScope.() -> DrawResult = block
@@ -275,7 +271,6 @@
     onDraw: ContentDrawScope.() -> Unit
 ): Modifier = this then DrawWithContentElement(onDraw)
 
-@OptIn(ExperimentalComposeUiApi::class)
 private data class DrawWithContentElement(
     val onDraw: ContentDrawScope.() -> Unit
 ) : ModifierNodeElement<DrawWithContentModifier>() {
@@ -291,7 +286,6 @@
     }
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 private class DrawWithContentModifier(
     var onDraw: ContentDrawScope.() -> Unit
 ) : Modifier.Node(), DrawModifierNode {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
index a62e2c3..e4eaeb0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/PainterModifier.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.draw
 
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.Modifier
@@ -58,7 +57,6 @@
  *
  * @sample androidx.compose.ui.samples.PainterModifierSample
  */
-@OptIn(ExperimentalComposeUiApi::class)
 fun Modifier.paint(
     painter: Painter,
     sizeToIntrinsics: Boolean = true,
@@ -66,7 +64,7 @@
     contentScale: ContentScale = ContentScale.Inside,
     alpha: Float = DefaultAlpha,
     colorFilter: ColorFilter? = null
-) = this then PainterModifierNodeElement(
+) = this then PainterElement(
     painter = painter,
     sizeToIntrinsics = sizeToIntrinsics,
     alignment = alignment,
@@ -87,16 +85,16 @@
  *
  * @sample androidx.compose.ui.samples.PainterModifierSample
  */
-private data class PainterModifierNodeElement(
+private data class PainterElement(
     val painter: Painter,
     val sizeToIntrinsics: Boolean,
     val alignment: Alignment,
     val contentScale: ContentScale,
     val alpha: Float,
     val colorFilter: ColorFilter?
-) : ModifierNodeElement<PainterModifierNode>() {
-    override fun create(): PainterModifierNode {
-        return PainterModifierNode(
+) : ModifierNodeElement<PainterNode>() {
+    override fun create(): PainterNode {
+        return PainterNode(
             painter = painter,
             sizeToIntrinsics = sizeToIntrinsics,
             alignment = alignment,
@@ -106,7 +104,7 @@
         )
     }
 
-    override fun update(node: PainterModifierNode) {
+    override fun update(node: PainterNode) {
         val intrinsicsChanged = node.sizeToIntrinsics != sizeToIntrinsics ||
             (sizeToIntrinsics && node.painter.intrinsicSize != painter.intrinsicSize)
 
@@ -143,11 +141,10 @@
  *
  * IMPORTANT NOTE: This class sets [androidx.compose.ui.Modifier.Node.shouldAutoInvalidate]
  * to false which means it MUST invalidate both draw and the layout. It invalidates both in the
- * [PainterModifierNodeElement.update] method through [LayoutModifierNode.invalidateLayer]
+ * [PainterElement.update] method through [LayoutModifierNode.invalidateLayer]
  * (invalidates draw) and [LayoutModifierNode.invalidateLayout] (invalidates layout).
  */
-@OptIn(ExperimentalComposeUiApi::class)
-private class PainterModifierNode(
+private class PainterNode(
     var painter: Painter,
     var sizeToIntrinsics: Boolean,
     var alignment: Alignment = Alignment.Center,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
index 7211c47..b743bd4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusOwnerImpl.kt
@@ -41,7 +41,7 @@
 import androidx.compose.ui.node.ancestors
 import androidx.compose.ui.node.dispatchForKind
 import androidx.compose.ui.node.nearestAncestor
-import androidx.compose.ui.node.visitLocalChildren
+import androidx.compose.ui.node.visitLocalDescendants
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEach
@@ -257,7 +257,7 @@
 
     private fun DelegatableNode.lastLocalKeyInputNode(): Modifier.Node? {
         var focusedKeyInputNode: Modifier.Node? = null
-        visitLocalChildren(Nodes.FocusTarget or Nodes.KeyInput) { modifierNode ->
+        visitLocalDescendants(Nodes.FocusTarget or Nodes.KeyInput) { modifierNode ->
             if (modifierNode.isKind(Nodes.FocusTarget)) return focusedKeyInputNode
 
             focusedKeyInputNode = modifierNode
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt
index 05285bd..cfa7499 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTargetNode.kt
@@ -25,10 +25,10 @@
 import androidx.compose.ui.focus.FocusStateImpl.Inactive
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.Nodes
-import androidx.compose.ui.node.ObserverNode
+import androidx.compose.ui.node.ObserverModifierNode
 import androidx.compose.ui.node.dispatchForKind
 import androidx.compose.ui.node.observeReads
 import androidx.compose.ui.node.requireOwner
@@ -40,7 +40,7 @@
  * This modifier node can be used to create a modifier that makes a component focusable.
  * Use a different instance of [FocusTargetNode] for each focusable component.
  */
-class FocusTargetNode : ObserverNode, ModifierLocalNode, Modifier.Node() {
+class FocusTargetNode : ObserverModifierNode, ModifierLocalModifierNode, Modifier.Node() {
     /**
      * The [FocusState] associated with this [FocusTargetNode].
      */
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
index 267b71f..b59ce12 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
@@ -360,7 +360,7 @@
     ambientShadowColor: Color = DefaultShadowColor,
     spotShadowColor: Color = DefaultShadowColor,
     compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
-) = this then GraphicsLayerModifierNodeElement(
+) = this then GraphicsLayerElement(
     scaleX,
     scaleY,
     alpha,
@@ -380,7 +380,7 @@
     compositingStrategy
 )
 
-private data class GraphicsLayerModifierNodeElement(
+private data class GraphicsLayerElement(
     val scaleX: Float,
     val scaleY: Float,
     val alpha: Float,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
index 2d61ab5..6c559ba 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollModifier.kt
@@ -20,7 +20,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Velocity
@@ -114,7 +114,7 @@
  */
 class NestedScrollDispatcher {
 
-    internal var modifierLocalNode: ModifierLocalNode? = null
+    internal var modifierLocalNode: ModifierLocalModifierNode? = null
 
     // lambda to calculate the most outer nested scroll scope for this dispatcher on demand
     internal var calculateNestedScrollScope: () -> CoroutineScope? = { scope }
@@ -255,6 +255,8 @@
         /**
          * Relocating when a component asks parents to scroll to bring it into view.
          */
+        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+        @get:ExperimentalComposeUiApi
         @ExperimentalComposeUiApi
         @Deprecated("Do not use. Will be removed in the future.")
         val Relocate: NestedScrollSource = NestedScrollSource(3)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
index f1282fb..94e74df 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/nestedscroll/NestedScrollNode.kt
@@ -18,8 +18,7 @@
 
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.modifier.ModifierLocalMap
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.modifierLocalMapOf
 import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.node.DelegatableNode
@@ -46,7 +45,7 @@
 internal class NestedScrollNode(
     var connection: NestedScrollConnection,
     dispatcher: NestedScrollDispatcher?
-) : ModifierLocalNode, NestedScrollConnection, DelegatableNode, Modifier.Node() {
+) : ModifierLocalModifierNode, NestedScrollConnection, DelegatableNode, Modifier.Node() {
 
     // Resolved dispatcher for re-use in case of null dispatcher is passed.
     private var resolvedDispatcher: NestedScrollDispatcher
@@ -61,8 +60,8 @@
     private val parentConnection: NestedScrollConnection?
         get() = if (isAttached) ModifierLocalNestedScroll.current else null
 
-    override val providedValues: ModifierLocalMap
-        get() = modifierLocalMapOf(ModifierLocalNestedScroll to this)
+    // Avoid get() to prevent constant allocations for static map.
+    override val providedValues = modifierLocalMapOf(entry = ModifierLocalNestedScroll to this)
 
     private val nestedCoroutineScope: CoroutineScope
         get() = parentModifierLocal?.nestedCoroutineScope
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index 3685a4c..a3d34a1 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.input.pointer
 
 import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.fastMapNotNull
 import androidx.compose.ui.geometry.Size
@@ -53,7 +52,7 @@
  * Receiver scope for awaiting pointer events in a call to
  * [PointerInputScope.awaitPointerEventScope].
  *
- * This is a restricted suspension scope. Code in this scope is always called undispatched and
+ * This is a restricted suspension scope. Code in this scope is always called un-dispatched and
  * may only suspend for calls to [awaitPointerEvent]. These functions
  * resume synchronously and the caller may mutate the result **before** the next await call to
  * affect the next stage of the input processing pipeline.
@@ -176,6 +175,7 @@
     ): R
 }
 
+@Suppress("ConstPropertyName")
 private const val PointerInputModifierNoParamError =
     "Modifier.pointerInput must provide one or more 'key' parameters that define the identity of " +
         "the modifier and determine when its previous input processing coroutine should be " +
@@ -189,9 +189,8 @@
 // This deprecated-error function shadows the varargs overload so that the varargs version
 // is not used without key parameters.
 @Suppress(
-    "DeprecatedCallableAddReplaceWith",
     "UNUSED_PARAMETER",
-    "unused",
+    "UnusedReceiverParameter",
     "ModifierFactoryUnreferencedReceiver"
 )
 @Deprecated(PointerInputModifierNoParamError, level = DeprecationLevel.ERROR)
@@ -227,7 +226,7 @@
 fun Modifier.pointerInput(
     key1: Any?,
     block: suspend PointerInputScope.() -> Unit
-): Modifier = this then SuspendPointerInputModifierNodeElement(
+): Modifier = this then SuspendPointerInputElement(
     key1 = key1,
     pointerInputHandler = block
 )
@@ -262,7 +261,7 @@
     key1: Any?,
     key2: Any?,
     block: suspend PointerInputScope.() -> Unit
-): Modifier = this then SuspendPointerInputModifierNodeElement(
+): Modifier = this then SuspendPointerInputElement(
     key1 = key1,
     key2 = key2,
     pointerInputHandler = block
@@ -296,12 +295,12 @@
 fun Modifier.pointerInput(
     vararg keys: Any?,
     block: suspend PointerInputScope.() -> Unit
-): Modifier = this then SuspendPointerInputModifierNodeElement(
+): Modifier = this then SuspendPointerInputElement(
     keys = keys,
     pointerInputHandler = block
 )
 
-internal class SuspendPointerInputModifierNodeElement(
+internal class SuspendPointerInputElement(
     val key1: Any? = null,
     val key2: Any? = null,
     val keys: Array<out Any?>? = null,
@@ -325,7 +324,7 @@
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
-        if (other !is SuspendPointerInputModifierNodeElement) return false
+        if (other !is SuspendPointerInputElement) return false
 
         if (key1 != other.key1) return false
         if (key2 != other.key2) return false
@@ -397,7 +396,6 @@
  * Note: The coroutine that executes the passed pointer event handler is launched lazily when the
  * first event is fired (making it more efficient) and is cancelled via resetPointerInputHandler().
  */
-@OptIn(ExperimentalComposeUiApi::class)
 internal class SuspendingPointerInputModifierNodeImpl(
     pointerInputHandler: suspend PointerInputScope.() -> Unit
 ) : Modifier.Node(),
@@ -625,7 +623,7 @@
             // We also create the coroutine with both a receiver and a completion continuation
             // of the handlerCoroutine itself; we don't use our currently available suspended
             // continuation as the resume point because handlerCoroutine needs to remove the
-            // ContinuationInterceptor from the supplied CoroutineContext to have undispatched
+            // ContinuationInterceptor from the supplied CoroutineContext to have un-dispatched
             // behavior in our restricted suspension scope. This is required so that we can
             // process event-awaits synchronously and affect the next stage in the pipeline
             // without running too late due to dispatch.
@@ -643,7 +641,7 @@
      *
      * [PointerEventHandlerCoroutine] implements [AwaitPointerEventScope] to provide the
      * input handler DSL, and [Continuation] so that it can wrap [completion] and remove the
-     * [ContinuationInterceptor] from the calling context and run undispatched.
+     * [ContinuationInterceptor] from the calling context and run un-dispatched.
      */
     private inner class PointerEventHandlerCoroutine<R>(
         private val completion: Continuation<R>,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutId.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutId.kt
index c73c968..9fa709a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutId.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutId.kt
@@ -30,9 +30,9 @@
  * @sample androidx.compose.ui.samples.LayoutTagChildrenUsage
  */
 @Stable
-fun Modifier.layoutId(layoutId: Any) = this then LayoutIdModifierElement(layoutId = layoutId)
+fun Modifier.layoutId(layoutId: Any) = this then LayoutIdElement(layoutId = layoutId)
 
-private data class LayoutIdModifierElement(
+private data class LayoutIdElement(
     private val layoutId: Any
 ) : ModifierNodeElement<LayoutIdModifier>() {
     override fun create() = LayoutIdModifier(layoutId)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt
index d96e68e..8c3c500 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutModifier.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.layout
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
@@ -115,9 +114,9 @@
 
 // TODO(popam): deduplicate from the copy-pasted logic of Layout.kt without making it public
 private object MeasuringIntrinsics {
-    internal fun minWidth(
+    fun minWidth(
         modifier: LayoutModifier,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         h: Int
     ): Int {
@@ -128,15 +127,15 @@
         )
         val constraints = Constraints(maxHeight = h)
         val layoutResult = with(modifier) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.width
     }
 
-    internal fun minHeight(
+    fun minHeight(
         modifier: LayoutModifier,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         w: Int
     ): Int {
@@ -147,15 +146,15 @@
         )
         val constraints = Constraints(maxWidth = w)
         val layoutResult = with(modifier) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.height
     }
 
-    internal fun maxWidth(
+    fun maxWidth(
         modifier: LayoutModifier,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         h: Int
     ): Int {
@@ -166,15 +165,15 @@
         )
         val constraints = Constraints(maxHeight = h)
         val layoutResult = with(modifier) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.width
     }
 
-    internal fun maxHeight(
+    fun maxHeight(
         modifier: LayoutModifier,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         w: Int
     ): Int {
@@ -185,7 +184,7 @@
         )
         val constraints = Constraints(maxWidth = w)
         val layoutResult = with(modifier) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.height
@@ -266,10 +265,9 @@
  */
 fun Modifier.layout(
     measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
-) = this then LayoutModifierElement(measure)
+) = this then LayoutElement(measure)
 
-@OptIn(ExperimentalComposeUiApi::class)
-private data class LayoutModifierElement(
+private data class LayoutElement(
     val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
 ) : ModifierNodeElement<LayoutModifierImpl>() {
     override fun create() = LayoutModifierImpl(measure)
@@ -284,7 +282,6 @@
     }
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 internal class LayoutModifierImpl(
     var measureBlock: MeasureScope.(Measurable, Constraints) -> MeasureResult
 ) : LayoutModifierNode, Modifier.Node() {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt
similarity index 93%
rename from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalNode.kt
rename to compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt
index 2d5b125..e91a419 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocalModifierNode.kt
@@ -25,11 +25,11 @@
 import androidx.compose.ui.node.visitAncestors
 
 /**
- * An opaque key-value holder of [ModifierLocal]s to be used with [ModifierLocalNode].
+ * An opaque key-value holder of [ModifierLocal]s to be used with [ModifierLocalModifierNode].
  *
  * @see modifierLocalMapOf
  */
-sealed class ModifierLocalMap() {
+sealed class ModifierLocalMap {
     internal abstract operator fun <T> set(key: ModifierLocal<T>, value: T)
     internal abstract operator fun <T> get(key: ModifierLocal<T>): T?
     internal abstract operator fun contains(key: ModifierLocal<*>): Boolean
@@ -113,7 +113,7 @@
  * @see ModifierLocal
  * @see androidx.compose.runtime.CompositionLocal
  */
-interface ModifierLocalNode : ModifierLocalReadScope, DelegatableNode {
+interface ModifierLocalModifierNode : ModifierLocalReadScope, DelegatableNode {
     /**
      * The map of provided ModifierLocal <-> value pairs that this node is providing. This value
      * must be overridden if you are going to provide any values. It should be overridden as a
@@ -136,10 +136,10 @@
      * any time on the UI thread, but in order to use this API, [providedValues] must be
      * implemented and [key] must be a key that was included in it.
      *
-     * By providing this new value, any [ModifierLocalNode] below it in the tree will read this
-     * [value] when reading [current], until another [ModifierLocalNode] provides a value for the
-     * same [key], however, consuming [ModifierLocalNode]s will NOT be notified that a new value
-     * was provided.
+     * By providing this new value, any [ModifierLocalModifierNode] below it in the tree will read
+     * this [value] when reading [current], until another [ModifierLocalModifierNode] provides a
+     * value for the same [key], however, consuming [ModifierLocalModifierNode]s will NOT be
+     * notified that a new value was provided.
      */
     fun <T> provide(key: ModifierLocal<T>, value: T) {
         require(providedValues !== EmptyMap) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt
index 08c38b3..774dd70 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/BackwardsCompatNode.kt
@@ -53,7 +53,7 @@
 import androidx.compose.ui.modifier.ModifierLocal
 import androidx.compose.ui.modifier.ModifierLocalConsumer
 import androidx.compose.ui.modifier.ModifierLocalMap
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.ModifierLocalProvider
 import androidx.compose.ui.modifier.ModifierLocalReadScope
 import androidx.compose.ui.modifier.modifierLocalMapOf
@@ -78,7 +78,7 @@
     DrawModifierNode,
     SemanticsModifierNode,
     PointerInputModifierNode,
-    ModifierLocalNode,
+    ModifierLocalModifierNode,
     ModifierLocalReadScope,
     ParentDataModifierNode,
     LayoutAwareModifierNode,
@@ -371,7 +371,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     override fun sharePointerInputWithSiblings(): Boolean {
         return with(element as PointerInputModifier) {
             pointerInputFilter.shareWithSiblings
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
index e326bec..d8b0359 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatableNode.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
@@ -44,33 +43,10 @@
 
 // TREE TRAVERSAL APIS
 // For now, traversing the node tree and layout node tree will be kept out of public API.
+// However, when we add APIs here, we should add corresponding tests.
 // Some internal modifiers, such as Focus, PointerInput, etc. will all need to utilize this
 // a bit, but I think we want to avoid giving this power to public API just yet. We can
 // introduce this as valid cases arise
-internal fun DelegatableNode.localChild(mask: Int): Modifier.Node? {
-    val child = node.child ?: return null
-    if (child.aggregateChildKindSet and mask == 0) return null
-    var next: Modifier.Node? = child
-    while (next != null) {
-        if (next.kindSet and mask != 0) {
-            return next
-        }
-        next = next.child
-    }
-    return null
-}
-
-internal fun DelegatableNode.localParent(mask: Int): Modifier.Node? {
-    var next = node.parent
-    while (next != null) {
-        if (next.kindSet and mask != 0) {
-            return next
-        }
-        next = next.parent
-    }
-    return null
-}
-
 internal inline fun DelegatableNode.visitAncestors(
     mask: Int,
     includeSelf: Boolean = false,
@@ -97,6 +73,7 @@
     }
 }
 
+@Suppress("unused")
 internal fun DelegatableNode.nearestAncestor(mask: Int): Modifier.Node? {
     check(node.isAttached)
     var node: Modifier.Node? = node.parent
@@ -117,32 +94,6 @@
     return null
 }
 
-internal fun DelegatableNode.firstChild(mask: Int): Modifier.Node? {
-    check(node.isAttached)
-    val branches = mutableVectorOf<Modifier.Node>()
-    val child = node.child
-    if (child == null)
-        branches.addLayoutNodeChildren(node)
-    else
-        branches.add(child)
-    while (branches.isNotEmpty()) {
-        val branch = branches.removeAt(branches.lastIndex)
-        if (branch.aggregateChildKindSet and mask == 0) {
-            branches.addLayoutNodeChildren(branch)
-            // none of these nodes match the mask, so don't bother traversing them
-            continue
-        }
-        var node: Modifier.Node? = branch
-        while (node != null) {
-            if (node.kindSet and mask != 0) {
-                return node
-            }
-            node = node.child
-        }
-    }
-    return null
-}
-
 internal inline fun DelegatableNode.visitSubtree(mask: Int, block: (Modifier.Node) -> Unit) {
     // TODO(lmr): we might want to add some safety wheels to prevent this from being called
     //  while one of the chains is being diffed / updated.
@@ -165,14 +116,13 @@
                 }
                 node = node.child
             }
-            node = null
         }
+        node = null
         nodes.push(layout._children)
         layout = if (nodes.isNotEmpty()) nodes.pop() else null
     }
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 private fun MutableVector<Modifier.Node>.addLayoutNodeChildren(node: Modifier.Node) {
     node.requireLayoutNode()._children.forEachReversed {
         add(it.nodes.head)
@@ -233,7 +183,10 @@
     }
 }
 
-internal inline fun DelegatableNode.visitLocalChildren(mask: Int, block: (Modifier.Node) -> Unit) {
+internal inline fun DelegatableNode.visitLocalDescendants(
+    mask: Int,
+    block: (Modifier.Node) -> Unit
+) {
     check(node.isAttached)
     val self = node
     if (self.aggregateChildKindSet and mask == 0) return
@@ -246,7 +199,10 @@
     }
 }
 
-internal inline fun DelegatableNode.visitLocalParents(mask: Int, block: (Modifier.Node) -> Unit) {
+internal inline fun DelegatableNode.visitLocalAncestors(
+    mask: Int,
+    block: (Modifier.Node) -> Unit
+) {
     check(node.isAttached)
     var next = node.parent
     while (next != null) {
@@ -257,17 +213,17 @@
     }
 }
 
-internal inline fun <reified T> DelegatableNode.visitLocalChildren(
+internal inline fun <reified T> DelegatableNode.visitLocalDescendants(
     type: NodeKind<T>,
     block: (T) -> Unit
-) = visitLocalChildren(type.mask) {
+) = visitLocalDescendants(type.mask) {
     it.dispatchForKind(type, block)
 }
 
-internal inline fun <reified T> DelegatableNode.visitLocalParents(
+internal inline fun <reified T> DelegatableNode.visitLocalAncestors(
     type: NodeKind<T>,
     block: (T) -> Unit
-) = visitLocalParents(type.mask) {
+) = visitLocalAncestors(type.mask) {
     it.dispatchForKind(type, block)
 }
 
@@ -296,10 +252,8 @@
 ): List<T>? {
     var result: MutableList<T>? = null
     visitAncestors(type) {
-        val list = if (result == null) {
-            mutableListOf<T>().also { result = it }
-        } else result!!
-        list += it
+        if (result == null) result = mutableListOf()
+        result?.add(it)
     }
     return result
 }
@@ -332,8 +286,8 @@
 internal inline fun <reified T> DelegatableNode.visitSubtreeIf(
     type: NodeKind<T>,
     block: (T) -> Boolean
-) = visitSubtreeIf(type.mask) foo@{
-    it.dispatchForKind(type) {
+) = visitSubtreeIf(type.mask) foo@{ node ->
+    node.dispatchForKind(type) {
         if (!block(it)) return@foo false
     }
     true
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
index 565c6f6..f7379ba 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet.kt
@@ -26,7 +26,7 @@
  * to not find the item in the tree set, which we previously added.
  */
 internal class DepthSortedSet(
-    private val extraAssertions: Boolean = true
+    private val extraAssertions: Boolean
 ) {
     // stores the depth used when the node was added into the set so we can assert it wasn't
     // changed since then. we need to enforce this as changing the depth can break the contract
@@ -103,3 +103,83 @@
         return set.toString()
     }
 }
+
+internal class DepthSortedSetsForDifferentPasses(extraAssertions: Boolean) {
+    private val lookaheadSet = DepthSortedSet(extraAssertions)
+    private val set = DepthSortedSet(extraAssertions)
+
+    /**
+     * Checks if the given node exists in the corresponding set based on the provided
+     * [affectsLookahead].
+     */
+    fun contains(node: LayoutNode, affectsLookahead: Boolean): Boolean {
+        val constainsInLookahead = lookaheadSet.contains(node)
+        return if (affectsLookahead) {
+            constainsInLookahead
+        } else {
+            constainsInLookahead || set.contains(node)
+        }
+    }
+
+    /**
+     * Checks if the node exists in either set.
+     */
+    fun contains(node: LayoutNode): Boolean = lookaheadSet.contains(node) || set.contains(node)
+
+    /**
+     * Adds the given node to the corresponding set based on whether its lookahead
+     * measurement/placement should be invalidated.
+     *
+     * Note: When [affectsLookahead] is true, both lookahead and main measure/layout will be
+     * triggered as needed (i.e. if the FooPending flag is dirty). Otherwise, lookahead
+     * remeasurement/relayout will be skipped.
+     */
+    fun add(node: LayoutNode, affectsLookahead: Boolean) {
+        if (affectsLookahead) {
+            lookaheadSet.add(node)
+        } else {
+            if (!lookaheadSet.contains(node)) {
+                // Only add the node to set if it's not already in the lookahead set. Nodes in
+                // lookaheadSet will get a remeasure/relayout call after lookahead.
+                set.add(node)
+            }
+        }
+    }
+
+    fun remove(node: LayoutNode, affectsLookahead: Boolean): Boolean {
+        val contains = if (affectsLookahead) {
+            lookaheadSet.remove(node)
+        } else {
+            set.remove(node)
+        }
+        return contains
+    }
+
+    fun remove(node: LayoutNode): Boolean {
+        val containsInLookahead = lookaheadSet.remove(node)
+        return set.remove(node) || containsInLookahead
+    }
+
+    fun pop(): LayoutNode {
+        if (lookaheadSet.isNotEmpty()) {
+            return lookaheadSet.pop()
+        }
+        return set.pop()
+    }
+
+    /**
+     * Pops nodes that require lookahead remeasurement/replacement first until the lookaheadSet
+     * is empty, before handling nodes that only require invalidation for the main pass.
+     */
+    inline fun popEach(crossinline block: (node: LayoutNode, affectsLookahead: Boolean) -> Unit) {
+        while (isNotEmpty()) {
+            val affectsLookahead = lookaheadSet.isNotEmpty()
+            val node = if (affectsLookahead) lookaheadSet.pop() else set.pop()
+            block(node, affectsLookahead)
+        }
+    }
+
+    fun isEmpty(): Boolean = set.isEmpty() && lookaheadSet.isEmpty()
+
+    fun isNotEmpty(): Boolean = !isEmpty()
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt
index 1364122..9bac97d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNode.kt
@@ -148,7 +148,7 @@
 fun LayoutModifierNode.invalidatePlacement() = requireLayoutNode().requestRelayout()
 
 /**
- * This invalidates the current node's measure result, and ensures that a remeasurement
+ * This invalidates the current node's measure result, and ensures that a re-measurement
  * (the measurement block rerun) of this node will happen for the next frame.
  */
 fun LayoutModifierNode.invalidateMeasurement() = requireLayoutNode().invalidateMeasurements()
@@ -163,7 +163,7 @@
 
     internal fun minWidth(
         measureBlock: MeasureBlock,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         h: Int
     ): Int {
@@ -176,8 +176,8 @@
         val layoutResult =
             with(measureBlock) {
                 IntrinsicsMeasureScope(
-                    instrinsicMeasureScope,
-                    instrinsicMeasureScope.layoutDirection
+                    intrinsicMeasureScope,
+                    intrinsicMeasureScope.layoutDirection
                 ).measure(measurable, constraints)
             }
 
@@ -186,7 +186,7 @@
 
     internal fun minHeight(
         measureBlock: MeasureBlock,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         w: Int
     ): Int {
@@ -197,7 +197,7 @@
         )
         val constraints = Constraints(maxWidth = w)
         val layoutResult = with(measureBlock) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.height
@@ -205,7 +205,7 @@
 
     internal fun maxWidth(
         measureBlock: MeasureBlock,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         h: Int
     ): Int {
@@ -216,7 +216,7 @@
         )
         val constraints = Constraints(maxHeight = h)
         val layoutResult = with(measureBlock) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.width
@@ -224,7 +224,7 @@
 
     internal fun maxHeight(
         measureBlock: MeasureBlock,
-        instrinsicMeasureScope: IntrinsicMeasureScope,
+        intrinsicMeasureScope: IntrinsicMeasureScope,
         intrinsicMeasurable: IntrinsicMeasurable,
         w: Int
     ): Int {
@@ -235,7 +235,7 @@
         )
         val constraints = Constraints(maxWidth = w)
         val layoutResult = with(measureBlock) {
-            IntrinsicsMeasureScope(instrinsicMeasureScope, instrinsicMeasureScope.layoutDirection)
+            IntrinsicsMeasureScope(intrinsicMeasureScope, intrinsicMeasureScope.layoutDirection)
                 .measure(measurable, constraints)
         }
         return layoutResult.height
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
index efdec00..4ebfe68 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutTreeConsistencyChecker.kt
@@ -26,7 +26,7 @@
  */
 internal class LayoutTreeConsistencyChecker(
     private val root: LayoutNode,
-    private val relayoutNodes: DepthSortedSet,
+    private val relayoutNodes: DepthSortedSetsForDifferentPasses,
     private val postponedMeasureRequests: List<MeasureAndLayoutDelegate.PostponedRequest>
 ) {
     fun assertConsistent() {
@@ -88,13 +88,13 @@
                 return true
             }
             if (lookaheadMeasurePending) {
-                return relayoutNodes.contains(this) ||
+                return relayoutNodes.contains(this, true) ||
                     parent?.lookaheadMeasurePending == true ||
                     parentLayoutState == LayoutNode.LayoutState.LookaheadMeasuring ||
                     (parent?.measurePending == true && lookaheadRoot == this)
             }
             if (lookaheadLayoutPending) {
-                return relayoutNodes.contains(this) ||
+                return relayoutNodes.contains(this, true) ||
                     parent == null ||
                     parent.lookaheadMeasurePending ||
                     parent.lookaheadLayoutPending ||
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index 311f437..d297aef 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -40,7 +40,7 @@
     /**
      * LayoutNodes that need measure or layout.
      */
-    private val relayoutNodes = DepthSortedSet(Owner.enableExtraAssertions)
+    private val relayoutNodes = DepthSortedSetsForDifferentPasses(Owner.enableExtraAssertions)
 
     /**
      * Whether any LayoutNode needs measure or layout.
@@ -92,8 +92,11 @@
         if (rootConstraints != constraints) {
             require(!duringMeasureLayout)
             rootConstraints = constraints
+            if (root.lookaheadRoot != null) {
+                root.markLookaheadMeasurePending()
+            }
             root.markMeasurePending()
-            relayoutNodes.add(root)
+            relayoutNodes.add(root, root.lookaheadRoot != null)
         }
     }
 
@@ -150,7 +153,7 @@
                         layoutNode.canAffectParentInLookahead
                     ) {
                         if (layoutNode.parent?.lookaheadMeasurePending != true) {
-                            relayoutNodes.add(layoutNode)
+                            relayoutNodes.add(layoutNode, true)
                         }
                     }
                     !duringMeasureLayout
@@ -192,7 +195,7 @@
                     layoutNode.markMeasurePending()
                     if (layoutNode.isPlaced || layoutNode.canAffectParent) {
                         if (layoutNode.parent?.measurePending != true) {
-                            relayoutNodes.add(layoutNode)
+                            relayoutNodes.add(layoutNode, false)
                         }
                     }
                     !duringMeasureLayout
@@ -235,7 +238,7 @@
                         if (parent?.lookaheadMeasurePending != true &&
                             parent?.lookaheadLayoutPending != true
                         ) {
-                            relayoutNodes.add(layoutNode)
+                            relayoutNodes.add(layoutNode, true)
                         }
                     }
                     !duringMeasureLayout
@@ -269,7 +272,7 @@
                     if (layoutNode.isPlaced) {
                         val parent = layoutNode.parent
                         if (parent?.layoutPending != true && parent?.measurePending != true) {
-                            relayoutNodes.add(layoutNode)
+                            relayoutNodes.add(layoutNode, false)
                         }
                     }
                     !duringMeasureLayout
@@ -332,8 +335,8 @@
         var rootNodeResized = false
         performMeasureAndLayout {
             if (relayoutNodes.isNotEmpty()) {
-                relayoutNodes.popEach { layoutNode ->
-                    val sizeChanged = remeasureAndRelayoutIfNeeded(layoutNode)
+                relayoutNodes.popEach { layoutNode, affectsLookahead ->
+                    val sizeChanged = remeasureAndRelayoutIfNeeded(layoutNode, affectsLookahead)
                     if (layoutNode === root && sizeChanged) {
                         rootNodeResized = true
                     }
@@ -521,13 +524,10 @@
         require(!pending(layoutNode))
 
         layoutNode.forEachChild { child ->
-            if (pending(child) && relayoutNodes.contains(child)) {
+            if (pending(child) && relayoutNodes.remove(child, affectsLookahead)) {
                 // If lookaheadMeasurePending && this forceMeasureSubtree call doesn't affect
                 // lookahead, we'll leave the node in the [relayoutNodes] for further lookahead
                 // remeasurement.
-                if (!(child.lookaheadMeasurePending && !affectsLookahead)) {
-                    relayoutNodes.remove(child)
-                }
                 remeasureAndRelayoutIfNeeded(child, affectsLookahead)
             }
 
@@ -543,7 +543,7 @@
         // if the child was resized during the remeasurement it could request a remeasure on
         // the parent. we need to remeasure now as this function assumes the whole subtree is
         // fully measured as a result of the invocation.
-        if (pending(layoutNode) && relayoutNodes.remove(layoutNode)) {
+        if (pending(layoutNode) && relayoutNodes.remove(layoutNode, affectsLookahead)) {
             remeasureAndRelayoutIfNeeded(layoutNode)
         }
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt
index 0bbb386..7cf6f2d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifierNodeElement.kt
@@ -89,12 +89,16 @@
         tryPopulateReflectively(this@ModifierNodeElement)
     }
 
-    // Require hashCode() to be implemented. Using a data class is sufficient. Singletons and
-    // modifiers with no parameters may implement this function by returning an arbitrary constant.
+    /**
+     * Require hashCode() to be implemented. Using a data class is sufficient. Singletons and
+     * modifiers with no parameters may implement this function by returning an arbitrary constant.
+     */
     abstract override fun hashCode(): Int
 
-    // Require equals() to be implemented. Using a data class is sufficient. Singletons may
-    // implement this function with referential equality (`this === other`). Modifiers with no
-    // inputs may implement this function by checking the type of the other object.
+    /**
+     * Require equals() to be implemented. Using a data class is sufficient. Singletons may
+     * implement this function with referential equality (`this === other`). Modifiers with no
+     * inputs may implement this function by checking the type of the other object.
+     */
     abstract override fun equals(other: Any?): Boolean
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index ab86a7a..b0abc9f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:OptIn(ExperimentalComposeUiApi::class)
 
 package androidx.compose.ui.node
 
 import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.MutableRect
 import androidx.compose.ui.geometry.Offset
@@ -112,7 +110,7 @@
         }
     }
 
-    fun hasNode(type: NodeKind<*>): Boolean {
+    private fun hasNode(type: NodeKind<*>): Boolean {
         return headNode(type.includeSelfInTraversal)?.has(type) == true
     }
 
@@ -233,13 +231,13 @@
             val thisNode = tail
             if (layoutNode.nodes.has(Nodes.ParentData)) {
                 with(layoutNode.density) {
-                    layoutNode.nodes.tailToHead {
-                        if (it.isKind(Nodes.ParentData)) {
-                            it.dispatchForKind(Nodes.ParentData) {
+                    layoutNode.nodes.tailToHead { node ->
+                        if (node.isKind(Nodes.ParentData)) {
+                            node.dispatchForKind(Nodes.ParentData) {
                                 data = with(it) { modifyParentData(data) }
                             }
                         }
-                        if (it === thisNode) return@tailToHead
+                        if (node === thisNode) return@tailToHead
                     }
                 }
             }
@@ -347,7 +345,6 @@
         wrapped?.draw(canvas)
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
     fun onPlaced() {
         visitNodes(Nodes.LayoutAware) {
             it.onPlaced(this)
@@ -1038,7 +1035,7 @@
         if (ancestor1 === ancestor2) {
             val otherNode = other.tail
             // They are on the same node, but we don't know which is the deeper of the two
-            tail.visitLocalParents(Nodes.Layout.mask) {
+            tail.visitLocalAncestors(Nodes.Layout.mask) {
                 if (it === otherNode) return other
             }
             return this
@@ -1071,7 +1068,7 @@
 
     fun shouldSharePointerInputWithSiblings(): Boolean {
         val start = headNode(Nodes.PointerInput.includeSelfInTraversal) ?: return false
-        start.visitLocalChildren(Nodes.PointerInput) {
+        start.visitLocalDescendants(Nodes.PointerInput) {
             if (it.sharePointerInputWithSiblings()) return true
         }
         return false
@@ -1202,7 +1199,6 @@
         /**
          * Hit testing specifics for pointer input.
          */
-        @OptIn(ExperimentalComposeUiApi::class)
         val PointerInputSource =
             object : HitTestSource {
                 override fun entityType() = Nodes.PointerInput
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
index 1e63241..813f367 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
@@ -14,16 +14,12 @@
  * limitations under the License.
  */
 
-@file:Suppress("DEPRECATION", "NOTHING_TO_INLINE")
-
 package androidx.compose.ui.node
 
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.DrawModifier
-import androidx.compose.ui.focus.FocusEventModifier
 import androidx.compose.ui.focus.FocusEventModifierNode
-import androidx.compose.ui.focus.FocusOrderModifier
 import androidx.compose.ui.focus.FocusProperties
 import androidx.compose.ui.focus.FocusPropertiesModifierNode
 import androidx.compose.ui.focus.FocusTargetNode
@@ -41,18 +37,21 @@
 import androidx.compose.ui.layout.OnRemeasuredModifier
 import androidx.compose.ui.layout.ParentDataModifier
 import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalNode
+import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.ModifierLocalProvider
 import androidx.compose.ui.semantics.SemanticsModifier
 
+@Suppress("NOTHING_TO_INLINE")
 @JvmInline
 internal value class NodeKind<T>(val mask: Int) {
     inline infix fun or(other: NodeKind<*>): Int = mask or other.mask
     inline infix fun or(other: Int): Int = mask or other
 }
 
+@Suppress("NOTHING_TO_INLINE")
 internal inline infix fun Int.or(other: NodeKind<*>): Int = this or other.mask
 
+@Suppress("NOTHING_TO_INLINE")
 internal inline operator fun Int.contains(value: NodeKind<*>): Boolean = this and value.mask != 0
 
 // For a given NodeCoordinator, the "LayoutAware" nodes that it is concerned with should include
@@ -78,7 +77,7 @@
     @JvmStatic
     inline val PointerInput get() = NodeKind<PointerInputModifierNode>(0b1 shl 4)
     @JvmStatic
-    inline val Locals get() = NodeKind<ModifierLocalNode>(0b1 shl 5)
+    inline val Locals get() = NodeKind<ModifierLocalModifierNode>(0b1 shl 5)
     @JvmStatic
     inline val ParentData get() = NodeKind<ParentDataModifierNode>(0b1 shl 6)
     @JvmStatic
@@ -126,10 +125,12 @@
     ) {
         mask = mask or Nodes.Locals
     }
-    if (element is FocusEventModifier) {
+    @Suppress("DEPRECATION")
+    if (element is androidx.compose.ui.focus.FocusEventModifier) {
         mask = mask or Nodes.FocusEvent
     }
-    if (element is FocusOrderModifier) {
+    @Suppress("DEPRECATION")
+    if (element is androidx.compose.ui.focus.FocusOrderModifier) {
         mask = mask or Nodes.FocusProperties
     }
     if (element is OnGloballyPositionedModifier) {
@@ -166,7 +167,7 @@
     if (node is PointerInputModifierNode) {
         mask = mask or Nodes.PointerInput
     }
-    if (node is ModifierLocalNode) {
+    if (node is ModifierLocalModifierNode) {
         mask = mask or Nodes.Locals
     }
     if (node is ParentDataModifierNode) {
@@ -205,8 +206,11 @@
     return mask
 }
 
+@Suppress("ConstPropertyName")
 private const val Updated = 0
+@Suppress("ConstPropertyName")
 private const val Inserted = 1
+@Suppress("ConstPropertyName")
 private const val Removed = 2
 
 internal fun autoInvalidateRemovedNode(node: Modifier.Node) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt
similarity index 80%
rename from compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverNode.kt
rename to compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt
index 207df54..c3f37a2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ObserverModifierNode.kt
@@ -23,7 +23,7 @@
  * [onObservedReadsChanged] that will be called whenever the value of read object has changed.
  * To trigger [onObservedReadsChanged], read values within an [observeReads] block.
  */
-interface ObserverNode : DelegatableNode {
+interface ObserverModifierNode : DelegatableNode {
 
     /**
      * This callback is called when any values that are read within the [observeReads] block
@@ -32,12 +32,14 @@
     fun onObservedReadsChanged()
 }
 
-internal class ModifierNodeOwnerScope(internal val observerNode: ObserverNode) : OwnerScope {
+internal class ObserverNodeOwnerScope(
+    internal val observerNode: ObserverModifierNode
+) : OwnerScope {
     override val isValidOwnerScope: Boolean
         get() = observerNode.node.isAttached
 
     companion object {
-        internal val OnObserveReadsChanged: (ModifierNodeOwnerScope) -> Unit = {
+        internal val OnObserveReadsChanged: (ObserverNodeOwnerScope) -> Unit = {
             if (it.isValidOwnerScope) it.observerNode.onObservedReadsChanged()
         }
     }
@@ -46,11 +48,11 @@
 /**
  * Use this function to observe reads within the specified [block].
  */
-fun <T> T.observeReads(block: () -> Unit) where T : Modifier.Node, T : ObserverNode {
-    val target = ownerScope ?: ModifierNodeOwnerScope(this).also { ownerScope = it }
+fun <T> T.observeReads(block: () -> Unit) where T : Modifier.Node, T : ObserverModifierNode {
+    val target = ownerScope ?: ObserverNodeOwnerScope(this).also { ownerScope = it }
     requireOwner().snapshotObserver.observeReads(
         target = target,
-        onChanged = ModifierNodeOwnerScope.OnObserveReadsChanged,
+        onChanged = ObserverNodeOwnerScope.OnObserveReadsChanged,
         block = block
     )
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
index 1477974..723d099 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsModifier.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.semantics
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
 import androidx.compose.ui.node.ModifierNodeElement
@@ -47,7 +46,7 @@
     val semanticsConfiguration: SemanticsConfiguration
 }
 
-internal object EmptySemanticsModifierNodeElement :
+internal object EmptySemanticsElement :
     ModifierNodeElement<CoreSemanticsModifierNode>() {
 
     private val semanticsConfiguration = SemanticsConfiguration().apply {
@@ -67,7 +66,6 @@
     override fun equals(other: Any?) = (other === this)
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 internal class CoreSemanticsModifierNode(
     override var semanticsConfiguration: SemanticsConfiguration
 ) : Modifier.Node(), SemanticsModifierNode
@@ -102,17 +100,16 @@
  * @param properties properties to add to the semantics. [SemanticsPropertyReceiver] will be
  * provided in the scope to allow access for common properties and its values.
  */
-@OptIn(ExperimentalComposeUiApi::class)
 fun Modifier.semantics(
     mergeDescendants: Boolean = false,
     properties: (SemanticsPropertyReceiver.() -> Unit)
-): Modifier = this then AppendedSemanticsModifierNodeElement(
+): Modifier = this then AppendedSemanticsElement(
     mergeDescendants = mergeDescendants,
     properties = properties
 )
 
 // Implement SemanticsModifier to allow tooling to inspect the semantics configuration
-internal data class AppendedSemanticsModifierNodeElement(
+internal data class AppendedSemanticsElement(
     override val semanticsConfiguration: SemanticsConfiguration
 ) : ModifierNodeElement<CoreSemanticsModifierNode>(), SemanticsModifier {
 
@@ -156,13 +153,12 @@
  * @param properties properties to add to the semantics. [SemanticsPropertyReceiver] will be
  * provided in the scope to allow access for common properties and its values.
  */
-@OptIn(ExperimentalComposeUiApi::class)
 fun Modifier.clearAndSetSemantics(
     properties: (SemanticsPropertyReceiver.() -> Unit)
-): Modifier = this then ClearAndSetSemanticsModifierNodeElement(properties)
+): Modifier = this then ClearAndSetSemanticsElement(properties)
 
 // Implement SemanticsModifier to allow tooling to inspect the semantics configuration
-internal data class ClearAndSetSemanticsModifierNodeElement(
+internal data class ClearAndSetSemanticsElement(
     override val semanticsConfiguration: SemanticsConfiguration
 ) : ModifierNodeElement<CoreSemanticsModifierNode>(), SemanticsModifier {
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index c1b5d1c..bf84d72 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -120,6 +120,17 @@
     )
 
     /**
+     * @see SemanticsPropertyReceiver.traversalIndex
+     */
+    val TraversalIndex = SemanticsPropertyKey<Float>(
+        name = "TraversalIndex",
+        mergePolicy = { parentValue, _ ->
+            // Never merge traversal indices
+            parentValue
+        }
+    )
+
+    /**
      * @see SemanticsPropertyReceiver.horizontalScrollAxisRange
      */
     val HorizontalScrollAxisRange =
@@ -821,6 +832,24 @@
 }
 
 /**
+ * A value to manually control screenreader traversal order.
+ *
+ * This API can be used to customize TalkBack traversal order. When the `traversalIndex` property is
+ * set on a traversalGroup or on a screenreader-focusable node, then the sorting algorithm will
+ * prioritize nodes with smaller `traversalIndex`s earlier. The default traversalIndex value is
+ * zero, and traversalIndices are compared at a peer level.
+ *
+ * For example,` traversalIndex = -1f` can be used to force a top bar to be ordered earlier, and
+ * `traversalIndex = 1f` to make a bottom bar ordered last, in the edge cases where this does not
+ * happen by default.  As another example, if you need to reorder two Buttons within a Row, then
+ * you can set `isTraversalGroup = true` on the Row, and set `traversalIndex` on one of the Buttons.
+ *
+ * Note that if `traversalIndex` seems to have no effect, be sure to set `isTraversalGroup = true`
+ * as well.
+ */
+var SemanticsPropertyReceiver.traversalIndex by SemanticsProperties.TraversalIndex
+
+/**
  * The horizontal scroll state of this node if this node is scrollable.
  */
 var SemanticsPropertyReceiver.horizontalScrollAxisRange
diff --git a/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.kt b/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.kt
index 3df5d53..18da868 100644
--- a/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.kt
+++ b/compose/ui/ui/src/jvmMain/kotlin/androidx/compose/ui/Actual.kt
@@ -26,7 +26,6 @@
 
 // TODO: For non-JVM platforms, you can revive the kotlin-reflect implementation from
 //  https://android-review.googlesource.com/c/platform/frameworks/support/+/2441379
-@OptIn(ExperimentalComposeUiApi::class)
 internal actual fun InspectorInfo.tryPopulateReflectively(
     element: ModifierNodeElement<*>
 ) {
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
index 66310c1..9aab9d9 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaBasedOwner.skiko.kt
@@ -69,7 +69,7 @@
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.OwnerSnapshotObserver
 import androidx.compose.ui.node.RootForTest
-import androidx.compose.ui.semantics.EmptySemanticsModifierNodeElement
+import androidx.compose.ui.semantics.EmptySemanticsElement
 import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.input.PlatformTextInputPluginRegistry
@@ -123,7 +123,7 @@
 
     override val sharedDrawScope = LayoutNodeDrawScope()
 
-    private val semanticsModifier = EmptySemanticsModifierNodeElement
+    private val semanticsModifier = EmptySemanticsElement
 
     override val focusOwner: FocusOwner = FocusOwnerImpl {
         registerOnEndApplyChangesListener(it)
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index eab9342..586d600 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -91,7 +91,6 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@OptIn(ExperimentalComposeUiApi::class)
 class LayoutNodeTest {
     // Ensure that attach and detach work properly
     @Test
@@ -1384,22 +1383,22 @@
     @Test
     fun hitTestSemantics_pointerInMinimumTouchTarget_closestHit() {
         val semanticsConfiguration = SemanticsConfiguration()
-        val semanticsModifier1 = object : SemanticsModifierNode, Modifier.Node() {
+        val semanticsNode1 = object : SemanticsModifierNode, Modifier.Node() {
             override val semanticsConfiguration: SemanticsConfiguration = semanticsConfiguration
         }
-        val semanticsModifier2 = object : SemanticsModifierNode, Modifier.Node() {
+        val semanticsNode2 = object : SemanticsModifierNode, Modifier.Node() {
             override val semanticsConfiguration: SemanticsConfiguration = semanticsConfiguration
         }
-        data class TestSemanticsModifierElement(
+        data class TestSemanticsElement(
             private val node: Modifier.Node
         ) : ModifierNodeElement<Modifier.Node>() {
             override fun create() = node
             override fun update(node: Modifier.Node) {}
         }
-        val semanticsModifierElement1 = TestSemanticsModifierElement(semanticsModifier1)
-        val semanticsModifierElement2 = TestSemanticsModifierElement(semanticsModifier2)
-        val layoutNode1 = LayoutNode(0, 0, 5, 5, semanticsModifierElement1, DpSize(48.dp, 48.dp))
-        val layoutNode2 = LayoutNode(6, 6, 11, 11, semanticsModifierElement2, DpSize(48.dp, 48.dp))
+        val semanticsElement1 = TestSemanticsElement(semanticsNode1)
+        val semanticsElement2 = TestSemanticsElement(semanticsNode2)
+        val layoutNode1 = LayoutNode(0, 0, 5, 5, semanticsElement1, DpSize(48.dp, 48.dp))
+        val layoutNode2 = LayoutNode(6, 6, 11, 11, semanticsElement2, DpSize(48.dp, 48.dp))
         val outerNode = LayoutNode(0, 0, 11, 11).apply { attach(MockOwner()) }
         outerNode.add(layoutNode1)
         outerNode.add(layoutNode2)
@@ -1411,42 +1410,42 @@
         outerNode.hitTestSemantics(Offset(5.1f, 5.5f), hit1, true)
 
         assertThat(hit1).hasSize(1)
-        assertThat(hit1[0]).isEqualTo(semanticsModifier1)
+        assertThat(hit1[0]).isEqualTo(semanticsNode1)
 
         // Hit closer to layoutNode2
         val hit2 = HitTestResult()
         outerNode.hitTestSemantics(Offset(5.9f, 5.5f), hit2, true)
 
         assertThat(hit2).hasSize(1)
-        assertThat(hit2[0]).isEqualTo(semanticsModifier2)
+        assertThat(hit2[0]).isEqualTo(semanticsNode2)
 
         // Hit closer to layoutNode1
         val hit3 = HitTestResult()
         outerNode.hitTestSemantics(Offset(5.5f, 5.1f), hit3, true)
 
         assertThat(hit3).hasSize(1)
-        assertThat(hit3[0]).isEqualTo(semanticsModifier1)
+        assertThat(hit3[0]).isEqualTo(semanticsNode1)
 
         // Hit closer to layoutNode2
         val hit4 = HitTestResult()
         outerNode.hitTestSemantics(Offset(5.5f, 5.9f), hit4, true)
 
         assertThat(hit4).hasSize(1)
-        assertThat(hit4[0]).isEqualTo(semanticsModifier2)
+        assertThat(hit4[0]).isEqualTo(semanticsNode2)
 
         // Hit inside layoutNode1
         val hit5 = HitTestResult()
         outerNode.hitTestSemantics(Offset(4.9f, 4.9f), hit5, true)
 
         assertThat(hit5).hasSize(1)
-        assertThat(hit5[0]).isEqualTo(semanticsModifier1)
+        assertThat(hit5[0]).isEqualTo(semanticsNode1)
 
         // Hit inside layoutNode2
         val hit6 = HitTestResult()
         outerNode.hitTestSemantics(Offset(6.1f, 6.1f), hit6, true)
 
         assertThat(hit6).hasSize(1)
-        assertThat(hit6[0]).isEqualTo(semanticsModifier2)
+        assertThat(hit6[0]).isEqualTo(semanticsNode2)
     }
 
     @Test
@@ -2496,9 +2495,8 @@
     }
 }
 
-@OptIn(InternalCoreApi::class)
 internal class MockOwner(
-    val position: IntOffset = IntOffset.Zero,
+    private val position: IntOffset = IntOffset.Zero,
     override val root: LayoutNode = LayoutNode(),
     override val coroutineContext: CoroutineContext =
         Executors.newFixedThreadPool(3).asCoroutineDispatcher()
@@ -2553,6 +2551,7 @@
         get() = TODO("Not yet implemented")
     override val layoutDirection: LayoutDirection
         get() = LayoutDirection.Ltr
+    @InternalCoreApi
     override var showLayoutBounds: Boolean = false
     override val snapshotObserver = OwnerSnapshotObserver { it.invoke() }
     override val modifierLocalManager: ModifierLocalManager = ModifierLocalManager(this)
@@ -2721,7 +2720,6 @@
     override val sharedDrawScope = LayoutNodeDrawScope()
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 private fun LayoutNode.hitTest(
     pointerPosition: Offset,
     hitPointerInputFilters: MutableList<Modifier.Node>,
@@ -2783,7 +2781,6 @@
 // This returns the corresponding modifier that produced the PointerInputNode. This is only
 // possible for PointerInputNodes that are BackwardsCompatNodes and once we refactor the
 // pointerInput modifier to use Modifier.Nodes directly, the tests that use this should be rewritten
-@OptIn(ExperimentalComposeUiApi::class)
 fun PointerInputModifierNode.toFilter(): PointerInputFilter {
     val node = this as? BackwardsCompatNode
         ?: error("Incorrectly assumed PointerInputNode was a BackwardsCompatNode")
@@ -2792,7 +2789,6 @@
     return modifier.pointerInputFilter
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 fun List<Modifier.Node>.toFilters(): List<PointerInputFilter> = map {
     (it as PointerInputModifierNode).toFilter()
 }
@@ -2800,7 +2796,6 @@
 // This returns the corresponding modifier that produced the Node. This is only possible for
 // Nodes that are BackwardsCompatNodes and once we refactor semantics / pointer input to use
 // Modifier.Nodes directly, the tests that use this should be rewritten
-@OptIn(ExperimentalComposeUiApi::class)
 fun DelegatableNode.toModifier(): Modifier.Element {
     val node = node as? BackwardsCompatNode
         ?: error("Incorrectly assumed Modifier.Node was a BackwardsCompatNode")
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierNodeElementTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierNodeElementTest.kt
index 63ecefa..862f89b 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierNodeElementTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/ModifierNodeElementTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.node
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.ValueElement
@@ -25,14 +24,13 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-@OptIn(ExperimentalComposeUiApi::class)
 @RunWith(JUnit4::class)
 class ModifierNodeElementTest {
 
     @Test
     fun testDefaultInspectableProperties() {
         @Suppress("unused", "UNUSED_PARAMETER")
-        class AModifierElement(
+        class AElement(
             val string: String,
             val int: Int,
             val map: Map<String, Any>,
@@ -46,7 +44,7 @@
             override fun equals(other: Any?) = (this === other)
         }
 
-        val modifier = AModifierElement(
+        val modifier = AElement(
             string = "parameter 1",
             int = 12345,
             map = mapOf("key" to "value"),
@@ -57,7 +55,7 @@
 
         assertEquals(
             "The modifier's inspectable value was not automatically populated as expected",
-            expectedName = "AModifierElement",
+            expectedName = "AElement",
             expectedValue = null,
             expectedProperties = listOf(
                 ValueElement("classProperty", 42),
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
index 9c36d19..a003146 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
@@ -490,7 +490,6 @@
 
 @PublishedApi
 @Composable
-@Suppress("UnavailableSymbol")
 internal fun MotionLayoutCore(
     @Suppress("HiddenTypeParameter")
     motionScene: MotionScene,
@@ -539,7 +538,6 @@
 
 @PublishedApi
 @Composable
-@Suppress("UnavailableSymbol")
 internal fun MotionLayoutCore(
     start: ConstraintSet,
     end: ConstraintSet,
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java
index f956ce5..9da084d 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionLayout.java
@@ -17,6 +17,7 @@
 package androidx.constraintlayout.motion.widget;
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
 import static androidx.constraintlayout.motion.widget.MotionScene.Transition.TRANSITION_FLAG_FIRST_DRAW;
 import static androidx.constraintlayout.motion.widget.MotionScene.Transition.TRANSITION_FLAG_INTERCEPT_TOUCH;
 import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID;
@@ -504,8 +505,8 @@
  * <table summary="Variant attributes" >
  * <tr>
  * <td>[ConstraintLayout attributes]</td>
- * <td>see {@see androidx.constraintlayout.widget.
- * ConstraintLayout ConstraintLayout} for attributes</td>
+ * <td>Also see {@link ConstraintLayout.LayoutParams
+ * ConstraintLayout.LayoutParams} for attributes</td>
  * </tr>
  * </table>
  *
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
index 63d4f6c..4b86246 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
@@ -1507,9 +1507,9 @@
             sMapToConstant.append(R.styleable.Layout_layout_constraintHeight,
                     LAYOUT_CONSTRAINT_HEIGHT);
             sMapToConstant.append(R.styleable.Layout_layout_constrainedWidth,
-                    LAYOUT_CONSTRAINT_WIDTH);
+                    CONSTRAINED_WIDTH);
             sMapToConstant.append(R.styleable.Layout_layout_constrainedHeight,
-                    LAYOUT_CONSTRAINT_HEIGHT);
+                    CONSTRAINED_HEIGHT);
             sMapToConstant.append(R.styleable.Layout_layout_wrapBehaviorInParent,
                     LAYOUT_WRAP_BEHAVIOR);
 
diff --git a/core/core/src/androidTest/java/androidx/core/view/OWNERS b/core/core/src/androidTest/java/androidx/core/view/OWNERS
index da18aa6..23e355a 100644
--- a/core/core/src/androidTest/java/androidx/core/view/OWNERS
+++ b/core/core/src/androidTest/java/androidx/core/view/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 461355
-per-file AccessibilityDelegateCompatTest.java = file:accessibility/OWNERS
\ No newline at end of file
+per-file AccessibilityDelegateCompatTest.java = file:accessibility/OWNERS
+per-file ViewCompatTest.java = file: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 1e4e68f..daf6ad0 100644
--- a/core/core/src/main/java/androidx/core/view/OWNERS
+++ b/core/core/src/main/java/androidx/core/view/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 461355
-per-file AccessibilityDelegateCompat.java = file:accessibility/OWNERS
\ No newline at end of file
+per-file AccessibilityDelegateCompat.java = file:accessibility/OWNERS
+per-file ViewCompat.java = file:accessibility/OWNERS
\ No newline at end of file
diff --git a/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbManager.kt b/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbManager.kt
index d4769cc..92785bf3 100644
--- a/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbManager.kt
+++ b/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbManager.kt
@@ -59,7 +59,10 @@
                 uwbClient, RangingCapabilities(
                     rangingCapabilities.supportsDistance(),
                     rangingCapabilities.supportsAzimuthalAngle(),
-                    rangingCapabilities.supportsElevationAngle()
+                    rangingCapabilities.supportsElevationAngle(),
+                    rangingCapabilities.getMinRangingInterval(),
+                    rangingCapabilities.getSupportedChannels().toSet(),
+                    rangingCapabilities.getSupportedConfigIds().toSet()
                 ),
                 UwbAddress(localAddress.address),
                 androidx.core.uwb.UwbComplexChannel(
@@ -71,7 +74,10 @@
                 uwbClient, RangingCapabilities(
                     rangingCapabilities.supportsDistance(),
                     rangingCapabilities.supportsAzimuthalAngle(),
-                    rangingCapabilities.supportsElevationAngle()
+                    rangingCapabilities.supportsElevationAngle(),
+                    rangingCapabilities.getMinRangingInterval(),
+                    rangingCapabilities.getSupportedChannels().toSet(),
+                    rangingCapabilities.getSupportedConfigIds().toSet()
                 ),
                 UwbAddress(localAddress.address)
             )
diff --git a/core/uwb/uwb/api/current.txt b/core/uwb/uwb/api/current.txt
index 888340b..57ac364 100644
--- a/core/uwb/uwb/api/current.txt
+++ b/core/uwb/uwb/api/current.txt
@@ -2,13 +2,19 @@
 package androidx.core.uwb {
 
   public final class RangingCapabilities {
-    ctor public RangingCapabilities(boolean isDistanceSupported, boolean isAzimuthalAngleSupported, boolean isElevationAngleSupported);
+    ctor public RangingCapabilities(boolean isDistanceSupported, boolean isAzimuthalAngleSupported, boolean isElevationAngleSupported, int minRangingInterval, java.util.Set<java.lang.Integer> supportedChannels, java.util.Set<java.lang.Integer> supportedConfigIds);
+    method public int getMinRangingInterval();
+    method public java.util.Set<java.lang.Integer> getSupportedChannels();
+    method public java.util.Set<java.lang.Integer> getSupportedConfigIds();
     method public boolean isAzimuthalAngleSupported();
     method public boolean isDistanceSupported();
     method public boolean isElevationAngleSupported();
     property public final boolean isAzimuthalAngleSupported;
     property public final boolean isDistanceSupported;
     property public final boolean isElevationAngleSupported;
+    property public final int minRangingInterval;
+    property public final java.util.Set<java.lang.Integer> supportedChannels;
+    property public final java.util.Set<java.lang.Integer> supportedConfigIds;
   }
 
   public final class RangingMeasurement {
diff --git a/core/uwb/uwb/api/public_plus_experimental_current.txt b/core/uwb/uwb/api/public_plus_experimental_current.txt
index 888340b..57ac364 100644
--- a/core/uwb/uwb/api/public_plus_experimental_current.txt
+++ b/core/uwb/uwb/api/public_plus_experimental_current.txt
@@ -2,13 +2,19 @@
 package androidx.core.uwb {
 
   public final class RangingCapabilities {
-    ctor public RangingCapabilities(boolean isDistanceSupported, boolean isAzimuthalAngleSupported, boolean isElevationAngleSupported);
+    ctor public RangingCapabilities(boolean isDistanceSupported, boolean isAzimuthalAngleSupported, boolean isElevationAngleSupported, int minRangingInterval, java.util.Set<java.lang.Integer> supportedChannels, java.util.Set<java.lang.Integer> supportedConfigIds);
+    method public int getMinRangingInterval();
+    method public java.util.Set<java.lang.Integer> getSupportedChannels();
+    method public java.util.Set<java.lang.Integer> getSupportedConfigIds();
     method public boolean isAzimuthalAngleSupported();
     method public boolean isDistanceSupported();
     method public boolean isElevationAngleSupported();
     property public final boolean isAzimuthalAngleSupported;
     property public final boolean isDistanceSupported;
     property public final boolean isElevationAngleSupported;
+    property public final int minRangingInterval;
+    property public final java.util.Set<java.lang.Integer> supportedChannels;
+    property public final java.util.Set<java.lang.Integer> supportedConfigIds;
   }
 
   public final class RangingMeasurement {
diff --git a/core/uwb/uwb/api/restricted_current.txt b/core/uwb/uwb/api/restricted_current.txt
index 888340b..57ac364 100644
--- a/core/uwb/uwb/api/restricted_current.txt
+++ b/core/uwb/uwb/api/restricted_current.txt
@@ -2,13 +2,19 @@
 package androidx.core.uwb {
 
   public final class RangingCapabilities {
-    ctor public RangingCapabilities(boolean isDistanceSupported, boolean isAzimuthalAngleSupported, boolean isElevationAngleSupported);
+    ctor public RangingCapabilities(boolean isDistanceSupported, boolean isAzimuthalAngleSupported, boolean isElevationAngleSupported, int minRangingInterval, java.util.Set<java.lang.Integer> supportedChannels, java.util.Set<java.lang.Integer> supportedConfigIds);
+    method public int getMinRangingInterval();
+    method public java.util.Set<java.lang.Integer> getSupportedChannels();
+    method public java.util.Set<java.lang.Integer> getSupportedConfigIds();
     method public boolean isAzimuthalAngleSupported();
     method public boolean isDistanceSupported();
     method public boolean isElevationAngleSupported();
     property public final boolean isAzimuthalAngleSupported;
     property public final boolean isDistanceSupported;
     property public final boolean isElevationAngleSupported;
+    property public final int minRangingInterval;
+    property public final java.util.Set<java.lang.Integer> supportedChannels;
+    property public final java.util.Set<java.lang.Integer> supportedConfigIds;
   }
 
   public final class RangingMeasurement {
diff --git a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt
index 7e8d423..91093c4 100644
--- a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt
+++ b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbClientSessionScopeImplTest.kt
@@ -51,7 +51,10 @@
         androidx.core.uwb.RangingCapabilities(
             RANGING_CAPABILITIES.supportsDistance(),
             RANGING_CAPABILITIES.supportsAzimuthalAngle(),
-            RANGING_CAPABILITIES.supportsElevationAngle()
+            RANGING_CAPABILITIES.supportsElevationAngle(),
+            RANGING_CAPABILITIES.getMinRangingInterval(),
+            RANGING_CAPABILITIES.getSupportedChannels().toSet(),
+            RANGING_CAPABILITIES.getSupportedConfigIds().toSet()
         ),
         androidx.core.uwb.UwbAddress(LOCAL_ADDRESS.address)
     )
diff --git a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbControllerSessionScopeImplTest.kt b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbControllerSessionScopeImplTest.kt
index 02a36c8..b68d546 100644
--- a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbControllerSessionScopeImplTest.kt
+++ b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/impl/UwbControllerSessionScopeImplTest.kt
@@ -48,7 +48,10 @@
         androidx.core.uwb.RangingCapabilities(
             RANGING_CAPABILITIES.supportsDistance(),
             RANGING_CAPABILITIES.supportsAzimuthalAngle(),
-            RANGING_CAPABILITIES.supportsElevationAngle()
+            RANGING_CAPABILITIES.supportsElevationAngle(),
+            RANGING_CAPABILITIES.getMinRangingInterval(),
+            RANGING_CAPABILITIES.getSupportedChannels().toSet(),
+            RANGING_CAPABILITIES.getSupportedConfigIds().toSet()
         ),
         UwbAddress(LOCAL_ADDRESS.address)
     )
@@ -57,7 +60,10 @@
         androidx.core.uwb.RangingCapabilities(
             RANGING_CAPABILITIES.supportsDistance(),
             RANGING_CAPABILITIES.supportsAzimuthalAngle(),
-            RANGING_CAPABILITIES.supportsElevationAngle()
+            RANGING_CAPABILITIES.supportsElevationAngle(),
+            RANGING_CAPABILITIES.getMinRangingInterval(),
+            RANGING_CAPABILITIES.getSupportedChannels().toSet(),
+            RANGING_CAPABILITIES.getSupportedConfigIds().toSet()
         ),
         UwbAddress(LOCAL_ADDRESS.address),
         androidx.core.uwb.UwbComplexChannel(
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt
index 76aedc7..5ef3b7b 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingCapabilities.kt
@@ -21,9 +21,15 @@
  * @property isDistanceSupported - Whether distance ranging is supported
  * @property isAzimuthalAngleSupported - Whether azimuthal angle of arrival is supported
  * @property isElevationAngleSupported - Whether elevation angle of arrival is supported
+ * @property minRangingInterval - Minimum ranging interval
+ * @property supportedChannels - Array of supported channels
+ * @property supportedConfigIds - Array of supported config ids
  **/
 class RangingCapabilities(
     val isDistanceSupported: Boolean,
     val isAzimuthalAngleSupported: Boolean,
-    val isElevationAngleSupported: Boolean
+    val isElevationAngleSupported: Boolean,
+    val minRangingInterval: Int,
+    val supportedChannels: Set<Int>,
+    val supportedConfigIds: Set<Int>
 )
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/backend/RangingCapabilities.java b/core/uwb/uwb/src/main/java/androidx/core/uwb/backend/RangingCapabilities.java
index f53e525..9854ac0 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/backend/RangingCapabilities.java
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/backend/RangingCapabilities.java
@@ -36,6 +36,17 @@
     public boolean supportsAzimuthalAngle = false;
     @SuppressLint("MutableBareField")
     public boolean supportsElevationAngle = false;
+    @SuppressLint("MutableBareField")
+    public int minRangingInterval = 0;
+    @NonNull
+    @SuppressLint("MutableBareField")
+    public int[] supportedChannels;
+    @NonNull
+    @SuppressLint("MutableBareField")
+    public int[] supportedNtfConfigs;
+    @NonNull
+    @SuppressLint("MutableBareField")
+    public int[] supportedConfigIds;
     @NonNull
     public static final android.os.Parcelable.Creator<RangingCapabilities> CREATOR = new android.os.Parcelable.Creator<RangingCapabilities>() {
         @Override
@@ -56,6 +67,10 @@
         _aidl_parcel.writeBoolean(supportsDistance);
         _aidl_parcel.writeBoolean(supportsAzimuthalAngle);
         _aidl_parcel.writeBoolean(supportsElevationAngle);
+        _aidl_parcel.writeInt(minRangingInterval);
+        _aidl_parcel.writeIntArray(supportedChannels);
+        _aidl_parcel.writeIntArray(supportedNtfConfigs);
+        _aidl_parcel.writeIntArray(supportedConfigIds);
         int _aidl_end_pos = _aidl_parcel.dataPosition();
         _aidl_parcel.setDataPosition(_aidl_start_pos);
         _aidl_parcel.writeInt(_aidl_end_pos - _aidl_start_pos);
@@ -74,6 +89,14 @@
             supportsAzimuthalAngle = _aidl_parcel.readBoolean();
             if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
             supportsElevationAngle = _aidl_parcel.readBoolean();
+            if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
+            minRangingInterval = _aidl_parcel.readInt();
+            if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
+            supportedChannels = _aidl_parcel.createIntArray();
+            if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
+            supportedNtfConfigs = _aidl_parcel.createIntArray();
+            if (_aidl_parcel.dataPosition() - _aidl_start_pos >= _aidl_parcelable_size) return;
+            supportedConfigIds = _aidl_parcel.createIntArray();
         } finally {
             if (_aidl_start_pos > (Integer.MAX_VALUE - _aidl_parcelable_size)) {
                 throw new android.os.BadParcelableException("Overflow in the size of parcelable");
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
index 84297cf..4edf85e 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
@@ -90,7 +90,10 @@
             val rangingCapabilities = RangingCapabilities(
                 nearbyRangingCapabilities.supportsDistance(),
                 nearbyRangingCapabilities.supportsAzimuthalAngle(),
-                nearbyRangingCapabilities.supportsElevationAngle())
+                nearbyRangingCapabilities.supportsElevationAngle(),
+                nearbyRangingCapabilities.getMinRangingInterval(),
+                nearbyRangingCapabilities.getSupportedChannels().toSet(),
+                nearbyRangingCapabilities.getSupportedConfigIds().toSet())
             return if (isController) {
                 val uwbComplexChannel = uwbClient.complexChannel.await()
                 UwbControllerSessionScopeImpl(
@@ -128,7 +131,10 @@
                 RangingCapabilities(
                     it.supportsDistance,
                     it.supportsAzimuthalAngle,
-                    it.supportsElevationAngle)
+                    it.supportsElevationAngle,
+                    it.minRangingInterval,
+                    it.supportedChannels.toSet(),
+                    it.supportedConfigIds.toSet())
             }
             return if (isController) {
                 val uwbComplexChannel = uwbClient.complexChannel
diff --git a/development/studio/studio.vmoptions b/development/studio/studio.vmoptions
index 2935f84..78a7910 100644
--- a/development/studio/studio.vmoptions
+++ b/development/studio/studio.vmoptions
@@ -1,6 +1,8 @@
 -Xmx8g
 -Dappinspection.use.dev.jar=true
 -Dlayout.inspector.rel.jar.location=#studio/../../../../../../out/dist/inspection
+# b/279181712
+-Djdk.attach.allowAttachSelf=true
 # https://github.com/google/google-java-format#intellij-jre-config
 --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
 --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
index a6775dc..57e00cd 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sk/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="emoji_category_recent" msgid="7142376595414250279">"NEDÁVNO POUŽITÉ"</string>
-    <string name="emoji_category_emotions" msgid="1570830970240985537">"SMAJLÍKY A EMOTIKONY"</string>
+    <string name="emoji_category_emotions" msgid="1570830970240985537">"SMAJLÍKY A EMÓCIE"</string>
     <string name="emoji_category_people" msgid="7968173366822927025">"ĽUDIA"</string>
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ZVIERATÁ A PRÍRODA"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"JEDLO A NÁPOJE"</string>
diff --git a/fragment/fragment/src/androidTest/AndroidManifest.xml b/fragment/fragment/src/androidTest/AndroidManifest.xml
index 2ea4839..d162dc5 100644
--- a/fragment/fragment/src/androidTest/AndroidManifest.xml
+++ b/fragment/fragment/src/androidTest/AndroidManifest.xml
@@ -51,6 +51,9 @@
         <activity android:name="androidx.fragment.app.FragmentFinishEarlyTestActivity" />
         <activity android:name="androidx.fragment.app.SimpleContainerActivity" />
         <activity android:name="androidx.fragment.app.ReplaceInCreateActivity" />
+        <activity android:name="androidx.fragment.app.ResultActivity1" />
+        <activity android:name="androidx.fragment.app.ResultActivity2" />
+        <activity android:name="androidx.fragment.app.ResultActivity3" />
         <activity android:name="androidx.fragment.app.DialogActivity"
                   android:theme="@style/DialogTheme"/>
         <activity android:name="androidx.fragment.app.TestAppCompatActivity"
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt
index 99caf17..be4e603 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentActivityResultTest.kt
@@ -26,12 +26,15 @@
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
 import androidx.core.app.ActivityOptionsCompat
 import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.testutils.withActivity
 import androidx.testutils.withUse
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import leakcanary.DetectLeaksAfterTestSuccess
 import org.junit.Assert.fail
 import org.junit.Rule
@@ -55,7 +58,7 @@
                 val fragment = RegisterInLifecycleCallbackFragment(Fragment.ATTACHED)
 
                 supportFragmentManager.beginTransaction()
-                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .add(R.id.content, fragment)
                     .commitNow()
 
                 assertThat(fragment.launchedCounter).isEqualTo(1)
@@ -70,7 +73,7 @@
                 val fragment = RegisterInLifecycleCallbackFragment(Fragment.CREATED)
 
                 supportFragmentManager.beginTransaction()
-                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .add(R.id.content, fragment)
                     .commitNow()
 
                 assertThat(fragment.launchedCounter).isEqualTo(1)
@@ -86,7 +89,7 @@
 
                 try {
                     supportFragmentManager.beginTransaction()
-                        .add(androidx.fragment.test.R.id.content, fragment)
+                        .add(R.id.content, fragment)
                         .commitNow()
                     fail("Registering for activity result after onCreate() should fail")
                 } catch (e: IllegalStateException) {
@@ -108,7 +111,7 @@
                 val fragment = ActivityResultFragment()
 
                 supportFragmentManager.beginTransaction()
-                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .add(R.id.content, fragment)
                     .commitNow()
             }
         }
@@ -121,13 +124,62 @@
                 val fragment = DoubleActivityResultFragment()
 
                 supportFragmentManager.beginTransaction()
-                    .add(androidx.fragment.test.R.id.content, fragment)
+                    .add(R.id.content, fragment)
                     .commitNow()
 
                 assertThat(fragment.launchedCounter).isEqualTo(2)
             }
         }
     }
+
+    @Test
+    fun launchMultipleActivitiesFromFragment() {
+        withUse(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fragment = LaunchMultipleActivitiesFragment()
+            val fm = withActivity { supportFragmentManager }
+
+            fm.beginTransaction()
+                .add(R.id.content, fragment)
+                .commit()
+            executePendingTransactions()
+
+            @Suppress("DEPRECATION")
+            withActivity {
+                fragment.startActivityForResult(
+                    Intent(this, ResultActivity1::class.java),
+                    ResultActivity1.REQUEST_CODE
+                )
+                fragment.startActivityForResult(
+                    Intent(this, ResultActivity2::class.java),
+                    ResultActivity2.REQUEST_CODE
+                )
+                fragment.startActivityForResult(
+                    Intent(this, ResultActivity3::class.java),
+                    ResultActivity3.REQUEST_CODE
+                )
+            }
+
+            assertThat(
+                fragment.onActivityResultCountDownLatch.await(1000, TimeUnit.MILLISECONDS)
+            ).isTrue()
+
+            assertThat(fragment.launcherInfoMap)
+                .containsEntry(
+                    ResultActivity1.REQUEST_CODE,
+                    ResultActivity1.RESULT_KEY
+                )
+            assertThat(fragment.launcherInfoMap)
+                .containsEntry(
+                    ResultActivity2.REQUEST_CODE,
+                    ResultActivity2.RESULT_KEY
+                )
+            assertThat(fragment.launcherInfoMap)
+                .containsEntry(
+                    ResultActivity3.REQUEST_CODE,
+                    ResultActivity3.RESULT_KEY
+                )
+        }
+    }
 }
 
 class ActivityResultFragment : Fragment() {
@@ -225,3 +277,65 @@
         launcher.launch(Intent())
     }
 }
+
+@Suppress("DEPRECATION")
+class LaunchMultipleActivitiesFragment : Fragment() {
+    val launcherInfoMap: MutableMap<Int, String> = mutableMapOf()
+    val onActivityResultCountDownLatch = CountDownLatch(3)
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+
+        val extras = data?.extras
+        if (extras!!.containsKey(ResultActivity1.RESULT_KEY)) {
+            launcherInfoMap[requestCode] = ResultActivity1.RESULT_KEY
+        } else if (extras.containsKey(ResultActivity2.RESULT_KEY)) {
+            launcherInfoMap[requestCode] = ResultActivity2.RESULT_KEY
+        } else if (extras.containsKey(ResultActivity3.RESULT_KEY)) {
+            launcherInfoMap[requestCode] = ResultActivity3.RESULT_KEY
+        }
+
+        onActivityResultCountDownLatch.countDown()
+    }
+}
+
+class ResultActivity1 : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setResult(RESULT_OK, Intent().putExtra(RESULT_KEY, RESULT_VALUE))
+        finish()
+    }
+
+    companion object {
+        const val REQUEST_CODE = 1111
+        const val RESULT_KEY = "ResultActivity1"
+        private const val RESULT_VALUE = "ResultActivity1Value"
+    }
+}
+
+class ResultActivity2 : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setResult(RESULT_OK, Intent().putExtra(RESULT_KEY, RESULT_VALUE))
+        finish()
+    }
+
+    companion object {
+        const val REQUEST_CODE = 2222
+        const val RESULT_KEY = "ResultActivity2"
+        private const val RESULT_VALUE = "ResultActivity2Value"
+    }
+}
+
+class ResultActivity3 : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setResult(RESULT_OK, Intent().putExtra(RESULT_KEY, RESULT_VALUE))
+        finish()
+    }
+
+    companion object {
+        const val REQUEST_CODE = 3333
+        const val RESULT_KEY = "ResultActivity3"
+        private const val RESULT_VALUE = "ResultActivity3Value"
+    }
+}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 01cd33a..3e841c8 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -2735,7 +2735,7 @@
                     new ActivityResultCallback<ActivityResult>() {
                         @Override
                         public void onActivityResult(ActivityResult result) {
-                            LaunchedFragmentInfo requestInfo = mLaunchedFragments.pollFirst();
+                            LaunchedFragmentInfo requestInfo = mLaunchedFragments.pollLast();
                             if (requestInfo == null) {
                                 Log.w(TAG, "No Activities were started for result for " + this);
                                 return;
diff --git a/glance/glance-appwidget/integration-tests/demos/build.gradle b/glance/glance-appwidget/integration-tests/demos/build.gradle
index e372ce9..c38f05a 100644
--- a/glance/glance-appwidget/integration-tests/demos/build.gradle
+++ b/glance/glance-appwidget/integration-tests/demos/build.gradle
@@ -31,11 +31,10 @@
     implementation(project(":glance:glance-material3"))
     implementation("androidx.activity:activity:1.4.0")
     implementation("androidx.activity:activity-compose:1.4.0")
-    implementation("androidx.compose.material:material:1.1.0-beta02")
     implementation("androidx.compose.ui:ui:1.1.0-beta02")
     implementation("androidx.compose.foundation:foundation:1.1.0-beta02")
     implementation("androidx.compose.foundation:foundation-layout:1.1.0-beta02")
-    implementation("androidx.compose.material:material:1.1.0-beta02")
+    implementation("androidx.compose.material:material:1.4.0")
     implementation("androidx.datastore:datastore-preferences-core:1.0.0")
     implementation("androidx.datastore:datastore-preferences:1.0.0")
     implementation "androidx.compose.material3:material3:1.0.0"
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index 2791119..9ff3ff8 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -36,18 +36,5 @@
                 android:name="android.appwidget.provider"
                 android:resource="@xml/default_app_widget_info" />
         </receiver>
-        <receiver
-            android:name="androidx.glance.appwidget.macrobenchmark.target.BasicAppWidgetWithSessionReceiver"
-            android:label="BasicAppWidget Receiver with sessions enabled"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="androidx.glance.appwidget.action.DEBUG_UPDATE" />
-                <action android:name="android.intent.action.LOCALE_CHANGED" />
-            </intent-filter>
-            <meta-data
-                android:name="android.appwidget.provider"
-                android:resource="@xml/default_app_widget_info" />
-        </receiver>
     </application>
 </manifest>
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/java/androidx/glance/appwidget/macrobenchmark/target/BasicAppWidget.kt b/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/java/androidx/glance/appwidget/macrobenchmark/target/BasicAppWidget.kt
index e3d1d31..ebc47b6 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/java/androidx/glance/appwidget/macrobenchmark/target/BasicAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark-target/src/main/java/androidx/glance/appwidget/macrobenchmark/target/BasicAppWidget.kt
@@ -53,12 +53,6 @@
     }
 }
 
-class BasicAppWidgetWithSession : BasicAppWidget()
-
 class BasicAppWidgetReceiver : GlanceAppWidgetReceiver() {
     override val glanceAppWidget: GlanceAppWidget = BasicAppWidget()
 }
-
-class BasicAppWidgetWithSessionReceiver : GlanceAppWidgetReceiver() {
-    override val glanceAppWidget: GlanceAppWidget = BasicAppWidgetWithSession()
-}
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle b/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle
index 8bc801c..19b0df6 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark/build.gradle
@@ -30,8 +30,9 @@
 
 dependencies {
     implementation 'androidx.compose.ui:ui-unit:1.2.1'
-    androidTestImplementation('androidx.benchmark:benchmark-junit4:1.1.0')
-    androidTestImplementation('androidx.benchmark:benchmark-macro-junit4:1.1.0')
+    androidTestImplementation(project(':benchmark:benchmark-macro'))
+    androidTestImplementation(project(':benchmark:benchmark-common'))
+    androidTestImplementation(project(':benchmark:benchmark-macro-junit4'))
     androidTestImplementation('androidx.core:core-ktx:1.7.0')
     androidTestImplementation(project(":glance:glance"))
     androidTestImplementation(project(":glance:glance-appwidget"))
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostRule.kt b/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostRule.kt
index 90abda1..d70d850 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostRule.kt
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostRule.kt
@@ -40,19 +40,13 @@
 class AppWidgetHostRule(
     private var mPortraitSize: DpSize = DpSize(200.dp, 300.dp),
     private var mLandscapeSize: DpSize = DpSize(300.dp, 200.dp),
-    useSession: Boolean = false,
 ) : TestRule {
     private val mInstrumentation = InstrumentationRegistry.getInstrumentation()
     private val mUiAutomation = mInstrumentation.uiAutomation
-    private val targetComponent =
-        ComponentName(
-            "androidx.glance.appwidget.macrobenchmark.target",
-            if (useSession) {
-                "androidx.glance.appwidget.macrobenchmark.target.BasicAppWidgetWithSessionReceiver"
-            } else {
-                "androidx.glance.appwidget.macrobenchmark.target.BasicAppWidgetReceiver"
-            }
-        )
+    private val targetComponent = ComponentName(
+        "androidx.glance.appwidget.macrobenchmark.target",
+        "androidx.glance.appwidget.macrobenchmark.target.BasicAppWidgetReceiver",
+    )
 
     private val mActivityRule: ActivityScenarioRule<AppWidgetHostTestActivity> =
         ActivityScenarioRule(
@@ -62,11 +56,7 @@
                         ApplicationProvider.getApplicationContext(),
                         AppWidgetHostTestActivity::class.java,
                     )
-                )
-                .putExtra(
-                    AppWidgetHostTestActivity.EXTRA_TARGET_RECEIVER,
-                    targetComponent
-                )
+                ).putExtra(AppWidgetHostTestActivity.EXTRA_TARGET_RECEIVER, targetComponent)
         )
 
     private val mUiDevice = UiDevice.getInstance(mInstrumentation)
@@ -86,8 +76,8 @@
     private val mInnerRules = RuleChain.outerRule(mActivityRule).around(mOrientationRule)
 
     private var mHostStarted = false
-    private var mMaybeHostView: TestAppWidgetHostView? = null
-    private var mAppWidgetId = 0
+    private lateinit var hostView: TestAppWidgetHostView
+    private val appWidgetId: Int get() = hostView.appWidgetId
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
 
     override fun apply(base: Statement, description: Description) = object : Statement() {
@@ -103,18 +93,16 @@
      * Start the host and bind the app widget.
      * Measures time from binding an app widget to receiving the first RemoteViews.
      */
-    fun startHost() {
+    suspend fun startHost() {
         mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.BIND_APPWIDGET)
         mHostStarted = true
 
         Trace.beginSection("appWidgetInitialUpdate")
         mActivityRule.scenario.onActivity { activity ->
-            mMaybeHostView = activity.bindAppWidget(mPortraitSize, mLandscapeSize)
+            hostView = checkNotNull(activity.bindAppWidget(mPortraitSize, mLandscapeSize)) {
+                "Failed to bind widget and create host view"
+            }
         }
-
-        val hostView = checkNotNull(mMaybeHostView) { "Host view wasn't successfully started" }
-
-        mAppWidgetId = hostView.appWidgetId
         hostView.waitForRemoteViews()
         Trace.endSection()
     }
@@ -122,21 +110,15 @@
     /**
      * Measures time from sending APPWIDGET_UPDATE broadcast to receiving RemoteViews.
      */
-    fun updateAppWidget() {
+    suspend fun updateAppWidget() {
         val intent = Intent(GlanceAppWidgetReceiver.ACTION_DEBUG_UPDATE)
             .setPackage("androidx.glance.appwidget.macrobenchmark.target")
-            .setComponent(
-                ComponentName(
-                    "androidx.glance.appwidget.macrobenchmark.target",
-                    "androidx.glance.appwidget.macrobenchmark.target.BasicAppWidgetReceiver"
-                )
-            )
-            .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(mAppWidgetId))
-        val hostView = checkNotNull(mMaybeHostView) { "Host view not started" }
+            .setComponent(targetComponent)
+            .putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId))
         Trace.beginSection("appWidgetUpdate")
-        hostView.resetRemoteViewsLatch()
-        mContext.sendBroadcast(intent)
-        hostView.waitForRemoteViews()
+        hostView.runAndWaitForRemoteViews {
+            mContext.sendBroadcast(intent)
+        }
         Trace.endSection()
     }
 }
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostTestActivity.kt b/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostTestActivity.kt
index 41098fc..c04ff3c 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostTestActivity.kt
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetHostTestActivity.kt
@@ -44,8 +44,8 @@
 import androidx.compose.ui.unit.max
 import androidx.compose.ui.unit.min
 import java.util.Locale
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
 import org.junit.Assert.fail
 
 @RequiresApi(26)
@@ -147,13 +147,10 @@
 class TestAppWidgetHostView(context: Context) : AppWidgetHostView(context) {
 
     init {
-        // Prevent asynchronous inflation of the App Widget
-        setExecutor(null)
         layoutDirection = View.LAYOUT_DIRECTION_LOCALE
     }
 
-    private var mLatch: CountDownLatch? = null
-    private var mRemoteViews: RemoteViews? = null
+    private val remoteViews = MutableStateFlow<RemoteViews?>(null)
     private var mPortraitSize: DpSize = DpSize(0.dp, 0.dp)
     private var mLandscapeSize: DpSize = DpSize(0.dp, 0.dp)
 
@@ -161,31 +158,19 @@
      * Wait for the new remote views to be received. If a remote views was already received, return
      * immediately.
      */
-    fun waitForRemoteViews() {
-        synchronized(this) {
-            mRemoteViews?.let { return }
-            mLatch = CountDownLatch(1)
-        }
-        val result = mLatch?.await(5, TimeUnit.SECONDS)!!
-        require(result) { "Timeout before getting RemoteViews" }
+    suspend fun waitForRemoteViews() {
+        remoteViews.first { it != null }
+    }
+
+    suspend fun runAndWaitForRemoteViews(block: () -> Unit) {
+        remoteViews.value = null
+        block()
+        waitForRemoteViews()
     }
 
     override fun updateAppWidget(remoteViews: RemoteViews?) {
         super.updateAppWidget(remoteViews)
-        synchronized(this) {
-            mRemoteViews = remoteViews
-            if (remoteViews != null) {
-                mLatch?.countDown()
-            }
-        }
-    }
-
-    /** Reset the latch used to detect the arrival of a new RemoteViews. */
-    fun resetRemoteViewsLatch() {
-        synchronized(this) {
-            mRemoteViews = null
-            mLatch = null
-        }
+        this.remoteViews.value = remoteViews
     }
 
     fun setSizes(portraitSize: DpSize, landscapeSize: DpSize) {
@@ -211,7 +196,7 @@
         TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, displayMetrics).toInt()
 
     fun reapplyRemoteViews() {
-        mRemoteViews?.let { super.updateAppWidget(it) }
+        remoteViews.value?.let { super.updateAppWidget(it) }
     }
 }
 
diff --git a/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetUpdateBenchmark.kt b/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetUpdateBenchmark.kt
index 039a39b..f354fdf 100644
--- a/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetUpdateBenchmark.kt
+++ b/glance/glance-appwidget/integration-tests/macrobenchmark/src/androidTest/java/androidx/glance/appwidget/macrobenchmark/AppWidgetUpdateBenchmark.kt
@@ -23,7 +23,7 @@
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
-import androidx.testutils.createStartupCompilationParams
+import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,14 +34,12 @@
 @RunWith(Parameterized::class)
 class AppWidgetUpdateBenchmark(
     private val startupMode: StartupMode,
-    private val compilationMode: CompilationMode,
-    useGlanceSession: Boolean,
 ) {
     @get:Rule
     val benchmarkRule = MacrobenchmarkRule()
 
     @get:Rule
-    val appWidgetHostRule = AppWidgetHostRule(useSession = useGlanceSession)
+    val appWidgetHostRule = AppWidgetHostRule()
 
     @OptIn(ExperimentalMetricApi::class)
     @Test
@@ -51,11 +49,13 @@
             TraceSectionMetric("appWidgetInitialUpdate"),
             TraceSectionMetric("GlanceAppWidget::update"),
         ),
-        iterations = 100,
-        compilationMode = compilationMode,
+        iterations = 5,
+        compilationMode = CompilationMode.DEFAULT,
         startupMode = startupMode,
     ) {
-        appWidgetHostRule.startHost()
+       runBlocking {
+           appWidgetHostRule.startHost()
+       }
     }
 
     @OptIn(ExperimentalMetricApi::class)
@@ -66,29 +66,24 @@
             TraceSectionMetric("appWidgetUpdate"),
             TraceSectionMetric("GlanceAppWidget::update"),
         ),
-        iterations = 100,
-        compilationMode = compilationMode,
+        iterations = 5,
+        compilationMode = CompilationMode.DEFAULT,
         startupMode = startupMode,
         setupBlock = {
-            appWidgetHostRule.startHost()
-            if (startupMode == StartupMode.COLD) killProcess()
+            runBlocking {
+                appWidgetHostRule.startHost()
+            }
         }
     ) {
-        appWidgetHostRule.updateAppWidget()
+        runBlocking {
+            appWidgetHostRule.updateAppWidget()
+        }
     }
 
     companion object {
-        @Parameterized.Parameters(name = "startup={0},compilation={1},useSession={2}")
+        @Parameterized.Parameters(name = "startup={0}")
         @JvmStatic
         fun parameters() =
-            createStartupCompilationParams(
-                startupModes = listOf(StartupMode.COLD, StartupMode.WARM),
-                compilationModes = listOf(CompilationMode.DEFAULT)
-            ).flatMap {
-                listOf(
-                    it + true,
-                    it + false,
-                )
-            }
+            listOf(arrayOf(StartupMode.COLD), arrayOf(StartupMode.WARM))
     }
 }
\ No newline at end of file
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 7ba78d1..f9960be 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
@@ -1178,6 +1178,18 @@
         }
     }
 
+    @Test
+    fun rootViewIdIsNotReservedId() = runTest {
+        TestGlanceAppWidget.uiDefinition = {
+            Column {}
+        }
+
+        mHostRule.startHost()
+        mHostRule.onUnboxedHostView<View> { root ->
+            assertThat(root.id).isNotIn(0..1)
+        }
+    }
+
     // Check there is a single span of the given type and that it passes the [check].
     private inline
     fun <reified T> SpannedString.checkHasSingleTypedSpan(check: (T) -> Unit) {
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
index 5f5cf14..af28347 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
@@ -20,10 +20,13 @@
 import android.os.Build
 import android.view.Gravity
 import android.view.View
+import android.view.ViewTreeObserver
 import android.widget.Button
 import android.widget.FrameLayout
 import android.widget.ListView
 import android.widget.TextView
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.glance.Button
@@ -42,6 +45,8 @@
 import kotlin.test.assertIs
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,9 +56,11 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SdkSuppress(minSdkVersion = 29)
 @MediumTest
 class LazyColumnTest {
@@ -456,6 +463,44 @@
             assertThat(lastClicked).isEqualTo(index)
         }
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29, maxSdkVersion = 31)
+    fun listCanBeUpdated_RemoteViewsService() = runTest {
+        val countFlow = MutableStateFlow(0)
+        TestGlanceAppWidget.uiDefinition = {
+            val count by countFlow.collectAsState()
+            LazyColumn {
+                items(count) { Text("$it") }
+            }
+        }
+
+        mHostRule.startHost()
+        mHostRule.waitForListViewChildCount(countFlow.value)
+        (1..10).forEach { next ->
+            countFlow.emit(next)
+            mHostRule.waitForListViewChildCount(next)
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32)
+    fun listCanBeUpdated_RemoteCollectionItems() = runTest {
+        val countFlow = MutableStateFlow(0)
+        TestGlanceAppWidget.uiDefinition = {
+            val count by countFlow.collectAsState()
+            LazyColumn {
+                items(count) { Text("$it") }
+            }
+        }
+
+        mHostRule.startHost()
+        mHostRule.waitForListViewChildCount(countFlow.value)
+        (1..10).forEach { next ->
+            countFlow.emit(next)
+            mHostRule.waitForListViewChildCount(next)
+        }
+    }
 }
 
 internal fun AppWidgetHostRule.waitForListViewChildren(action: (list: ListView) -> Unit = {}) {
@@ -472,6 +517,32 @@
 }
 
 /**
+ * Wait until the first ListView child under the root AppWidgetHostView has [count] children.
+ *
+ * Suspending version that does not timeout, instead relies on the `runTest` timeout.
+ */
+internal suspend fun AppWidgetHostRule.waitForListViewChildCount(count: Int) {
+    val resume = Channel<Unit>(Channel.CONFLATED)
+    fun test() = mHostView.findChildByType<ListView>()?.childCount == count
+    val onDrawListener = ViewTreeObserver.OnDrawListener {
+        if (test()) resume.trySend(Unit)
+    }
+
+    onHostActivity {
+        // If test is already true, do not wait for the next draw to resume
+        if (test()) resume.trySend(Unit)
+        mHostView.viewTreeObserver.addOnDrawListener(onDrawListener)
+    }
+    try {
+        resume.receive()
+    } finally {
+        onHostActivity {
+            mHostView.viewTreeObserver.removeOnDrawListener(onDrawListener)
+        }
+    }
+}
+
+/**
  * Returns a flow that mirrors the original flow, but filters out values that are followed by the
  * newer values within the given timeout.
  */
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
index 1b50857..e6a5fed 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
@@ -146,6 +146,8 @@
         else -> throw IllegalArgumentException("There must be between 1 and 2 views.")
     }
 
+private const val LastInvalidViewId = 1
+
 internal data class TranslationContext(
     val context: Context,
     val appWidgetId: Int,
@@ -153,7 +155,7 @@
     val layoutConfiguration: LayoutConfiguration?,
     val itemPosition: Int,
     val isLazyCollectionDescendant: Boolean = false,
-    val lastViewId: AtomicInteger = AtomicInteger(0),
+    val lastViewId: AtomicInteger = AtomicInteger(LastInvalidViewId),
     val parentContext: InsertedViewInfo = InsertedViewInfo(),
     val isBackgroundSpecified: AtomicBoolean = AtomicBoolean(false),
     val layoutSize: DpSize = DpSize.Zero,
@@ -172,7 +174,7 @@
         forChild(pos = 0, parent = root.view)
             .copy(
                 isBackgroundSpecified = AtomicBoolean(false),
-                lastViewId = AtomicInteger(0),
+                lastViewId = AtomicInteger(LastInvalidViewId),
             )
 
     fun resetViewId(newViewId: Int = 0) = copy(lastViewId = AtomicInteger(newViewId))
diff --git a/glance/glance-template/integration-tests/template-demos/build.gradle b/glance/glance-template/integration-tests/template-demos/build.gradle
index e4f4972..1ae4ff0 100644
--- a/glance/glance-template/integration-tests/template-demos/build.gradle
+++ b/glance/glance-template/integration-tests/template-demos/build.gradle
@@ -32,14 +32,14 @@
     implementation(project(":glance:glance-material3"))
     implementation("androidx.activity:activity:1.4.0")
     implementation("androidx.activity:activity-compose:1.4.0")
-    implementation("androidx.compose.material:material:1.1.0-beta02")
+    implementation("androidx.compose.material:material:1.4.0")
     implementation("androidx.compose.ui:ui:1.1.0-beta02")
     implementation("androidx.compose.foundation:foundation:1.1.0-beta02")
     implementation("androidx.compose.foundation:foundation-layout:1.1.0-beta02")
     implementation("androidx.compose.material:material:1.1.0-beta02")
     implementation("androidx.datastore:datastore-preferences-core:1.0.0")
     implementation("androidx.datastore:datastore-preferences:1.0.0")
-    implementation "androidx.compose.material3:material3:1.0.0"
+    implementation("androidx.compose.material3:material3:1.0.0")
 }
 
 android {
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
index e3cb1fa..efa050f 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
@@ -61,7 +61,7 @@
  */
 internal class SessionWorker(
     appContext: Context,
-    params: WorkerParameters,
+    private val params: WorkerParameters,
     private val sessionManager: SessionManager = GlanceSessionManager,
     private val timeouts: TimeoutOptions = TimeoutOptions(),
     @Deprecated("Deprecated by super class, replacement in progress, see b/245353737")
@@ -98,7 +98,15 @@
 
     private suspend fun TimerScope.work(): Result {
         val session = sessionManager.getSession(key)
-            ?: error("No session available for key $key")
+            ?: if (params.runAttemptCount == 0) {
+                error("No session available for key $key")
+            } else {
+                // If this is a retry because the process was restarted (e.g. on app upgrade or
+                // reinstall), the Session object won't be available because it's not persistable
+                // at the moment.
+                Log.w(TAG, "SessionWorker attempted restart but Session is not available for $key")
+                return Result.success()
+            }
 
         if (DEBUG) Log.d(TAG, "Setting up composition for ${session.key}")
         val frameClock = InteractiveFrameClock(this)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fc35eee..e655b07 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -236,7 +236,7 @@
 paparazziNativeMacOsX64 = { module = "app.cash.paparazzi:layoutlib-native-macosx", version.ref = "paparazziNative" }
 protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
 protobufCompiler = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
-protobufGradlePluginz = { module = "com.google.protobuf:protobuf-gradle-plugin", version = "0.9.0" }
+protobufGradlePluginz = { module = "com.google.protobuf:protobuf-gradle-plugin", version = "0.9.3" }
 protobufLite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
 protobufKotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" }
 reactiveStreams = { module = "org.reactivestreams:reactive-streams", version = "1.0.0" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 3ac50bd..b0ef115 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -317,7 +317,10 @@
          <trusted-key id="ae9e53fc28ff2ab1012273d0bf1518e0160788a2" group="org.apache" name="apache"/>
          <trusted-key id="afa2b1823fc021bfd08c211fd5f4c07a434ab3da" group="com.squareup"/>
          <trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/>
-         <trusted-key id="b02335aa54ccf21e52bbf9abd9c565aa72ba2fdd" group="io.grpc"/>
+         <trusted-key id="b02335aa54ccf21e52bbf9abd9c565aa72ba2fdd">
+            <trusting group="com.google.protobuf"/>
+            <trusting group="io.grpc"/>
+         </trusted-key>
          <trusted-key id="b252e5789636134a311e4463971b04f56669b805" group="com.google.jsilver"/>
          <trusted-key id="b41089a2da79b0fa5810252872385ff0af338d52" group="org.threeten"/>
          <trusted-key id="b46dc71e03feeb7f89d1f2491f7a8f87b9d8f501" group="org.jetbrains.trove4j"/>
@@ -619,14 +622,6 @@
             <sha256 value="683be0cd32af9c80a6d4a143b9a6ac2eb45ebc3ccd16db4ca11b94e55fc5e52f" origin="Generated by Gradle" reason="Artifact is not signed"/>
          </artifact>
       </component>
-      <component group="gradle.plugin.com.google.protobuf" name="protobuf-gradle-plugin" version="0.8.13">
-         <artifact name="protobuf-gradle-plugin-0.8.13.jar">
-            <sha256 value="8a04b6eee4eab68c73b6e61cc8e00206753691b781d042afbae746f97e8c6f2d" origin="Generated by Gradle" reason="Artifact is not signed"/>
-         </artifact>
-         <artifact name="protobuf-gradle-plugin-0.8.13.pom">
-            <sha256 value="d8c46016037cda6360561b9c6a21a6c2a4847cad15c3c63903e15328fbcccc45" origin="Generated by Gradle" reason="Artifact is not signed"/>
-         </artifact>
-      </component>
       <component group="javax.annotation" name="jsr250-api" version="1.0">
          <artifact name="jsr250-api-1.0.jar">
             <sha256 value="a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f" origin="Generated by Gradle"/>
diff --git a/graphics/graphics-core/api/current.txt b/graphics/graphics-core/api/current.txt
index 69d2dd9..0079079 100644
--- a/graphics/graphics-core/api/current.txt
+++ b/graphics/graphics-core/api/current.txt
@@ -291,9 +291,9 @@
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, androidx.graphics.surface.SurfaceControlCompat? newParent);
     method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.view.AttachedSurfaceControl attachedSurfaceControl);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setAlpha(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float alpha);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function0<kotlin.Unit>? releaseCallback);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer, optional androidx.hardware.SyncFenceCompat? fence);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function0<kotlin.Unit>? releaseCallback);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBufferTransform(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int transformation);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setCrop(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Rect? crop);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDamageRegion(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Region? region);
diff --git a/graphics/graphics-core/api/public_plus_experimental_current.txt b/graphics/graphics-core/api/public_plus_experimental_current.txt
index 69d2dd9..0079079 100644
--- a/graphics/graphics-core/api/public_plus_experimental_current.txt
+++ b/graphics/graphics-core/api/public_plus_experimental_current.txt
@@ -291,9 +291,9 @@
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, androidx.graphics.surface.SurfaceControlCompat? newParent);
     method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.view.AttachedSurfaceControl attachedSurfaceControl);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setAlpha(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float alpha);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function0<kotlin.Unit>? releaseCallback);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer, optional androidx.hardware.SyncFenceCompat? fence);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function0<kotlin.Unit>? releaseCallback);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBufferTransform(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int transformation);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setCrop(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Rect? crop);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDamageRegion(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Region? region);
diff --git a/graphics/graphics-core/api/restricted_current.txt b/graphics/graphics-core/api/restricted_current.txt
index 0b087b8..f265e02 100644
--- a/graphics/graphics-core/api/restricted_current.txt
+++ b/graphics/graphics-core/api/restricted_current.txt
@@ -292,9 +292,9 @@
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, androidx.graphics.surface.SurfaceControlCompat? newParent);
     method @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public androidx.graphics.surface.SurfaceControlCompat.Transaction reparent(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.view.AttachedSurfaceControl attachedSurfaceControl);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setAlpha(androidx.graphics.surface.SurfaceControlCompat surfaceControl, float alpha);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function0<kotlin.Unit>? releaseCallback);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer, optional androidx.hardware.SyncFenceCompat? fence);
-    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer buffer);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence, optional kotlin.jvm.functions.Function0<kotlin.Unit>? releaseCallback);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer, optional androidx.hardware.SyncFenceCompat? fence);
+    method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBuffer(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.hardware.HardwareBuffer? buffer);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setBufferTransform(androidx.graphics.surface.SurfaceControlCompat surfaceControl, int transformation);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setCrop(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Rect? crop);
     method public androidx.graphics.surface.SurfaceControlCompat.Transaction setDamageRegion(androidx.graphics.surface.SurfaceControlCompat surfaceControl, android.graphics.Region? region);
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
index a849854..ba81a87 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
@@ -1307,6 +1307,161 @@
             }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testFrontBufferClearAfterRender() {
+        var frontLatch = CountDownLatch(1)
+        val commitLatch = CountDownLatch(1)
+        val executor = Executors.newSingleThreadExecutor()
+        val callbacks = object : GLFrontBufferedRenderer.Callback<Any> {
+
+            private val mOrthoMatrix = FloatArray(16)
+            private val mProjectionMatrix = FloatArray(16)
+
+            // Should only render once
+            private var mShouldRender = true
+
+            override fun onDrawFrontBufferedLayer(
+                eglManager: EGLManager,
+                bufferInfo: BufferInfo,
+                transform: FloatArray,
+                param: Any
+            ) {
+                if (mShouldRender) {
+                    GLES20.glViewport(0, 0, bufferInfo.width, bufferInfo.height)
+                    Matrix.orthoM(
+                        mOrthoMatrix,
+                        0,
+                        0f,
+                        bufferInfo.width.toFloat(),
+                        0f,
+                        bufferInfo.height.toFloat(),
+                        -1f,
+                        1f
+                    )
+                    Matrix.multiplyMM(mProjectionMatrix, 0, mOrthoMatrix, 0, transform, 0)
+                    Rectangle().draw(mProjectionMatrix, Color.RED, 0f, 0f, 100f, 100f)
+                    mShouldRender = false
+                }
+            }
+
+            override fun onDrawMultiBufferedLayer(
+                eglManager: EGLManager,
+                bufferInfo: BufferInfo,
+                transform: FloatArray,
+                params: Collection<Any>
+            ) {
+                GLES20.glViewport(0, 0, bufferInfo.width, bufferInfo.height)
+                Matrix.orthoM(
+                    mOrthoMatrix,
+                    0,
+                    0f,
+                    bufferInfo.width.toFloat(),
+                    0f,
+                    bufferInfo.height.toFloat(),
+                    -1f,
+                    1f
+                )
+                Matrix.multiplyMM(mProjectionMatrix, 0, mOrthoMatrix, 0, transform, 0)
+                Rectangle().draw(mProjectionMatrix, Color.BLUE, 0f, 0f, 100f, 100f)
+            }
+
+            override fun onMultiBufferedLayerRenderComplete(
+                frontBufferedLayerSurfaceControl: SurfaceControlCompat,
+                transaction: SurfaceControlCompat.Transaction
+            ) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                    transaction.addTransactionCommittedListener(
+                        executor,
+                        object : SurfaceControlCompat.TransactionCommittedListener {
+                            override fun onTransactionCommitted() {
+                                commitLatch.countDown()
+                            }
+                        })
+                } else {
+                    commitLatch.countDown()
+                }
+            }
+
+            override fun onFrontBufferedLayerRenderComplete(
+                frontBufferedLayerSurfaceControl: SurfaceControlCompat,
+                transaction: SurfaceControlCompat.Transaction
+            ) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                    transaction.addTransactionCommittedListener(
+                        executor,
+                        object : SurfaceControlCompat.TransactionCommittedListener {
+                            override fun onTransactionCommitted() {
+                                frontLatch.countDown()
+                            }
+                        })
+                } else {
+                    frontLatch.countDown()
+                }
+            }
+        }
+        var renderer: GLFrontBufferedRenderer<Any>? = null
+        var surfaceView: SurfaceView? = null
+        try {
+            val scenario = ActivityScenario.launch(FrontBufferedRendererTestActivity::class.java)
+                .moveToState(Lifecycle.State.CREATED)
+                .onActivity {
+                    surfaceView = it.getSurfaceView()
+                    renderer = GLFrontBufferedRenderer(surfaceView!!, callbacks)
+                }
+
+            scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+                renderer?.renderFrontBufferedLayer(Any())
+                assertTrue(frontLatch.await(3000, TimeUnit.MILLISECONDS))
+                renderer?.commit()
+
+                assertTrue(commitLatch.await(3000, TimeUnit.MILLISECONDS))
+                // Contents should be cleared and the front buffer should be visible but transparent
+                frontLatch = CountDownLatch(1)
+                renderer?.renderFrontBufferedLayer(Any())
+            }
+            assertTrue(frontLatch.await(3000, TimeUnit.MILLISECONDS))
+
+            val coords = IntArray(2)
+            val width: Int
+            val height: Int
+            with(surfaceView!!) {
+                getLocationOnScreen(coords)
+                width = this.width
+                height = this.height
+            }
+
+            SurfaceControlUtils.validateOutput { bitmap ->
+                (Math.abs(
+                    Color.red(Color.BLUE) - Color.red(
+                        bitmap.getPixel(
+                            coords[0] + width / 2,
+                            coords[1] + height / 2
+                        )
+                    )
+                ) < 2) &&
+                    (Math.abs(
+                        Color.green(Color.BLUE) - Color.green(
+                            bitmap.getPixel(
+                                coords[0] + width / 2,
+                                coords[1] + height / 2
+                            )
+                        )
+                    ) < 2) &&
+                    (Math.abs(
+                        Color.blue(Color.BLUE) - Color.blue(
+                            bitmap.getPixel(
+                                coords[0] + width / 2,
+                                coords[1] + height / 2
+                            )
+                        )
+                    ) < 2)
+            }
+        } finally {
+            renderer.blockingRelease()
+        }
+    }
+
     @RequiresApi(Build.VERSION_CODES.Q)
     private fun GLFrontBufferedRenderer<*>?.blockingRelease(timeoutMillis: Long = 3000) {
         if (this != null) {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
index 861a7f9..4d9d4b8 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
@@ -1263,6 +1263,52 @@
     }
 
     @Test
+    fun testTransactionSetNullBuffer() {
+        val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
+            .moveToState(
+                Lifecycle.State.CREATED
+            ).onActivity {
+                val callback = object : SurfaceHolderCallback() {
+                    override fun surfaceCreated(sh: SurfaceHolder) {
+                        val scCompat = SurfaceControlCompat
+                            .Builder()
+                            .setParent(it.getSurfaceView())
+                            .setName("SurfaceControlCompatTest")
+                            .build()
+
+                        // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+                        val buffer = SurfaceControlUtils.getSolidBuffer(
+                            SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+                            SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+                            Color.BLUE
+                        )
+                        SurfaceControlCompat.Transaction()
+                            .setBuffer(scCompat, buffer)
+                            .setOpaque(
+                                scCompat,
+                                false
+                            )
+                            .commit()
+
+                        SurfaceControlCompat.Transaction()
+                            .setBuffer(scCompat, null)
+                            .commit()
+                    }
+                }
+
+                it.addSurface(it.mSurfaceView, callback)
+            }
+
+        scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
+            SurfaceControlUtils.validateOutput { bitmap ->
+                val coord = intArrayOf(0, 0)
+                it.mSurfaceView.getLocationOnScreen(coord)
+                Color.BLACK == bitmap.getPixel(coord[0], coord[1])
+            }
+        }
+    }
+
+    @Test
     fun testTransactionSetAlpha_0_0() {
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
             .moveToState(
diff --git a/graphics/graphics-core/src/main/cpp/graphics-core.cpp b/graphics/graphics-core/src/main/cpp/graphics-core.cpp
index d7c44a7..e19cb4d 100644
--- a/graphics/graphics-core/src/main/cpp/graphics-core.cpp
+++ b/graphics/graphics-core/src/main/cpp/graphics-core.cpp
@@ -291,8 +291,12 @@
     if (android_get_device_api_level() >= 29) {
         auto transaction = reinterpret_cast<ASurfaceTransaction *>(surfaceTransaction);
         auto sc = reinterpret_cast<ASurfaceControl *>(surfaceControl);
-        auto hardwareBuffer = AHardwareBuffer_fromHardwareBuffer(env, hBuffer);
-        auto fence_fd = dup_fence_fd(env, syncFence);
+        AHardwareBuffer* hardwareBuffer;
+        auto fence_fd = -1;
+        if (hBuffer) {
+            hardwareBuffer = AHardwareBuffer_fromHardwareBuffer(env, hBuffer);
+            fence_fd = dup_fence_fd(env, syncFence);
+        }
         ASurfaceTransaction_setBuffer(transaction, sc, hardwareBuffer, fence_fd);
     }
 }
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt
index 415fa3b..5b00170 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt
@@ -47,11 +47,14 @@
                 USAGE_COMPOSER_OVERLAY
 
         internal fun obtainHardwareBufferUsageFlags(): Long =
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            if (!UseCompatSurfaceControl &&
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                 UsageFlagsVerificationHelper.obtainUsageFlagsV33()
             } else {
                 BaseFlags
             }
+
+        internal const val UseCompatSurfaceControl = false
     }
 }
 
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
index 65a5a40..7ef9643 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
@@ -37,7 +37,6 @@
 import androidx.opengl.EGLExt.Companion.EGL_KHR_FENCE_SYNC
 import java.lang.IllegalStateException
 import java.util.concurrent.ConcurrentLinkedQueue
-import java.util.concurrent.Executors
 
 /**
  * Class responsible for supporting a "front buffered" rendering system. This allows for lower
@@ -86,12 +85,11 @@
             frontBufferedLayerSurfaceControl: SurfaceControlCompat,
             transaction: SurfaceControlCompat.Transaction
         ) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                transaction.addTransactionCommittedListener(mExecutor, mCommittedListener)
-            } else {
-                clearFrontBuffer()
-            }
             mFrontBufferSyncStrategy.isVisible = false
+
+            // Set a null buffer here so that the original front buffer's release callback
+            // gets invoked and we can clear the content of the front buffer
+            transaction.setBuffer(frontBufferedLayerSurfaceControl, null)
             callback.onMultiBufferedLayerRenderComplete(
                 frontBufferedLayerSurfaceControl,
                 transaction
@@ -111,19 +109,18 @@
         }
     }
 
-    private val mExecutor = Executors.newSingleThreadExecutor()
-
-    private val mCommittedListener = object : SurfaceControlCompat.TransactionCommittedListener {
-        override fun onTransactionCommitted() {
-            clearFrontBuffer()
+    // GLThread
+    private val mClearFrontBufferRunnable = Runnable {
+        mFrontLayerBuffer?.let { frameBuffer ->
+            if (mPendingClear) {
+                frameBuffer.makeCurrent()
+                GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
+                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+                mPendingClear = false
+            }
         }
     }
 
-    internal fun clearFrontBuffer() {
-        mFrontBufferedLayerRenderer?.clear()
-        mFrontBufferedRenderTarget?.requestRender()
-    }
-
     /**
      * [GLRenderer.EGLContextCallback]s used to release the corresponding [FrameBufferPool]
      * if the [GLRenderer] is torn down.
@@ -172,6 +169,19 @@
     }
 
     /**
+     * Flag to determine if a request to clear the front buffer content is pending. This should
+     * only be accessed on the GLThread
+     */
+    private var mPendingClear = false
+
+    /**
+     * Runnable exdecuted on the GLThread to flip the [mPendingClear] flag
+     */
+    private val mSetPendingClearRunnable = Runnable {
+        mPendingClear = true
+    }
+
+    /**
      * Runnable executed on the GLThread to update [FrontBufferSyncStrategy.isVisible] as well
      * as hide the SurfaceControl associated with the front buffered layer
      */
@@ -455,6 +465,7 @@
     fun commit() {
         if (isValid()) {
             mSegments.add(mActiveSegment.release())
+            mGLRenderer.execute(mSetPendingClearRunnable)
             mMultiBufferedLayerRenderTarget?.requestRender()
         } else {
             Log.w(
@@ -560,7 +571,6 @@
             mGLRenderer.stop(false)
         }
 
-        mExecutor.shutdown()
         mIsReleased = true
     }
 
@@ -601,6 +611,11 @@
 
                 @WorkerThread
                 override fun onDraw(eglManager: EGLManager) {
+                    if (mPendingClear) {
+                        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
+                        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+                        mPendingClear = false
+                    }
                     bufferInfo.apply {
                         this.width = mParentRenderLayer.getBufferWidth()
                         this.height = mParentRenderLayer.getBufferHeight()
@@ -628,7 +643,9 @@
                             frontBufferedLayerSurfaceControl,
                             frameBuffer.hardwareBuffer,
                             syncFenceCompat
-                        )
+                        ) {
+                            mGLRenderer.execute(mClearFrontBufferRunnable)
+                        }
                         .setVisibility(frontBufferedLayerSurfaceControl, true)
                     val inverseTransform = mParentRenderLayer.getInverseBufferTransform()
                     if (inverseTransform != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt
index 5b51639..daa5636 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlCompat.kt
@@ -26,6 +26,7 @@
 import android.view.SurfaceView
 import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
+import androidx.graphics.lowlatency.FrontBufferUtils
 import androidx.hardware.SyncFenceCompat
 import java.util.concurrent.Executor
 
@@ -164,12 +165,15 @@
 
         internal companion object {
             @RequiresApi(Build.VERSION_CODES.Q)
-            fun createImpl(): SurfaceControlImpl.Builder =
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            fun createImpl(): SurfaceControlImpl.Builder {
+                val usePlatformTransaction = !FrontBufferUtils.UseCompatSurfaceControl &&
+                        Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                return if (usePlatformTransaction) {
                     SurfaceControlVerificationHelper.createBuilderV33()
                 } else {
                     SurfaceControlVerificationHelper.createBuilderV29()
                 }
+            }
         }
     }
 
@@ -298,7 +302,8 @@
          *
          * @param surfaceControl Target [SurfaceControlCompat] to configure the provided buffer.
          * @param buffer [HardwareBuffer] instance to be rendered by the [SurfaceControlCompat]
-         * instance.
+         * instance. Use null to remove the current buffer that was previously configured on
+         * this [SurfaceControlCompat] instance.
          * @param fence Optional [SyncFenceCompat] that serves as the presentation fence. If set,
          * the [SurfaceControlCompat.Transaction] will not apply until the fence signals.
          * @param releaseCallback Optional callback invoked when the buffer is ready for re-use
@@ -307,7 +312,7 @@
         @JvmOverloads
         fun setBuffer(
             surfaceControl: SurfaceControlCompat,
-            buffer: HardwareBuffer,
+            buffer: HardwareBuffer?,
             fence: SyncFenceCompat? = null,
             releaseCallback: (() -> Unit)? = null
         ): Transaction {
@@ -503,12 +508,15 @@
 
         internal companion object {
             @RequiresApi(Build.VERSION_CODES.Q)
-            fun createImpl(): SurfaceControlImpl.Transaction =
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            fun createImpl(): SurfaceControlImpl.Transaction {
+                val usePlatformSurfaceControl = !FrontBufferUtils.UseCompatSurfaceControl &&
+                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                return if (usePlatformSurfaceControl) {
                     SurfaceControlVerificationHelper.createTransactionV33()
                 } else {
                     SurfaceControlVerificationHelper.createTransactionV29()
                 }
+            }
         }
     }
 }
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlImpl.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlImpl.kt
index ec56717..9d8af62 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlImpl.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlImpl.kt
@@ -175,7 +175,7 @@
          */
         fun setBuffer(
             surfaceControl: SurfaceControlImpl,
-            buffer: HardwareBuffer,
+            buffer: HardwareBuffer?,
             fence: SyncFenceImpl? = null,
             releaseCallback: (() -> Unit)? = null
         ): Transaction
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV29.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV29.kt
index 010d20c..c1e1701 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV29.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV29.kt
@@ -16,6 +16,7 @@
 
 package androidx.graphics.surface
 
+import android.annotation.SuppressLint
 import android.graphics.Rect
 import android.graphics.Region
 import android.hardware.HardwareBuffer
@@ -24,6 +25,7 @@
 import android.view.SurfaceView
 import androidx.annotation.RequiresApi
 import androidx.graphics.lowlatency.BufferTransformHintResolver.Companion.UNKNOWN_TRANSFORM
+import androidx.graphics.lowlatency.FrontBufferUtils
 import androidx.hardware.SyncFenceImpl
 import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_ROTATE_270
 import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_ROTATE_90
@@ -206,26 +208,29 @@
          */
         override fun setBuffer(
             surfaceControl: SurfaceControlImpl,
-            buffer: HardwareBuffer,
+            buffer: HardwareBuffer?,
             fence: SyncFenceImpl?,
             releaseCallback: (() -> Unit)?
         ): SurfaceControlImpl.Transaction {
-            // we have a previous mapping in the same transaction, invoke callback
-            val data = BufferData(
-                width = buffer.width,
-                height = buffer.height,
-                releaseCallback = releaseCallback
-            )
-            uncommittedBufferCallbackMap.put(surfaceControl, data)?.releaseCallback?.invoke()
+            if (buffer != null) {
+                // we have a previous mapping in the same transaction, invoke callback
+                val data = BufferData(
+                    width = buffer.width,
+                    height = buffer.height,
+                    releaseCallback = releaseCallback
+                )
+                uncommittedBufferCallbackMap.put(surfaceControl, data)?.releaseCallback?.invoke()
+            }
 
+            val targetBuffer = buffer ?: PlaceholderBuffer
             // Ensure if we have a null value, we default to the default value for SyncFence
             // argument to prevent null pointer dereference
             if (fence == null) {
-                transaction.setBuffer(surfaceControl.asWrapperSurfaceControl(), buffer)
+                transaction.setBuffer(surfaceControl.asWrapperSurfaceControl(), targetBuffer)
             } else {
                 transaction.setBuffer(
                     surfaceControl.asWrapperSurfaceControl(),
-                    buffer,
+                    targetBuffer,
                     fence.asSyncFenceCompat()
                 )
             }
@@ -391,6 +396,22 @@
 
     private companion object {
 
+        // Certain Android platform versions have inconsistent behavior when it comes to
+        // configuring a null HardwareBuffer. More specifically Android Q appears to crash
+        // and restart emulator instances.
+        // Additionally the SDK setBuffer API hides the buffer from the display if it is
+        // null but the NDK API does not and persists the buffer contents on screen.
+        // So instead change the buffer to a 1 x 1 placeholder to achieve a similar effect
+        // with more consistent behavior.
+        @SuppressLint("WrongConstant")
+        val PlaceholderBuffer = HardwareBuffer.create(
+            1,
+            1,
+            HardwareBuffer.RGBA_8888,
+            1,
+            FrontBufferUtils.BaseFlags
+        )
+
         fun SurfaceControlImpl.asWrapperSurfaceControl(): SurfaceControlWrapper =
             if (this is SurfaceControlV29) {
                 surfaceControl
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV33.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV33.kt
index 69e3f4a0..37311f5 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV33.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlV33.kt
@@ -120,7 +120,7 @@
          */
         override fun setBuffer(
             surfaceControl: SurfaceControlImpl,
-            buffer: HardwareBuffer,
+            buffer: HardwareBuffer?,
             fence: SyncFenceImpl?,
             releaseCallback: (() -> Unit)?
         ): Transaction {
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
index 6b48262..1ad3054 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/surface/SurfaceControlWrapper.kt
@@ -69,7 +69,7 @@
         external fun nSetBuffer(
             surfaceTransaction: Long,
             surfaceControl: Long,
-            hardwareBuffer: HardwareBuffer,
+            hardwareBuffer: HardwareBuffer?,
             acquireFieldFd: SyncFenceV19
         )
 
@@ -288,7 +288,7 @@
         @JvmOverloads
         fun setBuffer(
             surfaceControl: SurfaceControlWrapper,
-            hardwareBuffer: HardwareBuffer,
+            hardwareBuffer: HardwareBuffer?,
             syncFence: SyncFenceV19 = SyncFenceV19(-1)
         ): Transaction {
             JniBindings.nSetBuffer(
diff --git a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt
index 5465eb8..29bc952 100644
--- a/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt
+++ b/graphics/graphics-core/src/main/java/androidx/hardware/SyncFenceCompat.kt
@@ -21,6 +21,7 @@
 import android.opengl.GLES20
 import android.os.Build
 import androidx.annotation.RequiresApi
+import androidx.graphics.lowlatency.FrontBufferUtils
 import androidx.opengl.EGLExt
 import androidx.graphics.surface.SurfaceControlCompat
 import androidx.opengl.EGLSyncKHR
@@ -46,7 +47,9 @@
          */
         @JvmStatic
         fun createNativeSyncFence(): SyncFenceCompat {
-            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            val usePlatformSyncFence = !FrontBufferUtils.UseCompatSurfaceControl &&
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+            return if (usePlatformSyncFence) {
                 SyncFenceCompatVerificationHelper.createSyncFenceCompatV33()
             } else {
                 val display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
diff --git a/graphics/integration-tests/testapp-compose/build.gradle b/graphics/integration-tests/testapp-compose/build.gradle
index 840935f..5e3f520 100644
--- a/graphics/integration-tests/testapp-compose/build.gradle
+++ b/graphics/integration-tests/testapp-compose/build.gradle
@@ -32,7 +32,7 @@
     implementation("androidx.appcompat:appcompat:1.5.1")
     implementation("androidx.compose.foundation:foundation:1.3.1")
     implementation("androidx.compose.foundation:foundation-layout:1.3.1")
-    implementation("androidx.compose.material:material:1.3.1")
+    implementation("androidx.compose.material:material:1.4.0")
     implementation("androidx.compose.runtime:runtime:1.3.3")
     implementation("androidx.compose.ui:ui:1.3.3")
     implementation("androidx.core:core-ktx:1.9.0")
diff --git a/health/connect/connect-client-proto/build.gradle b/health/connect/connect-client-proto/build.gradle
index 18c9dc8..0458446 100644
--- a/health/connect/connect-client-proto/build.gradle
+++ b/health/connect/connect-client-proto/build.gradle
@@ -40,7 +40,7 @@
     }
     generateProtoTasks {
         ofSourceSet("main").each { task ->
-            sourceSets.main.java.srcDir(task)
+            project.sourceSets.main.java.srcDir(task)
         }
         all().each { task ->
             task.builtins {
diff --git a/health/connect/connect-client/lint-baseline.xml b/health/connect/connect-client/lint-baseline.xml
index 075983f..c5e5522 100644
--- a/health/connect/connect-client/lint-baseline.xml
+++ b/health/connect/connect-client/lint-baseline.xml
@@ -157,6 +157,96 @@
     <issue
         id="RequireUnstableAidlAnnotation"
         message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable AggregateDataRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/AggregateDataRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable AggregateDataResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/AggregateDataResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ChangesEvent;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/changes/ChangesEvent.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable DeleteDataRangeRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/DeleteDataRangeRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable DeleteDataRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/DeleteDataRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ErrorStatus;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/error/ErrorStatus.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetChangesRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/GetChangesRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetChangesResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/GetChangesResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetChangesTokenRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/GetChangesTokenRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetChangesTokenResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/GetChangesTokenResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="oneway interface IAggregateDataCallback {"
         errorLine2="^">
         <location
@@ -371,6 +461,123 @@
     </issue>
 
     <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable InsertDataResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/InsertDataResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable Permission;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/permission/Permission.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ReadDataRangeRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/ReadDataRangeRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ReadDataRangeResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/ReadDataRangeResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ReadDataRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/ReadDataRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ReadDataResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/ReadDataResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ReadExerciseRouteRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/ReadExerciseRouteRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ReadExerciseRouteResponse;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/response/ReadExerciseRouteResponse.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable RegisterForDataNotificationsRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/RegisterForDataNotificationsRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable RequestContext;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/RequestContext.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable UnregisterFromDataNotificationsRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/UnregisterFromDataNotificationsRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable UpsertDataRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/UpsertDataRequest.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable UpsertExerciseRouteRequest;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/health/platform/client/request/UpsertExerciseRouteRequest.aidl"/>
+    </issue>
+
+    <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public ClientConfiguration(String apiClientName, String servicePackageName, String bindAction) {"
diff --git a/hilt/hilt-navigation-compose/build.gradle b/hilt/hilt-navigation-compose/build.gradle
index 3cc6e95..86b72a7 100644
--- a/hilt/hilt-navigation-compose/build.gradle
+++ b/hilt/hilt-navigation-compose/build.gradle
@@ -39,7 +39,7 @@
     api projectOrArtifact(":hilt:hilt-navigation")
     api("androidx.compose.runtime:runtime:1.0.1")
     api("androidx.compose.ui:ui:1.0.1")
-    api(project(":lifecycle:lifecycle-viewmodel-compose"))
+    api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
     api("androidx.navigation:navigation-compose:2.5.1")
 
     androidTestImplementation(libs.testExtJunit)
diff --git a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
index 50d21ab..f05b834 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -71,8 +71,7 @@
                 IgnoreClassLevelDetector.ISSUE,
                 ExperimentalPropertyAnnotationDetector.ISSUE,
                 BanRestrictToTestsScope.ISSUE,
-                // Temporarily disable AIDL lint check due to b/278871118.
-                // UnstableAidlAnnotationDetector.ISSUE,
+                UnstableAidlAnnotationDetector.ISSUE,
                 // MissingJvmDefaultWithCompatibilityDetector is intentionally left out of the
                 // registry, see comments on the class for more details.
                 BanVisibleForTestingParams.ISSUE,
diff --git a/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt b/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt
index fb9e2ef..637bee0 100644
--- a/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt
@@ -21,6 +21,7 @@
 import androidx.com.android.tools.idea.lang.aidl.psi.AidlAnnotationElement
 import androidx.com.android.tools.idea.lang.aidl.psi.AidlDeclaration
 import androidx.com.android.tools.idea.lang.aidl.psi.AidlInterfaceDeclaration
+import androidx.com.android.tools.idea.lang.aidl.psi.AidlParcelableDeclaration
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Context
 import com.android.tools.lint.detector.api.Implementation
@@ -41,8 +42,20 @@
 
 class UnstableAidlAnnotationDetector : AidlDefinitionDetector() {
 
+    override fun visitAidlParcelableDeclaration(context: Context, node: AidlParcelableDeclaration) {
+        checkDeclaration(context, node, node.annotationElementList)
+    }
+
     override fun visitAidlInterfaceDeclaration(context: Context, node: AidlInterfaceDeclaration) {
-        var passthruAnnotations: List<AidlAnnotationElement> = node.annotationElementList.filter {
+        checkDeclaration(context, node, node.annotationElementList)
+    }
+
+    private fun checkDeclaration(
+        context: Context,
+        node: AidlDeclaration,
+        annotations: List<AidlAnnotationElement>
+    ) {
+        var passthruAnnotations: List<AidlAnnotationElement> = annotations.filter {
                 annotationElement ->
             annotationElement.qualifiedName.name.equals(JAVA_PASSTHROUGH)
         }
diff --git a/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt b/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
index 0e552a9..a365b00 100644
--- a/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
@@ -23,6 +23,7 @@
 import androidx.com.android.tools.idea.lang.aidl.psi.AidlInterfaceDeclaration
 import androidx.com.android.tools.idea.lang.aidl.psi.AidlMethodDeclaration
 import androidx.com.android.tools.idea.lang.aidl.psi.AidlParcelableDeclaration
+import androidx.com.android.tools.idea.lang.aidl.psi.AidlUnionDeclaration
 import com.android.tools.lint.detector.api.Context
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Location
@@ -72,10 +73,19 @@
         when (aidlDeclaration) {
             is AidlInterfaceDeclaration ->
                 visitAidlInterfaceDeclaration(context, aidlDeclaration)
-            is AidlMethodDeclaration ->
-                visitAidlMethodDeclaration(context, aidlDeclaration)
             is AidlParcelableDeclaration ->
                 visitAidlParcelableDeclaration(context, aidlDeclaration)
+            is AidlUnionDeclaration -> {
+                listOf(
+                    aidlDeclaration.interfaceDeclarationList,
+                    aidlDeclaration.parcelableDeclarationList,
+                    aidlDeclaration.unionDeclarationList
+                ).forEach { declarationList ->
+                    declarationList.forEach { declaration ->
+                        visitAidlDeclaration(context, declaration)
+                    }
+                }
+            }
         }
     }
 
diff --git a/lint-checks/src/main/java/androidx/com/android/tools/idea/lang/aidl/AidlLanguage.java b/lint-checks/src/main/java/androidx/com/android/tools/idea/lang/aidl/AidlLanguage.java
index e40a8f1..7c2d47d 100644
--- a/lint-checks/src/main/java/androidx/com/android/tools/idea/lang/aidl/AidlLanguage.java
+++ b/lint-checks/src/main/java/androidx/com/android/tools/idea/lang/aidl/AidlLanguage.java
@@ -25,10 +25,9 @@
   public static final Language INSTANCE = getOrCreate();
 
   private static Language getOrCreate() {
-    for(Language lang : Language.getRegisteredLanguages()) {
-      if (ID.equals(lang.getID())) {
-        return lang;
-      }
+    Language lang = Language.findLanguageByID(ID);
+    if (lang != null) {
+      return lang;
     }
     return new AidlLanguage();
   }
diff --git a/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt
index e220f0f..8789d3b 100644
--- a/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt
@@ -58,7 +58,31 @@
     }
 
     @Test
-    fun wrongAnnotation() {
+    fun wrongAnnotationParcelable() {
+        val input = aidl(
+            "android/support/v4/os/ResultReceiver.aidl",
+            """
+                package android.support.v4.os;
+
+                @JavaOnlyStableParcelable
+                parcelable Receiver;
+            """.trimIndent()
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+            src/main/aidl/android/support/v4/os/ResultReceiver.aidl:4: Error: Unstable AIDL files must be annotated with @RequiresOptIn marker [RequireUnstableAidlAnnotation]
+            parcelable Receiver;
+            ~~~~~~~~~~~~~~~~~~~~
+            1 errors, 0 warnings
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(input).expect(expected)
+    }
+
+    @Test
+    fun wrongAnnotationInterface() {
         val input = arrayOf(
             java(
                 "src/androidx/core/MyAnnotation.java",
diff --git a/media/media/lint-baseline.xml b/media/media/lint-baseline.xml
index 78efc10..7760b0e 100644
--- a/media/media/lint-baseline.xml
+++ b/media/media/lint-baseline.xml
@@ -3,7 +3,7 @@
 
     <issue
         id="RequireUnstableAidlAnnotation"
-        message="Unstable AIDL files must be annotated with @RequiresOptIn marker"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="oneway interface IMediaControllerCallback {"
         errorLine2="^">
         <location
@@ -12,7 +12,7 @@
 
     <issue
         id="RequireUnstableAidlAnnotation"
-        message="Unstable AIDL files must be annotated with @RequiresOptIn marker"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="interface IMediaSession {"
         errorLine2="^">
         <location
@@ -20,6 +20,78 @@
     </issue>
 
     <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable MediaDescriptionCompat;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/MediaDescriptionCompat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable MediaMetadataCompat;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable MediaSessionCompat.Token;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable MediaSessionCompat.QueueItem;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable MediaSessionCompat.ResultReceiverWrapper;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable ParcelableVolumeInfo;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable PlaybackStateCompat;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/session/PlaybackStateCompat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="@JavaOnlyStableParcelable parcelable RatingCompat;"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/v4/media/RatingCompat.aidl"/>
+    </issue>
+
+    <issue
         id="LambdaLast"
         message="Functional interface parameters (such as parameter 1, &quot;listener&quot;, in androidx.media.AudioFocusRequestCompat.Builder.setOnAudioFocusChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions"
         errorLine1="                @NonNull OnAudioFocusChangeListener listener, @NonNull Handler handler) {"
diff --git a/media2/media2-session/version-compat-tests/common/lint-baseline.xml b/media2/media2-session/version-compat-tests/common/lint-baseline.xml
index 66ef781..90b9f4e 100644
--- a/media2/media2-session/version-compat-tests/common/lint-baseline.xml
+++ b/media2/media2-session/version-compat-tests/common/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-beta03" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.0.0-beta03">
+<issues format="6" by="lint 8.1.0-alpha11" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-alpha11)" variant="all" version="8.1.0-alpha11">
 
     <issue
         id="BanThreadSleep"
diff --git a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
index 50470e1..f3ae0ae 100644
--- a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -25,12 +25,16 @@
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
@@ -42,6 +46,8 @@
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource
 import androidx.paging.TestPagingSource
+import androidx.paging.cachedIn
+import androidx.paging.localLoadStatesOf
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
@@ -49,6 +55,8 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Assert.assertFalse
 import org.junit.Ignore
 import org.junit.Rule
@@ -936,6 +944,223 @@
         )
     }
 
+    @Test
+    fun cachedData() {
+        val flow = createPager().flow.cachedIn(TestScope(UnconfinedTestDispatcher()))
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        val restorationTester = StateRestorationTester(rule)
+        val dispatcher = StandardTestDispatcher()
+        var maxItem by mutableStateOf(6)
+        restorationTester.setContent {
+            lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
+            // load until we get 6 items
+            for (i in 0 until minOf(lazyPagingItems.itemCount, maxItem - 1)) {
+                lazyPagingItems[i]
+            }
+        }
+
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            lazyPagingItems.itemCount == maxItem
+        }
+
+        // we don't advance load dispatchers after restoration to prevent loads
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        // ensure we received the cached data
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemCount).isEqualTo(6)
+            assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(listOf(1, 2, 3, 4, 5, 6))
+        }
+
+        // try to load more data
+        maxItem = 7
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            lazyPagingItems.itemCount == maxItem
+        }
+
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemCount).isEqualTo(7)
+            assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(listOf(1, 2, 3, 4, 5, 6, 7))
+        }
+    }
+
+    @Test
+    fun cachedEmptyData() {
+        val flow = createPager().flow.cachedIn(TestScope(UnconfinedTestDispatcher()))
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        val restorationTester = StateRestorationTester(rule)
+        val dispatcher = StandardTestDispatcher()
+        restorationTester.setContent {
+            lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
+            // load until we get 6 items
+            for (i in 0 until minOf(lazyPagingItems.itemCount, 5)) {
+                lazyPagingItems[i]
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
+        }
+
+        // we don't let dispatchers load and directly restore state
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        // empty cache
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemCount).isEqualTo(0)
+            assertThat(lazyPagingItems.itemSnapshotList).isEmpty()
+        }
+
+        // check that new data can be loaded in properly
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            lazyPagingItems.itemCount == 6
+        }
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemCount).isEqualTo(6)
+            assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(listOf(1, 2, 3, 4, 5, 6))
+        }
+    }
+
+    @Test
+    fun cachedData_withPlaceholders() {
+        val flow = createPagerWithPlaceholders().flow
+            .cachedIn(TestScope(UnconfinedTestDispatcher()))
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        val restorationTester = StateRestorationTester(rule)
+        val dispatcher = StandardTestDispatcher()
+        var maxItem by mutableStateOf(6)
+        var loadedMaxItem = false
+        restorationTester.setContent {
+            lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
+            // load until we get 6 items
+            for (i in 0 until minOf(lazyPagingItems.itemCount, maxItem)) {
+                lazyPagingItems[i]
+                loadedMaxItem = lazyPagingItems.peek(i) == maxItem
+            }
+        }
+
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            loadedMaxItem
+        }
+
+        // we don't advance load dispatchers after restoration to prevent loads
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        // ensure we received the cached data + placeholders
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemCount).isEqualTo(10)
+            assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(
+                listOf(1, 2, 3, 4, 5, 6, null, null, null, null)
+            )
+        }
+
+        // try to load more data
+        maxItem = 7
+        loadedMaxItem = false
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            loadedMaxItem
+        }
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemSnapshotList).isEqualTo(
+                listOf(1, 2, 3, 4, 5, 6, 7, null, null, null)
+            )
+        }
+    }
+
+    @Test
+    fun cachedData_loadStates() {
+        val flow = createPager().flow.cachedIn(TestScope(UnconfinedTestDispatcher()))
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        val restorationTester = StateRestorationTester(rule)
+        val dispatcher = StandardTestDispatcher()
+        var maxItem by mutableStateOf(4)
+        restorationTester.setContent {
+            lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
+            // load until we get 6 items
+            for (i in 0 until minOf(lazyPagingItems.itemCount, maxItem - 1)) {
+                lazyPagingItems[i]
+            }
+        }
+
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            lazyPagingItems.itemCount == maxItem
+        }
+
+        assertThat(lazyPagingItems.loadState).isEqualTo(
+            localLoadStatesOf(
+                refreshLocal = LoadState.NotLoading(false),
+                prependLocal = LoadState.NotLoading(true)
+            )
+        )
+
+        // we don't advance load dispatchers after restoration to prevent loads
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        // ensure we received the cached loadstates
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.loadState).isEqualTo(
+                localLoadStatesOf(
+                    refreshLocal = LoadState.NotLoading(false),
+                    prependLocal = LoadState.NotLoading(true)
+                )
+            )
+        }
+    }
+
+    @Test
+    fun cachedData_restoresListState() {
+        val flow = createPager().flow.cachedIn(TestScope(UnconfinedTestDispatcher()))
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        lateinit var state: LazyListState
+        val restorationTester = StateRestorationTester(rule)
+        val dispatcher = StandardTestDispatcher()
+        restorationTester.setContent {
+            state = rememberLazyListState()
+            lazyPagingItems = flow.collectAsLazyPagingItems(dispatcher)
+            // load until we get 6 items
+            for (i in 0 until minOf(lazyPagingItems.itemCount, 5)) {
+                lazyPagingItems[i]
+            }
+            LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
+                // Static items are what triggers scroll state to erroneously reset to 0
+                item {
+                    Content("header")
+                }
+                items(
+                    lazyPagingItems.itemCount, lazyPagingItems.itemKey()
+                ) { index ->
+                    val item = lazyPagingItems[index]
+                    Content("$item")
+                }
+            }
+        }
+
+        rule.waitUntil {
+            dispatcher.scheduler.advanceUntilIdle() // let items load
+            lazyPagingItems.itemCount == 6
+        }
+
+        rule.runOnIdle {
+            runBlocking { state.scrollToItem(3) }
+            assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+        }
+
+        // we don't advance load dispatchers after restoration to prevent loads
+        restorationTester.emulateSavedInstanceStateRestore()
+
+        // ensure we received the cached data and preserved scroll state
+        rule.runOnIdle {
+            assertThat(lazyPagingItems.itemCount).isEqualTo(6)
+            assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+        }
+    }
+
     @Composable
     private fun Content(tag: String) {
         Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index ee5a63b..81a6901 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -40,6 +40,7 @@
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.withContext
@@ -61,22 +62,6 @@
 ) {
     private val mainDispatcher = Dispatchers.Main
 
-    /**
-     * Contains the immutable [ItemSnapshotList] of currently presented items, including any
-     * placeholders if they are enabled.
-     * Note that similarly to [peek] accessing the items in a list will not trigger any loads.
-     * Use [get] to achieve such behavior.
-     */
-    var itemSnapshotList by mutableStateOf(
-        ItemSnapshotList<T>(0, 0, emptyList())
-    )
-        private set
-
-    /**
-     * The number of items which can be accessed.
-     */
-    val itemCount: Int get() = itemSnapshotList.size
-
     private val differCallback: DifferCallback = object : DifferCallback {
         override fun onChanged(position: Int, count: Int) {
             if (count > 0) {
@@ -97,9 +82,17 @@
         }
     }
 
+    /**
+     * If the [flow] is a SharedFlow, it is expected to be the flow returned by from
+     * pager.flow.cachedIn(scope) which could contain a cached PagingData. We pass the cached
+     * PagingData to the differ so that if the PagingData contains cached data, the differ can be
+     * initialized with the data prior to collection on pager.
+     */
     private val pagingDataDiffer = object : PagingDataDiffer<T>(
         differCallback = differCallback,
-        mainContext = mainDispatcher
+        mainContext = mainDispatcher,
+        cachedPagingData =
+            if (flow is SharedFlow<PagingData<T>>) flow.replayCache.firstOrNull() else null
     ) {
         override suspend fun presentNewList(
             previousList: NullPaddedList<T>,
@@ -113,6 +106,22 @@
         }
     }
 
+    /**
+     * Contains the immutable [ItemSnapshotList] of currently presented items, including any
+     * placeholders if they are enabled.
+     * Note that similarly to [peek] accessing the items in a list will not trigger any loads.
+     * Use [get] to achieve such behavior.
+     */
+    var itemSnapshotList by mutableStateOf(
+        pagingDataDiffer.snapshot()
+    )
+        private set
+
+    /**
+     * The number of items which can be accessed.
+     */
+    val itemCount: Int get() = itemSnapshotList.size
+
     private fun updateItemSnapshotList() {
         itemSnapshotList = pagingDataDiffer.snapshot()
     }
@@ -303,7 +312,7 @@
  * [itemContentType] helper functions.
  */
 @Deprecated(
-    message = "Call LazyListScope.items directly with LazyPagingItems #itemKey and" +
+    message = "Call LazyListScope.items directly with LazyPagingItems #itemKey and " +
         "#itemContentType helper functions.",
     replaceWith = ReplaceWith(
         expression = """items(
@@ -360,8 +369,8 @@
  * with LazyPagingItems #itemKey and #itemContentType helper functions.
  */
 @Deprecated(
-    message = "Deprecating support for indexed keys on non-null items as it is susceptible to" +
-        "errors when items indices shift due to prepends. Call LazyListScope.items directly" +
+    message = "Deprecating support for indexed keys on non-null items as it is susceptible to " +
+        "errors when items indices shift due to prepends. Call LazyListScope.items directly " +
         "with LazyPagingItems #itemKey and #itemContentType helper functions.",
     replaceWith = ReplaceWith(
         expression = """items(
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index e17b5c1..fec6fdf 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,6 +25,6 @@
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=9971607
+androidx.playground.snapshotBuildId=10041883
 androidx.playground.metalavaBuildId=10009114
 androidx.studio.type=playground
diff --git a/preference/preference/res/values-fr/strings.xml b/preference/preference/res/values-fr/strings.xml
index 2b8a26a..1348f8c 100644
--- a/preference/preference/res/values-fr/strings.xml
+++ b/preference/preference/res/values-fr/strings.xml
@@ -3,7 +3,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="v7_preference_on" msgid="89551595707643515">"ACTIVÉ"</string>
     <string name="v7_preference_off" msgid="3140233346420563315">"DÉSACTIVÉ"</string>
-    <string name="expand_button_title" msgid="2427401033573778270">"Paramètres avancés"</string>
+    <string name="expand_button_title" msgid="2427401033573778270">"Avancé"</string>
     <string name="summary_collapsed_preference_list" msgid="9167775378838880170">"<xliff:g id="CURRENT_ITEMS">%1$s</xliff:g>, <xliff:g id="ADDED_ITEMS">%2$s</xliff:g>"</string>
     <string name="copy" msgid="6083905920877235314">"Copier"</string>
     <string name="preference_copied" msgid="6685851473431805375">"\"<xliff:g id="SUMMARY">%1$s</xliff:g>\" copié dans le presse-papier."</string>
diff --git a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/AbstractDiffTest.kt b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/AbstractDiffTest.kt
index bd362a5..6f1a95d 100644
--- a/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/AbstractDiffTest.kt
+++ b/privacysandbox/tools/tools-testing/src/main/java/androidx/privacysandbox/tools/testing/AbstractDiffTest.kt
@@ -58,8 +58,9 @@
 
     @Test
     fun generatedSourcesHaveExpectedContents() {
+        val expectedSourcesPath = "src/test/test-data/$subdirectoryName/output"
         val expectedKotlinSources =
-            loadSourcesFromDirectory(File("src/test/test-data/$subdirectoryName/output"))
+            loadSourcesFromDirectory(File(expectedSourcesPath))
 
         val expectedRelativePaths =
             expectedKotlinSources.map(Source::relativePath) + relativePathsToExpectedAidlClasses
@@ -68,9 +69,14 @@
 
         val actualRelativePathMap = generatedSources.associateBy(Source::relativePath)
         for (expectedKotlinSource in expectedKotlinSources) {
+            val outputFilePath = "$outputDir/${expectedKotlinSource.relativePath}"
+            val goldenPath = System.getProperty("user.dir") + "/" + expectedSourcesPath + "/" +
+                expectedKotlinSource.relativePath
             Truth.assertWithMessage(
                 "Contents of generated file ${expectedKotlinSource.relativePath} don't " +
-                    "match golden. Here's the path to generated sources: $outputDir"
+                    "match golden.\n" +
+                    "Approval command:\n" +
+                    "cp $outputFilePath $goldenPath"
             ).that(actualRelativePathMap[expectedKotlinSource.relativePath]?.contents)
                 .isEqualTo(expectedKotlinSource.contents)
         }
diff --git a/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileVerifier.java b/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileVerifier.java
index 123541a8..ac64841 100644
--- a/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileVerifier.java
+++ b/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/ProfileVerifier.java
@@ -425,9 +425,15 @@
 
         /**
          * Indicates that a profile is installed and the app will be compiled with it later when
-         * background dex optimization runs. This is the result of installation through profile
+         * background dex optimization runs (i.e when the device is in idle
+         * and connected to the power). This is the result of installation through profile
          * installer. When the profile is compiled, the result code will change to
-         * {@link #RESULT_CODE_COMPILED_WITH_PROFILE}.
+         * {@link #RESULT_CODE_COMPILED_WITH_PROFILE}. Note that to test that the app is compiled
+         * with the installed profile, the background dex optimization can be forced through the
+         * following adb shell command:
+         * ```
+         * adb shell cmd package compile -f -m speed-profile <PACKAGE_NAME>
+         * ```
          */
         public static final int RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION = 2;
 
diff --git a/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt b/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
index 8720b6e..7d9085e 100644
--- a/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
+++ b/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
@@ -18,6 +18,8 @@
 
 import androidx.testutils.gradle.ProjectSetupRule
 import com.google.common.truth.Expect
+import java.io.File
+import java.nio.file.Files
 import org.gradle.testkit.runner.BuildResult
 import org.gradle.testkit.runner.GradleRunner
 import org.gradle.testkit.runner.TaskOutcome
@@ -26,11 +28,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
-import java.io.File
-import java.nio.file.Files
-import javax.xml.parsers.DocumentBuilderFactory
-import javax.xml.xpath.XPathConstants
-import javax.xml.xpath.XPathFactory
 
 @RunWith(Parameterized::class)
 class RoomIncrementalAnnotationProcessingTest(
@@ -110,27 +107,7 @@
      * prebuilts (SNAPSHOT).
      */
     private val roomVersion by lazy {
-        val metadataFile = File(projectSetup.props.tipOfTreeMavenRepoPath).resolve(
-            "androidx/room/room-compiler/maven-metadata.xml"
-        )
-        check(metadataFile.exists()) {
-            "Cannot find room metadata file in ${metadataFile.absolutePath}"
-        }
-        check(metadataFile.isFile) {
-            "Metadata file should be a file but it is not."
-        }
-        val xmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
-            .parse(metadataFile)
-        val latestVersionNode = XPathFactory.newInstance().newXPath()
-            .compile("/metadata/versioning/latest").evaluate(
-                xmlDoc, XPathConstants.STRING
-            )
-        check(latestVersionNode is String) {
-            """Unexpected node for latest version:
-                $latestVersionNode / ${latestVersionNode::class.java}
-            """.trimIndent()
-        }
-        latestVersionNode
+        projectSetup.getLibraryLatestVersionInLocalRepo("androidx/room/room-compiler")
     }
 
     @Before
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 3bd5882..6556553 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -76,8 +76,7 @@
     implementation(project(":room:room-runtime"))
     implementation(project(":room:room-paging"))
     implementation(projectOrArtifact(":arch:core:core-runtime"))
-    implementation(projectOrArtifact(":lifecycle:lifecycle-livedata"))
-    implementation(projectOrArtifact(":lifecycle:lifecycle-livedata-ktx"))
+    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesAndroid)
     implementation(libs.multidex)
@@ -93,7 +92,7 @@
             project(path: ":room:room-compiler", configuration: "shadowAndImplementation")
     )
     androidTestImplementation(projectOrArtifact(":arch:core:core-runtime"))
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-livedata-ktx"))
+    androidTestImplementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner) {
@@ -121,7 +120,7 @@
     androidTestImplementation(project(":internal-testutils-common"))
     androidTestImplementation("androidx.arch.core:core-testing:2.0.1")
     androidTestImplementation("androidx.paging:paging-runtime:3.1.1")
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing"))
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
     androidTestImplementation(libs.rxjava2)
     androidTestImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.mockitoCore4)
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index d81c2ef..fea1c86 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -94,8 +94,8 @@
     implementation(project(":room:room-common"))
     implementation(project(":room:room-runtime"))
     implementation(projectOrArtifact(":arch:core:core-runtime"))
-    implementation(projectOrArtifact(":lifecycle:lifecycle-livedata"))
-    implementation(projectOrArtifact(":lifecycle:lifecycle-runtime"))
+    implementation("androidx.lifecycle:lifecycle-livedata:2.6.1")
+    implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
     implementation(libs.multidex)
 
     // Workaround for b/191286558.
@@ -122,9 +122,9 @@
     androidTestImplementation(project(":room:room-paging"))
     androidTestImplementation("androidx.arch.core:core-testing:2.0.1")
     androidTestImplementation(projectOrArtifact(":paging:paging-runtime"))
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime"))
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing"))
-    androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-livedata"))
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
+    androidTestImplementation("androidx.lifecycle:lifecycle-livedata:2.6.1")
 
     // guavaAndroid is a implementation dependency instead of androidTestImplementation because
     // if it's in the androidTest apk it causes the test APK to be multidex in ways that break
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
index 472f46e..62a4e01 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
@@ -107,6 +107,7 @@
                 val filename = "${db.version}.json"
                 val exportToResources =
                     Context.BooleanProcessorOptions.EXPORT_SCHEMA_RESOURCE.getValue(env)
+                val schemaInFolderPath = context.schemaInFolderPath
                 val schemaOutFolderPath = context.schemaOutFolderPath
                 if (exportToResources) {
                     context.logger.w(ProcessorErrors.EXPORTING_SCHEMA_TO_RESOURCES)
@@ -115,19 +116,24 @@
                         originatingElements = listOf(db.element)
                     )
                     db.exportSchema(schemaFileOutputStream)
-                } else if (schemaOutFolderPath != null) {
+                } else if (schemaInFolderPath != null && schemaOutFolderPath != null) {
+                    val schemaInFolder = SchemaFileResolver.RESOLVER.getFile(
+                        Path.of(schemaInFolderPath)
+                    )
                     val schemaOutFolder = SchemaFileResolver.RESOLVER.getFile(
                         Path.of(schemaOutFolderPath)
                     )
                     if (!schemaOutFolder.exists()) {
                         schemaOutFolder.mkdirs()
                     }
-                    val dbSchemaFolder = File(schemaOutFolder, qName)
-                    if (!dbSchemaFolder.exists()) {
-                        dbSchemaFolder.mkdirs()
+                    val dbSchemaInFolder = File(schemaInFolder, qName)
+                    val dbSchemaOutFolder = File(schemaOutFolder, qName)
+                    if (!dbSchemaOutFolder.exists()) {
+                        dbSchemaOutFolder.mkdirs()
                     }
                     db.exportSchema(
-                        File(dbSchemaFolder, "${db.version}.json")
+                        inputFile = File(dbSchemaInFolder, "${db.version}.json"),
+                        outputFile = File(dbSchemaOutFolder, "${db.version}.json")
                     )
                 } else {
                     context.logger.w(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
index 07ee499..54969bf 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -147,10 +147,32 @@
         }
     }
 
+    val schemaInFolderPath by lazy {
+        val internalInputFolder =
+            processingEnv.options[ProcessorOptions.INTERNAL_SCHEMA_INPUT_FOLDER.argName]
+        val legacySchemaFolder =
+            processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
+        if (!internalInputFolder.isNullOrBlank()) {
+            internalInputFolder
+        } else if (!legacySchemaFolder.isNullOrBlank()) {
+            legacySchemaFolder
+        } else {
+            null
+        }
+    }
+
     val schemaOutFolderPath by lazy {
-        val arg = processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
-        if (arg?.isNotEmpty() == true) {
-            arg
+        val internalOutputFolder =
+            processingEnv.options[ProcessorOptions.INTERNAL_SCHEMA_OUTPUT_FOLDER.argName]
+        val legacySchemaFolder =
+            processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
+        if (!internalOutputFolder.isNullOrBlank() && !legacySchemaFolder.isNullOrBlank()) {
+            logger.e(ProcessorErrors.INVALID_GRADLE_PLUGIN_AND_SCHEMA_LOCATION_OPTION)
+        }
+        if (!internalOutputFolder.isNullOrBlank()) {
+            internalOutputFolder
+        } else if (!legacySchemaFolder.isNullOrBlank()) {
+            legacySchemaFolder
         } else {
             null
         }
@@ -256,7 +278,9 @@
     }
 
     enum class ProcessorOptions(val argName: String) {
-        OPTION_SCHEMA_FOLDER("room.schemaLocation")
+        OPTION_SCHEMA_FOLDER("room.schemaLocation"),
+        INTERNAL_SCHEMA_INPUT_FOLDER("room.internal.schemaInput"),
+        INTERNAL_SCHEMA_OUTPUT_FOLDER("room.internal.schemaOutput"),
     }
 
     enum class BooleanProcessorOptions(val argName: String, private val defaultValue: Boolean) {
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index 6e045f2..211cd37 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -28,7 +28,7 @@
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.SchemaBundle
 import androidx.room.processor.ProcessorErrors.AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF
-import androidx.room.processor.ProcessorErrors.AUTO_MIGRATION_SCHEMA_OUT_FOLDER_NULL
+import androidx.room.processor.ProcessorErrors.AUTO_MIGRATION_SCHEMA_IN_FOLDER_NULL
 import androidx.room.processor.ProcessorErrors.autoMigrationSchemasMustBeRoomGenerated
 import androidx.room.processor.ProcessorErrors.invalidAutoMigrationSchema
 import androidx.room.util.SchemaFileResolver
@@ -151,63 +151,65 @@
 
         val autoMigrationList = dbAnnotation
             .getAsAnnotationBoxArray<AutoMigration>("autoMigrations")
+        if (autoMigrationList.isEmpty()) {
+            return emptyList()
+        }
 
-        if (autoMigrationList.isNotEmpty()) {
-            if (!dbAnnotation.value.exportSchema) {
-                context.logger.e(
-                    element,
-                    AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF
-                )
-                return emptyList()
-            }
-            if (context.schemaOutFolderPath == null) {
-                context.logger.e(
-                    element,
-                    AUTO_MIGRATION_SCHEMA_OUT_FOLDER_NULL
-                )
-                return emptyList()
-            }
+        if (!dbAnnotation.value.exportSchema) {
+            context.logger.e(
+                element,
+                AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF
+            )
+            return emptyList()
+        }
+        val schemaInFolderPath = context.schemaInFolderPath
+        if (schemaInFolderPath == null) {
+            context.logger.e(
+                element,
+                AUTO_MIGRATION_SCHEMA_IN_FOLDER_NULL
+            )
+            return emptyList()
         }
 
         return autoMigrationList.mapNotNull {
-            val databaseSchemaFolderPath = Path.of(
-                context.schemaOutFolderPath!!,
+            val databaseSchemaInFolderPath = Path.of(
+                schemaInFolderPath,
                 element.asClassName().canonicalName
             )
             val autoMigration = it.value
             val validatedFromSchemaFile = getValidatedSchemaFile(
                 autoMigration.from,
-                databaseSchemaFolderPath
-            )
+                databaseSchemaInFolderPath
+            ) ?: return@mapNotNull null
 
-            fun deserializeSchemaFile(fileInputStream: FileInputStream, versionNumber: Int): Any {
+            fun deserializeSchemaFile(
+                fileInputStream: FileInputStream,
+                versionNumber: Int
+            ): DatabaseBundle? {
                 return try {
                     SchemaBundle.deserialize(fileInputStream).database
                 } catch (th: Throwable) {
                     invalidAutoMigrationSchema(
                         "$versionNumber.json",
-                        databaseSchemaFolderPath.toString()
+                        databaseSchemaInFolderPath.toString()
                     )
+                    null
                 }
             }
 
-            if (validatedFromSchemaFile != null) {
-                val fromSchemaBundle = validatedFromSchemaFile.inputStream().use {
-                    deserializeSchemaFile(it, autoMigration.from)
-                }
-                val toSchemaBundle = if (autoMigration.to == latestDbSchema.version) {
+            val fromSchemaBundle = validatedFromSchemaFile.inputStream().use {
+                deserializeSchemaFile(it, autoMigration.from)
+            }
+            val toSchemaBundle =
+                if (autoMigration.to == latestDbSchema.version) {
                     latestDbSchema
                 } else {
                     val validatedToSchemaFile = getValidatedSchemaFile(
                         autoMigration.to,
-                        databaseSchemaFolderPath
-                    )
-                    if (validatedToSchemaFile != null) {
-                        validatedToSchemaFile.inputStream().use {
-                            deserializeSchemaFile(it, autoMigration.to)
-                        }
-                    } else {
-                        return@mapNotNull null
+                        databaseSchemaInFolderPath
+                    ) ?: return@mapNotNull null
+                    validatedToSchemaFile.inputStream().use {
+                        deserializeSchemaFile(it, autoMigration.to)
                     }
                 }
                 if (fromSchemaBundle !is DatabaseBundle || toSchemaBundle !is DatabaseBundle) {
@@ -221,15 +223,12 @@
                     return@mapNotNull null
                 }
 
-                AutoMigrationProcessor(
-                    context = context,
-                    spec = it.getAsType("spec")!!,
-                    fromSchemaBundle = fromSchemaBundle,
-                    toSchemaBundle = toSchemaBundle
-                ).process()
-            } else {
-                null
-            }
+            AutoMigrationProcessor(
+                context = context,
+                spec = it.getAsType("spec")!!,
+                fromSchemaBundle = fromSchemaBundle,
+                toSchemaBundle = toSchemaBundle
+            ).process()
         }
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 0367eeb..1612b0c 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -539,9 +539,10 @@
         """.trim()
     }
 
-    val MISSING_SCHEMA_EXPORT_DIRECTORY = "Schema export directory is not provided to the" +
-        " annotation processor so we cannot export the schema. You can either provide" +
-        " `room.schemaLocation` annotation processor argument OR set exportSchema to false."
+    val MISSING_SCHEMA_EXPORT_DIRECTORY = "Schema export directory was not provided to the" +
+        " annotation processor so Room cannot export the schema. You can either provide" +
+        " `room.schemaLocation` annotation processor argument by applying the Room Gradle plugin" +
+        " (id 'androidx.room') OR set exportSchema to false."
 
     val INVALID_FOREIGN_KEY_ACTION = "Invalid foreign key action. It must be one of the constants" +
         " defined in ForeignKey.Action"
@@ -938,8 +939,11 @@
 
     fun invalidAutoMigrationSchema(schemaFile: String, schemaOutFolderPath: String): String {
         return "Found invalid schema file '$schemaFile.json' at the schema out " +
-            "folder: $schemaOutFolderPath. The schema files must be generated by Room. Cannot " +
-            "generate auto migrations."
+            "folder: $schemaOutFolderPath.\nIf you've modified the file, you might've broken the " +
+            "JSON format, try deleting the file and re-running the compiler.\n" +
+            "If you've not modified the file, please file a bug at " +
+            "https://issuetracker.google.com/issues/new?component=413107&template=1096568 " +
+            "with a sample app to reproduce the issue."
     }
 
     fun autoMigrationSchemasMustBeRoomGenerated(
@@ -1076,13 +1080,13 @@
         return "Conflicting @RenameColumn annotations found: [$annotations]"
     }
 
-    val AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF = "Cannot create auto migrations when export " +
-        "schema is OFF."
+    val AUTO_MIGRATION_FOUND_BUT_EXPORT_SCHEMA_OFF = "Cannot create auto migrations when " +
+        "exportSchema is false."
 
-    val AUTO_MIGRATION_SCHEMA_OUT_FOLDER_NULL = "Schema export directory is not provided to the" +
-        " annotation processor so we cannot import the schema. To generate auto migrations, you " +
-        "must provide `room.schemaLocation` annotation processor argument AND set exportSchema to" +
-        " true."
+    val AUTO_MIGRATION_SCHEMA_IN_FOLDER_NULL = "Schema import directory was not provided to the" +
+        " annotation processor so Room cannot read older schemas. To generate auto migrations," +
+        " you must provide `room.schemaLocation` annotation processor arguments by applying the" +
+        " Room Gradle plugin (id 'androidx.room') AND set exportSchema to true."
 
     fun tableWithConflictingPrefixFound(tableName: String): String {
         return "The new version of the schema contains '$tableName' a table name" +
@@ -1157,4 +1161,10 @@
         " the schema file and extracting it from the JAR but not for production builds, otherwise" +
         " the schema file will end up in the final artifact which is typically not desired. This" +
         " warning serves as a reminder to use room.exportSchemaResource cautiously."
+
+    val INVALID_GRADLE_PLUGIN_AND_SCHEMA_LOCATION_OPTION = "The Room Gradle plugin " +
+        "(id 'androidx.room') cannot be used with an explicit use of the annotation processor" +
+        "option `room.schemaLocation`, please remove the configuration of the option and " +
+        "configure the schema location via the plugin project extension: " +
+        "`room { schemaDirectory(...) }`."
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/util/SchemaFileResolver.kt b/room/room-compiler/src/main/kotlin/androidx/room/util/SchemaFileResolver.kt
index b97d038..6cd884e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/util/SchemaFileResolver.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/util/SchemaFileResolver.kt
@@ -28,8 +28,8 @@
 
     /**
      * Resolves the given path to a file. The path will be a either a sibling of Room's schema
-     * location or the folder itself as provided via the annotation processor option
-     * 'room.schemaLocation'.
+     * location or the folder itself as provided via the annotation processor options
+     * 'room.schemaLocation' or 'roomSchemaInput.
      */
     fun getFile(path: Path): File
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
index 1bcd843b..6674a85 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -101,32 +101,26 @@
         DigestUtils.md5Hex(input)
     }
 
-    fun exportSchema(file: File) {
+    // Writes scheme file to output file, using the input file to check if the schema has changed
+    // otherwise it is not written.
+    fun exportSchema(inputFile: File, outputFile: File) {
         val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
-        if (file.exists()) {
-            val existing = try {
-                file.inputStream().use {
-                    SchemaBundle.deserialize(it)
-                }
-            } catch (th: Throwable) {
-                throw IllegalStateException(
-                    """
-                    Cannot parse existing schema file: ${file.absolutePath}.
-                    If you've modified the file, you might've broken the JSON format, try
-                    deleting the file and re-running the compiler.
-                    If you've not modified the file, please file a bug at
-                    https://issuetracker.google.com/issues/new?component=413107&template=1096568
-                    with a sample app to reproduce the issue.
-                    """.trimIndent()
-                )
+        if (inputFile.exists()) {
+            val existing = inputFile.inputStream().use {
+                SchemaBundle.deserialize(it)
             }
+            // If existing schema file is the same as the current schema then do not write the file
+            // which helps the copy task configured by the Room Gradle Plugin skip execution due
+            // to empty variant schema output directory.
             if (existing.isSchemaEqual(schemaBundle)) {
                 return
             }
         }
-        SchemaBundle.serialize(schemaBundle, file)
+        SchemaBundle.serialize(schemaBundle, outputFile)
     }
 
+    // Writes scheme file to output stream, the stream should be for a resource otherwise use the
+    // file version of `exportSchema`.
     fun exportSchema(outputStream: OutputStream) {
         val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
         SchemaBundle.serialize(schemaBundle, outputStream)
diff --git a/room/room-gradle-plugin/build.gradle b/room/room-gradle-plugin/build.gradle
new file mode 100644
index 0000000..8b12c66
--- /dev/null
+++ b/room/room-gradle-plugin/build.gradle
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+import androidx.build.SdkResourceGenerator
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+    id("java-gradle-plugin")
+}
+
+configurations {
+    // Config for plugin classpath to be used during tests
+    testPlugin {
+        canBeConsumed = false
+        canBeResolved = true
+    }
+}
+
+dependencies {
+    implementation(libs.kotlinStdlib)
+    implementation(gradleApi())
+    implementation("com.android.tools.build:gradle:7.3.0")
+    compileOnly(libs.kotlinGradlePluginz)
+    compileOnly(libs.kspGradlePluginz)
+
+    testImplementation(project(":internal-testutils-gradle-plugin"))
+    testImplementation(gradleTestKit())
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
+    testImplementation(libs.testParameterInjector)
+
+    testPlugin(libs.kotlinGradlePluginz)
+    testPlugin(libs.kspGradlePluginz)
+}
+
+SdkResourceGenerator.generateForHostTest(project)
+
+// Configure the generating task of plugin-under-test-metadata.properties to
+// include additional dependencies for the injected plugin classpath that
+// are not present in the main runtime dependencies. This allows us to test
+// the KAPT / KSP plugins while keeping a compileOnly dep on the main source.
+tasks.withType(PluginUnderTestMetadata.class).named("pluginUnderTestMetadata").configure {
+    it.pluginClasspath.from(configurations.testPlugin)
+}
+
+// Configure publishing tasks to be dependencies of 'test' so those artifacts are available for
+// the test project executed with Gradle Test Kit.
+tasks.findByPath("test").dependsOn(
+        tasks.findByPath(":annotation:annotation-experimental:publish"),
+        tasks.findByPath(":room:room-common:publish"),
+        tasks.findByPath(":room:room-runtime:publish"),
+        tasks.findByPath(":room:room-migration:publish"),
+        tasks.findByPath(":room:room-compiler:publish"),
+        tasks.findByPath(":room:room-compiler-processing:publish"),
+        tasks.findByPath(":sqlite:sqlite:publish"),
+        tasks.findByPath(":sqlite:sqlite-framework:publish"),
+)
+
+gradlePlugin {
+    plugins {
+        room {
+            id = "androidx.room"
+            implementationClass = "androidx.room.gradle.RoomGradlePlugin"
+        }
+    }
+}
+
+androidx {
+    name = "Android Room Gradle Plugin"
+    type = LibraryType.GRADLE_PLUGIN
+    inceptionYear = "2023"
+    description = "Android Room Gradle Plugin"
+}
+
+validatePlugins {
+    enableStricterValidation = true
+}
\ No newline at end of file
diff --git a/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt
new file mode 100644
index 0000000..dc10b3c
--- /dev/null
+++ b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomExtension.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.gradle
+
+import javax.inject.Inject
+import org.gradle.api.provider.Provider
+import org.gradle.api.provider.ProviderFactory
+
+open class RoomExtension @Inject constructor(private val providers: ProviderFactory) {
+    internal var schemaDirectory: Provider<String>? = null
+
+    // TODO(b/279748243): Consider adding overload that takes `org.gradle.api.file.Director`.
+
+    /**
+     * Sets the schema location where Room will output exported schema files.
+     *
+     * The location specified will be used as the base directory for schema files that will be
+     * generated per build variant. i.e. for a 'debug' build of the product flavor 'free' then a
+     * schema will be generated in
+     * `<schemaDirectory>/freeDebug/<database-package>/<database-version>.json`.
+     *
+     * See [Export Schemas Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
+     */
+    open fun schemaDirectory(path: String) {
+        schemaDirectory(providers.provider { path })
+    }
+
+    /**
+     * Sets the schema location where Room will output exported schema files.
+     *
+     * The location specified will be used as the base directory for schema files that will be
+     * generated per build variant. i.e. for a 'debug' build of the product flavor 'free' then a
+     * schema will be generated in
+     * `<schemaDirectory>/freeDebug/<database-package>/<database-version>.json`.
+     *
+     * See [Export Schemas Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
+     */
+    open fun schemaDirectory(path: Provider<String>) {
+        schemaDirectory = path
+    }
+}
\ No newline at end of file
diff --git a/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt
new file mode 100644
index 0000000..9dacb6d
--- /dev/null
+++ b/room/room-gradle-plugin/src/main/java/androidx/room/gradle/RoomGradlePlugin.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.gradle
+
+import com.android.build.api.AndroidPluginVersion
+import com.android.build.api.variant.AndroidComponentsExtension
+import com.android.build.api.variant.ComponentIdentity
+import com.android.build.api.variant.Variant
+import com.android.build.gradle.api.AndroidBasePlugin
+import com.google.devtools.ksp.gradle.KspTaskJvm
+import java.util.Locale
+import javax.inject.Inject
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.io.path.Path
+import kotlin.io.path.notExists
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.Directory
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.ProjectLayout
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.IgnoreEmptyDirectories
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.SkipWhenEmpty
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.configurationcache.extensions.capitalized
+import org.gradle.process.CommandLineArgumentProvider
+import org.gradle.work.DisableCachingByDefault
+import org.jetbrains.kotlin.gradle.internal.KaptTask
+
+class RoomGradlePlugin @Inject constructor(
+    private val projectLayout: ProjectLayout,
+    private val objectFactory: ObjectFactory,
+) : Plugin<Project> {
+    override fun apply(project: Project) {
+        var configured = false
+        project.plugins.withType(AndroidBasePlugin::class.java) {
+            configured = true
+            configureRoom(project)
+        }
+        project.afterEvaluate {
+            project.check(configured) {
+                "The Room Gradle plugin can only be applied to an Android project."
+            }
+        }
+    }
+
+    private fun configureRoom(project: Project) {
+        // TODO(b/277899741): Validate version of Room supports the AP options configured by plugin.
+        val roomExtension =
+            project.extensions.create("room", RoomExtension::class.java)
+        val componentsExtension =
+            project.extensions.findByType(AndroidComponentsExtension::class.java)
+        project.check(componentsExtension != null) {
+            "Could not find the Android Gradle Plugin (AGP) extension, the Room Gradle plugin " +
+                "should be only applied to an Android projects."
+        }
+        project.check(componentsExtension.pluginVersion >= AndroidPluginVersion(7, 3)) {
+            "The Room Gradle plugin is only compatible with Android Gradle plugin (AGP) " +
+                "version 7.3.0 or higher (found ${componentsExtension.pluginVersion})."
+        }
+        componentsExtension.onVariants { variant ->
+            val locationProvider = roomExtension.schemaDirectory
+            project.check(locationProvider != null) {
+                "The Room Gradle plugin was applied but not schema location was specified. " +
+                    "Use the `room { schemaDirectory(...) }` DSL to specify one."
+            }
+            val schemaDirectory = locationProvider.get()
+            project.check(schemaDirectory.isNotEmpty()) {
+                "The schemaDirectory path must not be empty."
+            }
+            configureVariant(project, schemaDirectory, variant)
+        }
+    }
+
+    private fun configureVariant(
+        project: Project,
+        schemaDirectory: String,
+        variant: Variant
+    ) {
+        val androidVariantTaskNames = AndroidVariantsTaskNames(variant.name, variant)
+        val configureTask: (Task, ComponentIdentity) -> RoomSchemaDirectoryArgumentProvider = {
+                task, variantIdentity ->
+            val schemaDirectoryPath = Path(schemaDirectory, variantIdentity.name)
+            if (schemaDirectoryPath.notExists()) {
+                project.check(schemaDirectoryPath.toFile().mkdirs()) {
+                    "Unable to create directory: $schemaDirectoryPath"
+                }
+            }
+            val schemaInputDir = objectFactory.directoryProperty().apply {
+                set(project.file(schemaDirectoryPath))
+            }
+
+            val schemaOutputDir =
+                projectLayout.buildDirectory.dir("intermediates/room/schemas/${task.name}")
+
+            val copyTask = androidVariantTaskNames.copyTasks.getOrPut(variant.name) {
+                project.tasks.register(
+                    "copyRoomSchemas${variantIdentity.name.capitalize()}",
+                    RoomSchemaCopyTask::class.java
+                ) {
+                    it.schemaDirectory.set(schemaInputDir)
+                }
+            }
+            copyTask.configure { it.variantSchemaOutputDirectories.from(schemaOutputDir) }
+            task.finalizedBy(copyTask)
+
+            RoomSchemaDirectoryArgumentProvider(
+                forKsp = task.isKspTask(),
+                schemaInputDir = schemaInputDir,
+                schemaOutputDir = schemaOutputDir
+            )
+        }
+
+        configureJavaTasks(project, androidVariantTaskNames, configureTask)
+        configureKaptTasks(project, androidVariantTaskNames, configureTask)
+        configureKspTasks(project, androidVariantTaskNames, configureTask)
+
+        // TODO: Consider also setting up the androidTest and test source set to include the
+        //  relevant schema location so users can use MigrationTestHelper without additional
+        //  configuration.
+    }
+
+    private fun configureJavaTasks(
+        project: Project,
+        androidVariantsTaskNames: AndroidVariantsTaskNames,
+        configureBlock: (Task, ComponentIdentity) -> RoomSchemaDirectoryArgumentProvider
+    ) = project.tasks.withType(JavaCompile::class.java) { task ->
+        androidVariantsTaskNames.withJavaCompile(task.name)?.let { variantIdentity ->
+            val argProvider = configureBlock.invoke(task, variantIdentity)
+            task.options.compilerArgumentProviders.add(argProvider)
+        }
+    }
+
+    private fun configureKaptTasks(
+        project: Project,
+        androidVariantsTaskNames: AndroidVariantsTaskNames,
+        configureBlock: (Task, ComponentIdentity) -> RoomSchemaDirectoryArgumentProvider
+    ) = project.plugins.withId("kotlin-kapt") {
+        project.tasks.withType(KaptTask::class.java) { task ->
+            androidVariantsTaskNames.withKaptTask(task.name)?.let { variantIdentity ->
+                val argProvider = configureBlock.invoke(task, variantIdentity)
+                // TODO: Update once KT-58009 is fixed.
+                try {
+                    // Because of KT-58009, we need to add a `listOf(argProvider)` instead
+                    // of `argProvider`.
+                    task.annotationProcessorOptionProviders.add(listOf(argProvider))
+                } catch (e: Throwable) {
+                    // Once KT-58009 is fixed, adding `listOf(argProvider)` will fail, we will
+                    // pass `argProvider` instead, which is the correct way.
+                    task.annotationProcessorOptionProviders.add(argProvider)
+                }
+            }
+        }
+    }
+
+    private fun configureKspTasks(
+        project: Project,
+        androidVariantsTaskNames: AndroidVariantsTaskNames,
+        configureBlock: (Task, ComponentIdentity) -> RoomSchemaDirectoryArgumentProvider
+    ) = project.plugins.withId("com.google.devtools.ksp") {
+        project.tasks.withType(KspTaskJvm::class.java) { task ->
+            androidVariantsTaskNames.withKspTaskJvm(task.name)?.let { variantIdentity ->
+                val argProvider = configureBlock.invoke(task, variantIdentity)
+                task.commandLineArgumentProviders.add(argProvider)
+            }
+        }
+    }
+
+    internal class AndroidVariantsTaskNames(
+        private val variantName: String,
+        private val variantIdentity: ComponentIdentity
+    ) {
+        // Variant name to copy task
+        val copyTasks = mutableMapOf<String, TaskProvider<RoomSchemaCopyTask>>()
+
+        private val javaCompileName by lazy {
+            "compile${variantName.capitalized()}JavaWithJavac"
+        }
+
+        private val kaptTaskName by lazy {
+            "kapt${variantName.capitalized()}Kotlin"
+        }
+
+        private val kspTaskJvm by lazy {
+            "ksp${variantName.capitalized()}Kotlin"
+        }
+
+        fun withJavaCompile(taskName: String) =
+            if (taskName == javaCompileName) variantIdentity else null
+
+        fun withKaptTask(taskName: String) =
+            if (taskName == kaptTaskName) variantIdentity else null
+
+        fun withKspTaskJvm(taskName: String) =
+            if (taskName == kspTaskJvm) variantIdentity else null
+    }
+
+    @DisableCachingByDefault(because = "Simple disk bound task.")
+    abstract class RoomSchemaCopyTask : DefaultTask() {
+        @get:InputFiles
+        @get:SkipWhenEmpty
+        @get:IgnoreEmptyDirectories
+        @get:PathSensitive(PathSensitivity.RELATIVE)
+        abstract val variantSchemaOutputDirectories: ConfigurableFileCollection
+
+        @get:Internal
+        abstract val schemaDirectory: DirectoryProperty
+
+        @TaskAction
+        fun copySchemas() {
+            variantSchemaOutputDirectories.files
+                .filter { it.exists() }
+                .forEach {
+                    // TODO(b/278266663): Error when two same relative path schemas are found in out
+                    //  dirs and their content is different an indicator of an inconsistency between
+                    //  the compile tasks of the same variant.
+                    it.copyRecursively(schemaDirectory.get().asFile, overwrite = true)
+                }
+        }
+    }
+
+    class RoomSchemaDirectoryArgumentProvider(
+        @get:Input
+        val forKsp: Boolean,
+        @get:InputFiles
+        @get:PathSensitive(PathSensitivity.RELATIVE)
+        val schemaInputDir: Provider<Directory>,
+        @get:OutputDirectory
+        val schemaOutputDir: Provider<Directory>
+    ) : CommandLineArgumentProvider {
+        override fun asArguments() = buildList {
+            val prefix = if (forKsp) "" else "-A"
+            add("${prefix}room.internal.schemaInput=${schemaInputDir.get().asFile.path}")
+            add("${prefix}room.internal.schemaOutput=${schemaOutputDir.get().asFile.path}")
+        }
+    }
+
+    companion object {
+        internal fun String.capitalize(): String = this.replaceFirstChar {
+            if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString()
+        }
+
+        internal fun Task.isKspTask(): Boolean = try {
+            val kspTaskClass = Class.forName("com.google.devtools.ksp.gradle.KspTask")
+            kspTaskClass.isAssignableFrom(this::class.java)
+        } catch (ex: ClassNotFoundException) {
+            false
+        }
+
+        @OptIn(ExperimentalContracts::class)
+        internal fun Project.check(value: Boolean, lazyMessage: () -> String) {
+            contract {
+                returns() implies value
+            }
+            if (isGradleSyncRunning()) return
+            if (!value) {
+                throw GradleException(lazyMessage())
+            }
+        }
+
+        private fun Project.isGradleSyncRunning() = gradleSyncProps.any {
+            it in this.properties && this.properties[it].toString().toBoolean()
+        }
+
+        private val gradleSyncProps by lazy {
+            listOf(
+                "android.injected.build.model.v2",
+                "android.injected.build.model.only",
+                "android.injected.build.model.only.advanced",
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt b/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
new file mode 100644
index 0000000..ce82693
--- /dev/null
+++ b/room/room-gradle-plugin/src/test/java/androidx/room/gradle/RoomGradlePluginTest.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.gradle
+
+import androidx.testutils.gradle.ProjectSetupRule
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import java.io.File
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(TestParameterInjector::class)
+class RoomGradlePluginTest(
+    @TestParameter val backend: ProcessingBackend
+) {
+    @get:Rule
+    val projectSetup = ProjectSetupRule()
+
+    private val roomVersion by lazy {
+        projectSetup.getLibraryLatestVersionInLocalRepo("androidx/room/room-compiler")
+    }
+
+    private fun setup(projectName: String, projectRoot: File = projectSetup.rootDir) {
+        // copy test project
+        File("src/test/test-data/$projectName").copyRecursively(projectRoot)
+
+        if (backend.isForKotlin) {
+            // copy Kotlin database file
+            File("src/test/test-data/kotlin/MyDatabase.kt").let {
+                it.copyTo(projectRoot.resolve("src/main/java/room/testapp/${it.name}"))
+            }
+        } else {
+            // copy Java database file
+            File("src/test/test-data/java/MyDatabase.java").let {
+                it.copyTo(projectRoot.resolve("src/main/java/room/testapp/${it.name}"))
+            }
+        }
+
+        val additionalPluginsBlock = when (backend) {
+            ProcessingBackend.JAVAC ->
+                ""
+            ProcessingBackend.KAPT ->
+                """
+                    id('kotlin-android')
+                    id('kotlin-kapt')
+                """
+            ProcessingBackend.KSP ->
+                """
+                    id('kotlin-android')
+                    id('com.google.devtools.ksp')
+                """
+        }
+
+        val repositoriesBlock = buildString {
+            appendLine("repositories {")
+            projectSetup.allRepositoryPaths.forEach {
+                appendLine("""maven { url "$it" }""")
+            }
+            appendLine("}")
+        }
+
+        val processorConfig = when (backend) {
+            ProcessingBackend.JAVAC -> "annotationProcessor"
+            ProcessingBackend.KAPT -> "kapt"
+            ProcessingBackend.KSP -> "ksp"
+        }
+
+        val kotlinJvmTargetBlock = if (backend.isForKotlin) {
+            """
+            tasks.withType(
+                org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+            ).configureEach {
+                kotlinOptions {
+                    jvmTarget = "1.8"
+                }
+            }
+            """.trimIndent()
+        } else {
+            ""
+        }
+
+        // set up build file
+        File(projectRoot, "build.gradle").writeText(
+            """
+            plugins {
+                id('com.android.application')
+                id('androidx.room')
+                $additionalPluginsBlock
+            }
+
+            $repositoriesBlock
+
+            %s
+
+            dependencies {
+                // Uses latest Room built from tip of tree
+                implementation "androidx.room:room-runtime:$roomVersion"
+                $processorConfig "androidx.room:room-compiler:$roomVersion"
+            }
+
+            android {
+                namespace "room.testapp"
+                compileOptions {
+                  sourceCompatibility = JavaVersion.VERSION_1_8
+                  targetCompatibility = JavaVersion.VERSION_1_8
+                }
+            }
+
+            $kotlinJvmTargetBlock
+
+            room {
+                schemaDirectory("${'$'}projectDir/schemas")
+            }
+
+            """
+                .trimMargin()
+                // doing format instead of "$projectSetup.androidProject" on purpose,
+                // because otherwise trimIndent will mess with formatting
+                .format(projectSetup.androidProject)
+
+        )
+    }
+
+    @Test
+    fun testWorkflow() {
+        setup("simple-project")
+
+        // First clean build, all tasks need to run
+        runGradleTasks(CLEAN_TASK, COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.SUCCESS)
+        }
+
+        // Schema file at version 1 is created
+        var schemaOneTimestamp: Long
+        projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/1.json").let {
+            assertThat(it.exists()).isTrue()
+            schemaOneTimestamp = it.lastModified()
+        }
+
+        // Incremental build, compile task re-runs because schema 1 is used as input, but no copy
+        // is done since schema has not changed.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.NO_SOURCE)
+        }
+
+        // Incremental build, everything is up to date.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.UP_TO_DATE)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.NO_SOURCE)
+        }
+
+        // Make a change that changes the schema at version 1
+        searchAndReplace(
+            file = projectSetup.rootDir.resolve("src/main/java/room/testapp/MyEntity.java"),
+            search = "// Insert-change",
+            replace = "public String text;"
+        )
+
+        // Incremental build, new schema for version 1 is generated and copied.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.SUCCESS)
+        }
+
+        // Check schema file at version 1 is updated
+        projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/1.json").let {
+            assertThat(it.exists()).isTrue()
+            assertThat(schemaOneTimestamp).isNotEqualTo(it.lastModified())
+            schemaOneTimestamp = it.lastModified()
+        }
+
+        // Incremental build, compile task re-runs because schema 1 is used as input (it changed),
+        // but no copy is done since schema has not changed.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.NO_SOURCE)
+        }
+
+        // Incremental build, everything is up to date.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.UP_TO_DATE)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.NO_SOURCE)
+        }
+
+        // Add a new file, it does not change the schema
+        projectSetup.rootDir.resolve("src/main/java/room/testapp/NewUtil.java")
+            .writeText("""
+            package room.testapp;
+            public class NewUtil {
+            }
+            """.trimIndent())
+
+        // Incremental build, compile task re-runs because of new source, but no schema is copied
+        // since Room processor didn't even run.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.NO_SOURCE)
+        }
+
+        // Incremental build, everything is up to date.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.UP_TO_DATE)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.NO_SOURCE)
+        }
+
+        // Change the database version to 2
+        val dbFile = if (backend.isForKotlin) "MyDatabase.kt" else "MyDatabase.java"
+        searchAndReplace(
+            file = projectSetup.rootDir.resolve("src/main/java/room/testapp/$dbFile"),
+            search = "version = 1",
+            replace = "version = 2"
+        )
+
+        // Incremental build, due to the version change a new schema file is generated.
+        runGradleTasks(COMPILE_TASK).let { result ->
+            result.assertTaskOutcome(COMPILE_TASK, TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(COPY_TASK, TaskOutcome.SUCCESS)
+        }
+
+        // Check schema file at version 1 is still present and unchanged.
+        projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/1.json").let {
+            assertThat(it.exists()).isTrue()
+            assertThat(schemaOneTimestamp).isEqualTo(it.lastModified())
+        }
+
+        // Check schema file at version 2 is created and copied.
+        projectSetup.rootDir.resolve("schemas/debug/room.testapp.MyDatabase/2.json").let {
+            assertThat(it.exists()).isTrue()
+        }
+    }
+
+    @Test
+    fun testFlavoredProject() {
+        setup("flavored-project")
+
+        File(projectSetup.rootDir, "build.gradle").appendText(
+            """
+            android {
+                flavorDimensions "mode"
+                productFlavors {
+                    flavorOne {
+                        dimension "mode"
+                    }
+                    flavorTwo {
+                        dimension "mode"
+                    }
+                }
+            }
+            """.trimIndent()
+        )
+
+        runGradleTasks(
+            CLEAN_TASK,
+            "compileFlavorOneDebugJavaWithJavac",
+            "compileFlavorTwoDebugJavaWithJavac"
+        ).let { result ->
+            result.assertTaskOutcome(":compileFlavorOneDebugJavaWithJavac", TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(":compileFlavorTwoDebugJavaWithJavac", TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(":copyRoomSchemasFlavorOneDebug", TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(":copyRoomSchemasFlavorTwoDebug", TaskOutcome.SUCCESS)
+        }
+        // Check schema files are generated for both flavor, each in its own folder.
+        val flavorOneSchema = projectSetup.rootDir.resolve(
+            "schemas/flavorOneDebug/room.testapp.MyDatabase/1.json"
+        )
+        val flavorTwoSchema = projectSetup.rootDir.resolve(
+            "schemas/flavorTwoDebug/room.testapp.MyDatabase/1.json"
+        )
+        assertThat(flavorOneSchema.exists()).isTrue()
+        assertThat(flavorTwoSchema.exists()).isTrue()
+        // Check the schemas in both flavors are different
+        assertThat(flavorOneSchema.readText()).isNotEqualTo(flavorTwoSchema.readText())
+    }
+
+    @Test
+    fun testMoreBuildTypesProject() {
+        setup("simple-project")
+
+        File(projectSetup.rootDir, "build.gradle").appendText(
+            """
+            android {
+                buildTypes {
+                    staging {
+                        initWith debug
+                        applicationIdSuffix ".debugStaging"
+                    }
+                }
+            }
+            """.trimIndent()
+        )
+
+        runGradleTasks(CLEAN_TASK, "compileStagingJavaWithJavac",).let { result ->
+            result.assertTaskOutcome(":compileStagingJavaWithJavac", TaskOutcome.SUCCESS)
+            result.assertTaskOutcome(":copyRoomSchemasStaging", TaskOutcome.SUCCESS)
+        }
+        val schemeFile = projectSetup.rootDir.resolve(
+            "schemas/staging/room.testapp.MyDatabase/1.json"
+        )
+        assertThat(schemeFile.exists()).isTrue()
+    }
+
+    private fun runGradleTasks(
+        vararg args: String,
+        projectDir: File = projectSetup.rootDir
+    ): BuildResult {
+        return GradleRunner.create()
+            .withProjectDir(projectDir)
+            .withPluginClasspath()
+            // workaround for b/231154556
+            .withArguments("-Dorg.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m", *args)
+            .build()
+    }
+
+    private fun BuildResult.assertTaskOutcome(taskPath: String, outcome: TaskOutcome) {
+        assertThat(this.task(taskPath)!!.outcome).isEqualTo(outcome)
+    }
+
+    private fun searchAndReplace(file: File, search: String, replace: String) {
+        file.writeText(file.readText().replace(search, replace))
+    }
+
+    enum class ProcessingBackend(
+        val isForKotlin: Boolean
+    ) {
+        JAVAC(false),
+        KAPT(true),
+        KSP(true)
+    }
+
+    companion object {
+        private const val CLEAN_TASK = ":clean"
+        private const val COMPILE_TASK = ":compileDebugJavaWithJavac"
+        private const val COPY_TASK = ":copyRoomSchemasDebug"
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/flavorOne/java/room/testapp/MyEntity.java
similarity index 78%
rename from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
rename to room/room-gradle-plugin/src/test/test-data/flavored-project/src/flavorOne/java/room/testapp/MyEntity.java
index dcdff02..8959321 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/flavorOne/java/room/testapp/MyEntity.java
@@ -14,8 +14,13 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp;
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class MyEntity {
+    @PrimaryKey
+    public long id;
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/flavorTwo/java/room/testapp/MyEntity.java
similarity index 75%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to room/room-gradle-plugin/src/test/test-data/flavored-project/src/flavorTwo/java/room/testapp/MyEntity.java
index dcdff02..ea059e9 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/flavorTwo/java/room/testapp/MyEntity.java
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp;
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class MyEntity {
+    @PrimaryKey
+    public long id;
+
+    public boolean flavorTwoColumn;
+}
diff --git a/room/room-gradle-plugin/src/test/test-data/flavored-project/src/main/AndroidManifest.xml b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1e3e702
--- /dev/null
+++ b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest/>
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/main/java/room/testapp/MyDao.java
similarity index 75%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to room/room-gradle-plugin/src/test/test-data/flavored-project/src/main/java/room/testapp/MyDao.java
index dcdff02..727ffbc 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/flavored-project/src/main/java/room/testapp/MyDao.java
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp;
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface MyDao {
+    @Query("SELECT * FROM MyEntity")
+    List<MyEntity> getAll();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/java/MyDatabase.java
similarity index 71%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to room/room-gradle-plugin/src/test/test-data/java/MyDatabase.java
index dcdff02..29851d4 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/java/MyDatabase.java
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp;
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Database;
+import androidx.room.RoomDatabase;
+
+@Database(entities = { MyEntity.class }, version = 1)
+public abstract class MyDatabase extends RoomDatabase {
+    public abstract MyDao getDao();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/kotlin/MyDatabase.kt
similarity index 73%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to room/room-gradle-plugin/src/test/test-data/kotlin/MyDatabase.kt
index dcdff02..8b03a9f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/kotlin/MyDatabase.kt
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Database
+import androidx.room.RoomDatabase
+
+@Database(entities = [MyEntity::class], version = 1)
+abstract class MyDatabase : RoomDatabase() {
+    abstract fun getDao(): MyDao
+}
\ No newline at end of file
diff --git a/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/AndroidManifest.xml b/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1e3e702
--- /dev/null
+++ b/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest/>
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/java/room/testapp/MyDao.java
similarity index 75%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to room/room-gradle-plugin/src/test/test-data/simple-project/src/main/java/room/testapp/MyDao.java
index dcdff02..727ffbc 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/java/room/testapp/MyDao.java
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp;
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+@Dao
+public interface MyDao {
+    @Query("SELECT * FROM MyEntity")
+    List<MyEntity> getAll();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java b/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/java/room/testapp/MyEntity.java
similarity index 77%
copy from appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
copy to room/room-gradle-plugin/src/test/test-data/simple-project/src/main/java/room/testapp/MyEntity.java
index dcdff02..3e4c1c0 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/package-info.java
+++ b/room/room-gradle-plugin/src/test/test-data/simple-project/src/main/java/room/testapp/MyEntity.java
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-package androidx.appactions.interaction.capabilities.core.values;
+package room.testapp;
 
-import androidx.annotation.RestrictTo;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public class MyEntity {
+    @PrimaryKey
+    public long id;
+
+    // Insert-change
+}
diff --git a/room/room-runtime/proguard-rules.pro b/room/room-runtime/proguard-rules.pro
index 57eff04..60b1670 100644
--- a/room/room-runtime/proguard-rules.pro
+++ b/room/room-runtime/proguard-rules.pro
@@ -1,2 +1,3 @@
 -keep class * extends androidx.room.RoomDatabase
 -dontwarn androidx.room.paging.**
+-dontwarn androidx.lifecycle.LiveData
diff --git a/samples/MediaRoutingDemo/build.gradle b/samples/MediaRoutingDemo/build.gradle
index a40ccba..abce76a 100644
--- a/samples/MediaRoutingDemo/build.gradle
+++ b/samples/MediaRoutingDemo/build.gradle
@@ -4,14 +4,14 @@
 }
 
 dependencies {
-    implementation("androidx.appcompat:appcompat:1.6.0")
+    implementation(project(":appcompat:appcompat"))
     implementation(project(":mediarouter:mediarouter"))
-    implementation("androidx.recyclerview:recyclerview:1.2.1")
-    implementation("androidx.concurrent:concurrent-futures:1.1.0")
-    implementation(libs.material)
+    implementation(project(":recyclerview:recyclerview"))
+    implementation(project(":concurrent:concurrent-futures"))
 
-    implementation ("androidx.multidex:multidex:2.0.1")
-    implementation("com.google.guava:guava:31.1-android")
+    implementation(libs.material)
+    implementation(libs.multidex)
+    implementation(libs.guava)
 }
 
 android {
diff --git a/settings.gradle b/settings.gradle
index f600681..aa1e705 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,6 +1,6 @@
-import groovy.transform.Field
 import androidx.build.gradle.gcpbuildcache.GcpBuildCache
 import androidx.build.gradle.gcpbuildcache.GcpBuildCacheServiceFactory
+import groovy.transform.Field
 
 import java.util.regex.Matcher
 import java.util.regex.Pattern
@@ -401,13 +401,13 @@
 //
 /////////////////////////////
 
-includeProject(":activity:activity", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":activity:activity", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":activity:activity-compose", [BuildType.COMPOSE])
 includeProject(":activity:activity-compose:activity-compose-samples", "activity/activity-compose/samples", [BuildType.COMPOSE])
 includeProject(":activity:activity-compose:integration-tests:activity-demos", [BuildType.COMPOSE])
 includeProject(":activity:activity-compose-lint", [BuildType.COMPOSE])
-includeProject(":activity:activity-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
-includeProject(":activity:activity-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":activity:activity-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":activity:activity-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":activity:integration-tests:testapp", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":annotation:annotation")
 includeProject(":annotation:annotation-experimental")
@@ -425,6 +425,7 @@
 includeProject(":appactions:interaction:interaction-proto", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-service", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-service-proto", [BuildType.MAIN])
+includeProject(":appactions:interaction:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":appcompat:appcompat", [BuildType.MAIN])
 includeProject(":appcompat:appcompat-benchmark", [BuildType.MAIN])
 includeProject(":appcompat:appcompat-lint", [BuildType.MAIN])
@@ -440,8 +441,8 @@
 includeProject(":appsearch:appsearch-local-storage", [BuildType.MAIN])
 includeProject(":appsearch:appsearch-platform-storage", [BuildType.MAIN])
 includeProject(":appsearch:appsearch-test-util", [BuildType.MAIN])
-includeProject(":arch:core:core-common", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":arch:core:core-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":arch:core:core-common", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":arch:core:core-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":arch:core:core-testing", [BuildType.MAIN])
 includeProject(":asynclayoutinflater:asynclayoutinflater", [BuildType.MAIN])
 includeProject(":asynclayoutinflater:asynclayoutinflater-appcompat", [BuildType.MAIN])
@@ -659,8 +660,8 @@
 includeProject(":constraintlayout:constraintlayout-core", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":contentpager:contentpager", [BuildType.MAIN])
 includeProject(":coordinatorlayout:coordinatorlayout", [BuildType.MAIN])
-includeProject(":core:core", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
-includeProject(":core:core-testing", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":core:core", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":core:core-testing", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":core:core:integration-tests:publishing", [BuildType.MAIN])
 includeProject(":core:core-animation", [BuildType.MAIN])
 includeProject(":core:core-animation-integration-tests:testapp", [BuildType.MAIN])
@@ -668,7 +669,7 @@
 includeProject(":core:core-appdigest", [BuildType.MAIN])
 includeProject(":core:core-google-shortcuts", [BuildType.MAIN])
 includeProject(":core:core-i18n", [BuildType.MAIN])
-includeProject(":core:core-ktx", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":core:core-ktx", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":core:core-location-altitude", [BuildType.MAIN])
 includeProject(":core:core-performance", [BuildType.MAIN])
 includeProject(":core:core-performance:core-performance-samples", "core/core-performance/samples", [BuildType.MAIN])
@@ -724,9 +725,9 @@
 includeProject(":enterprise:enterprise-feedback", [BuildType.MAIN])
 includeProject(":enterprise:enterprise-feedback-testing", [BuildType.MAIN])
 includeProject(":exifinterface:exifinterface", [BuildType.MAIN])
-includeProject(":fragment:fragment", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR])
+includeProject(":fragment:fragment", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":fragment:fragment-ktx", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":fragment:fragment-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR])
+includeProject(":fragment:fragment-lint", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":fragment:fragment-testing", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":fragment:fragment-testing-lint", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":fragment:fragment-testing-manifest", [BuildType.MAIN, BuildType.FLAN])
@@ -785,33 +786,33 @@
 includeProject(":lifecycle:integration-tests:incrementality", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lifecycle:integration-tests:lifecycle-testapp", "lifecycle/integration-tests/testapp", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lifecycle:integration-tests:lifecycle-testapp-kotlin", "lifecycle/integration-tests/kotlintestapp", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":lifecycle:lifecycle-common", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-common-java8", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-common", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-common-java8", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-compiler", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lifecycle:lifecycle-extensions", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":lifecycle:lifecycle-livedata", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-livedata-core", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-livedata-core-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-livedata-core-ktx-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-livedata", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-livedata-core", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-livedata-core-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-livedata-core-ktx-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-livedata-core-truth", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":lifecycle:lifecycle-livedata-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-livedata-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-process", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lifecycle:lifecycle-reactivestreams", [BuildType.MAIN, BuildType.FLAN])
 includeProject(":lifecycle:lifecycle-reactivestreams-ktx", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":lifecycle:lifecycle-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-compose", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-compose:lifecycle-runtime-compose-samples", "lifecycle/lifecycle-runtime-compose/samples", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-runtime-compose:integration-tests:lifecycle-runtime-compose-demos", [BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-runtime-ktx-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-runtime-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-runtime-ktx-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-runtime-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-service", [BuildType.MAIN, BuildType.FLAN, BuildType.GLANCE])
-includeProject(":lifecycle:lifecycle-viewmodel", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-viewmodel", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples", "lifecycle/lifecycle-viewmodel-compose/samples", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:integration-tests:lifecycle-viewmodel-demos", [BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-viewmodel-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-viewmodel-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":lint-checks")
 includeProject(":lint-checks:integration-tests")
 includeProject(":loader:loader", [BuildType.MAIN])
@@ -917,6 +918,7 @@
 includeProject(":room:room-compiler-processing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":room:room-compiler-processing-testing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":room:room-guava", [BuildType.MAIN])
+includeProject(":room:room-gradle-plugin", [BuildType.MAIN])
 includeProject(":room:room-ktx", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":room:room-migration", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":room:room-paging", [BuildType.MAIN, BuildType.COMPOSE])
@@ -928,8 +930,8 @@
 includeProject(":room:room-rxjava2", [BuildType.MAIN])
 includeProject(":room:room-rxjava3", [BuildType.MAIN])
 includeProject(":room:room-testing", [BuildType.MAIN])
-includeProject(":savedstate:savedstate", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.WEAR])
-includeProject(":savedstate:savedstate-ktx", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.WEAR])
+includeProject(":savedstate:savedstate", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
+includeProject(":savedstate:savedstate-ktx", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":security:security-app-authenticator", [BuildType.MAIN])
 includeProject(":security:security-app-authenticator-testing", [BuildType.MAIN])
 includeProject(":security:security-biometric", [BuildType.MAIN])
@@ -983,7 +985,7 @@
 includeProject(":vectordrawable:vectordrawable-animated", [BuildType.MAIN])
 includeProject(":vectordrawable:vectordrawable-seekable", [BuildType.MAIN])
 includeProject(":versionedparcelable:versionedparcelable", [BuildType.MAIN, BuildType.MEDIA])
-includeProject(":versionedparcelable:versionedparcelable-compiler", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":versionedparcelable:versionedparcelable-compiler", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":viewpager2:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":viewpager2:integration-tests:targetsdk-tests", [BuildType.MAIN])
 includeProject(":viewpager2:viewpager2", [BuildType.MAIN])
@@ -1118,10 +1120,10 @@
 
 includeProject(":internal-testutils-common", "testutils/testutils-common", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":internal-testutils-datastore", "testutils/testutils-datastore", [BuildType.MAIN, BuildType.KMP])
-includeProject(":internal-testutils-runtime", "testutils/testutils-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.MEDIA, BuildType.WEAR])
+includeProject(":internal-testutils-runtime", "testutils/testutils-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.MEDIA])
 includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN])
 includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":internal-testutils-fonts", "testutils/testutils-fonts", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":internal-testutils-fonts", "testutils/testutils-fonts", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 includeProject(":internal-testutils-truth", "testutils/testutils-truth")
 includeProject(":internal-testutils-ktx", "testutils/testutils-ktx")
 includeProject(":internal-testutils-kmp", "testutils/testutils-kmp", [BuildType.MAIN, BuildType.KMP, BuildType.COMPOSE])
@@ -1130,7 +1132,7 @@
 includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-paparazzi", "testutils/testutils-paparazzi", [BuildType.COMPOSE])
 includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.TOOLS])
-includeProject(":internal-testutils-mockito", "testutils/testutils-mockito", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
+includeProject(":internal-testutils-mockito", "testutils/testutils-mockito", [BuildType.MAIN, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE])
 
 /////////////////////////////
 //
diff --git a/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt b/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt
index 7bd12a1c..0724f1f 100644
--- a/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt
+++ b/testutils/testutils-gradle-plugin/src/main/java/androidx/testutils/gradle/ProjectSetupRule.kt
@@ -16,12 +16,15 @@
 
 package androidx.testutils.gradle
 
+import java.io.File
+import java.util.Properties
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.xpath.XPathConstants
+import javax.xml.xpath.XPathFactory
 import org.junit.rules.ExternalResource
 import org.junit.rules.TemporaryFolder
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
-import java.io.File
-import java.util.Properties
 
 /**
  * Test rule that helps to setup android project in tests that run gradle.
@@ -130,6 +133,42 @@
         }
     }
 
+    /**
+     * Gets the latest version of a published library.
+     *
+     * Note that the library must have been locally published to locate its latest version, this
+     * can be done in test by adding :publish as a test dependency, for example:
+     * ```
+     * tasks.findByPath("test")
+     *   .dependsOn(tasks.findByPath(":room:room-compiler:publish")
+     * ```
+     *
+     * @param path - The library m2 path e.g. "androidx/room/room-compiler"
+     */
+    fun getLibraryLatestVersionInLocalRepo(path: String): String {
+        val metadataFile = File(props.tipOfTreeMavenRepoPath)
+            .resolve(path)
+            .resolve("maven-metadata.xml")
+        check(metadataFile.exists()) {
+            "Cannot find room metadata file in ${metadataFile.absolutePath}"
+        }
+        check(metadataFile.isFile) {
+            "Metadata file should be a file but it is not."
+        }
+        val xmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+            .parse(metadataFile)
+        val latestVersionNode = XPathFactory.newInstance().newXPath()
+            .compile("/metadata/versioning/latest").evaluate(
+                xmlDoc, XPathConstants.STRING
+            )
+        check(latestVersionNode is String) {
+            """Unexpected node for latest version:
+                $latestVersionNode / ${latestVersionNode::class.java}
+            """.trimIndent()
+        }
+        return latestVersionNode
+    }
+
     private fun copyLocalProperties() {
         var foundSdk = false
 
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.kt b/text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.kt
new file mode 100644
index 0000000..88bc9ba
--- /dev/null
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/InlineClassUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package androidx.compose.ui.text.android
+
+/**
+ * Packs two Int values into one Long value for use in inline classes.
+ */
+internal inline fun packInts(val1: Int, val2: Int): Long {
+    return val1.toLong().shl(32) or (val2.toLong() and 0xFFFFFFFF)
+}
+
+/**
+ * Unpacks the first Int value in [packInts] from its returned ULong.
+ */
+internal inline fun unpackInt1(value: Long): Int {
+    return value.shr(32).toInt()
+}
+
+/**
+ * Unpacks the second Int value in [packInts] from its returned ULong.
+ */
+internal inline fun unpackInt2(value: Long): Int {
+    return value.and(0xFFFFFFFF).toInt()
+}
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
index 0290795..20c751f 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
@@ -320,8 +320,8 @@
 
         lineHeightSpans = getLineHeightSpans()
         val lineHeightPaddings = getLineHeightPaddings(lineHeightSpans)
-        topPadding = max(verticalPaddings.first, lineHeightPaddings.first)
-        bottomPadding = max(verticalPaddings.second, lineHeightPaddings.second)
+        topPadding = max(verticalPaddings.topPadding, lineHeightPaddings.topPadding)
+        bottomPadding = max(verticalPaddings.bottomPadding, lineHeightPaddings.bottomPadding)
 
         val lastLineMetricsPair = getLastLineMetrics(textPaint, frameworkTextDir, lineHeightSpans)
         lastLineFontMetrics = lastLineMetricsPair.first
@@ -872,9 +872,24 @@
     }
 }
 
+internal fun VerticalPaddings(
+    topPadding: Int,
+    bottomPadding: Int
+) = VerticalPaddings(packInts(topPadding, bottomPadding))
+
+@kotlin.jvm.JvmInline
+internal value class VerticalPaddings internal constructor(internal val packedValue: Long) {
+
+  val topPadding: Int
+      get() = unpackInt1(packedValue)
+
+  val bottomPadding: Int
+      get() = unpackInt2(packedValue)
+}
+
 @OptIn(InternalPlatformTextApi::class)
-private fun TextLayout.getVerticalPaddings(): Pair<Int, Int> {
-    if (includePadding || isFallbackLinespacingApplied()) return Pair(0, 0)
+private fun TextLayout.getVerticalPaddings(): VerticalPaddings {
+    if (includePadding || isFallbackLinespacingApplied()) return ZeroVerticalPadding
 
     val paint = layout.paint
     val text = layout.text
@@ -912,18 +927,18 @@
     }
 
     return if (topPadding == 0 && bottomPadding == 0) {
-        EmptyPair
+        ZeroVerticalPadding
     } else {
-        Pair(topPadding, bottomPadding)
+        VerticalPaddings(topPadding, bottomPadding)
     }
 }
 
-private val EmptyPair = Pair(0, 0)
+private val ZeroVerticalPadding = VerticalPaddings(0, 0)
 
 @OptIn(InternalPlatformTextApi::class)
 private fun TextLayout.getLineHeightPaddings(
     lineHeightSpans: Array<LineHeightStyleSpan>
-): Pair<Int, Int> {
+): VerticalPaddings {
     var firstAscentDiff = 0
     var lastDescentDiff = 0
 
@@ -937,9 +952,9 @@
     }
 
     return if (firstAscentDiff == 0 && lastDescentDiff == 0) {
-        EmptyPair
+        ZeroVerticalPadding
     } else {
-        Pair(firstAscentDiff, lastDescentDiff)
+        VerticalPaddings(firstAscentDiff, lastDescentDiff)
     }
 }
 
@@ -1007,4 +1022,4 @@
     return lineHeightStyleSpans
 }
 
-internal fun Layout.isLineEllipsized(lineIndex: Int) = this.getEllipsisCount(lineIndex) > 0
\ No newline at end of file
+internal fun Layout.isLineEllipsized(lineIndex: Int) = this.getEllipsisCount(lineIndex) > 0
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index e2cc7d6..12488a4 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -372,6 +372,16 @@
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
   }
 
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NonInteractiveSurfaceDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getColor();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public long getContentColor();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.compose.ui.graphics.Shape getShape();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long color;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final long contentColor;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.compose.ui.graphics.Shape shape;
+    field public static final androidx.tv.material3.NonInteractiveSurfaceDefaults INSTANCE;
+  }
+
   @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedButtonDefaults {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedDisabledBorder);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor, optional long disabledContainerColor, optional long disabledContentColor);
@@ -456,6 +466,7 @@
   }
 
   public final class SurfaceKt {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(optional androidx.compose.ui.Modifier modifier, optional float tonalElevation, optional androidx.compose.ui.graphics.Shape shape, optional long color, optional long contentColor, optional androidx.tv.material3.Border border, optional androidx.tv.material3.Glow glow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ClickableSurfaceShape shape, optional androidx.tv.material3.ClickableSurfaceColor color, optional androidx.tv.material3.ClickableSurfaceColor contentColor, optional androidx.tv.material3.ClickableSurfaceScale scale, optional androidx.tv.material3.ClickableSurfaceBorder border, optional androidx.tv.material3.ClickableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ToggleableSurfaceShape shape, optional androidx.tv.material3.ToggleableSurfaceColor color, optional androidx.tv.material3.ToggleableSurfaceColor contentColor, optional androidx.tv.material3.ToggleableSurfaceScale scale, optional androidx.tv.material3.ToggleableSurfaceBorder border, optional androidx.tv.material3.ToggleableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalAbsoluteTonalElevation();
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/NonInteractiveSurfaceScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NonInteractiveSurfaceScreenshotTest.kt
new file mode 100644
index 0000000..c23e30b
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NonInteractiveSurfaceScreenshotTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import android.os.Build
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@MediumTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class NonInteractiveSurfaceScreenshotTest(private val scheme: ColorSchemeWrapper) {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    private val containerModifier = Modifier.size(150.dp)
+
+    private val surfaceModifier: @Composable BoxScope.() -> Modifier = {
+        Modifier
+            .size(100.dp)
+            .align(Alignment.Center)
+    }
+
+    private val wrapperTestTag = "NonInteractiveSurfaceWrapper"
+
+    @Test
+    fun nonInteractiveSurface_noCustomizations() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(surfaceModifier().align(Alignment.Center)) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_noCustomizations")
+    }
+
+    @Test
+    fun nonInteractiveSurface_nonZero_tonalElevation() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(surfaceModifier(), tonalElevation = 2.dp) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_nonZero_tonalElevation")
+    }
+
+    @Test
+    fun nonInteractiveSurface_circleShape() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(surfaceModifier(), shape = CircleShape) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_circleShape")
+    }
+
+    @Test
+    fun nonInteractiveSurface_containerColor() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(surfaceModifier(), color = Color.Green) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_containerColor")
+    }
+
+    @Test
+    fun nonInteractiveSurface_contentColor() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(surfaceModifier(), contentColor = Color.Red) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_contentColor")
+    }
+
+    @Test
+    fun nonInteractiveSurface_borderApplied() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(
+                    surfaceModifier(),
+                    border = Border(
+                        border = BorderStroke(2.dp, Color.Red),
+                        inset = 4.dp,
+                    ),
+                ) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_borderApplied")
+    }
+
+    @Test
+    fun nonInteractiveSurface_glowApplied() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(containerModifier.testTag(wrapperTestTag)) {
+                Surface(
+                    surfaceModifier(),
+                    glow = Glow(elevationColor = Color.Red, elevation = 2.dp)
+                ) {}
+            }
+        }
+        assertAgainstGolden("non_interactive_surface_${scheme.name}_glowApplied")
+    }
+
+    private fun assertAgainstGolden(goldenName: String) {
+        rule.onNodeWithTag(wrapperTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenName)
+    }
+
+    // Provide the ColorScheme and their name parameter in a ColorSchemeWrapper.
+    // This makes sure that the default method name and the initial Scuba image generated
+    // name is as expected.
+    companion object {
+        @OptIn(ExperimentalTvMaterial3Api::class)
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters() = arrayOf(
+            ColorSchemeWrapper(
+                "lightTheme", lightColorScheme(
+                    surface = Color(0xFFFF0090)
+                )
+            ),
+            ColorSchemeWrapper("darkTheme", darkColorScheme()),
+        )
+    }
+
+    @OptIn(ExperimentalTvMaterial3Api::class)
+    class ColorSchemeWrapper constructor(val name: String, val colorScheme: ColorScheme) {
+        override fun toString(): String {
+            return name
+        }
+    }
+}
\ No newline at end of file
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt
index 5293277..3969b0a 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt
@@ -133,7 +133,6 @@
 
     @Test
     fun settingParametersExplicitly() {
-        var textColor: Color? = null
         var textAlign: TextAlign? = null
         var fontSize: TextUnit? = null
         var fontStyle: FontStyle? = null
@@ -155,7 +154,6 @@
                         fontStyle = expectedFontStyle,
                         letterSpacing = expectedLetterSpacing,
                         onTextLayout = {
-                            textColor = it.layoutInput.style.color
                             textAlign = it.layoutInput.style.textAlign
                             fontSize = it.layoutInput.style.fontSize
                             fontStyle = it.layoutInput.style.fontStyle
@@ -168,7 +166,6 @@
 
         rule.runOnIdle {
             // explicit parameters should override values from the style.
-            Truth.assertThat(textColor).isEqualTo(expectedColor)
             Truth.assertThat(textAlign).isEqualTo(expectedTextAlign)
             Truth.assertThat(fontSize).isEqualTo(expectedFontSize)
             Truth.assertThat(fontStyle).isEqualTo(expectedFontStyle)
@@ -179,7 +176,6 @@
     // Not really an expected use-case, but we should ensure the behavior here is consistent.
     @Test
     fun settingColorAndTextStyle() {
-        var textColor: Color? = null
         var textAlign: TextAlign? = null
         var fontSize: TextUnit? = null
         var fontStyle: FontStyle? = null
@@ -202,7 +198,6 @@
                         letterSpacing = expectedLetterSpacing,
                         style = ExpectedTextStyle,
                         onTextLayout = {
-                            textColor = it.layoutInput.style.color
                             textAlign = it.layoutInput.style.textAlign
                             fontSize = it.layoutInput.style.fontSize
                             fontStyle = it.layoutInput.style.fontStyle
@@ -215,7 +210,6 @@
 
         rule.runOnIdle {
             // explicit parameters should override values from the style.
-            Truth.assertThat(textColor).isEqualTo(expectedColor)
             Truth.assertThat(textAlign).isEqualTo(expectedTextAlign)
             Truth.assertThat(fontSize).isEqualTo(expectedFontSize)
             Truth.assertThat(fontStyle).isEqualTo(expectedFontStyle)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
index 1ae2b6c..16f3dcc 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.NonRestartableComposable
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -56,6 +57,49 @@
 import kotlinx.coroutines.launch
 
 /**
+ * The [Surface] is a building block component that will be used for any element on TV such as
+ * buttons, cards, navigation, or a simple background etc. This non-interactive Surface is similar
+ * to Compose Material's Surface composable
+ *
+ * @param modifier Modifier to be applied to the layout corresponding to the surface
+ * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation will result
+ * in a darker color in light theme and lighter color in dark theme.
+ * @param shape Defines the surface's shape.
+ * @param color Color to be used on background of the Surface
+ * @param contentColor The preferred content color provided by this Surface to its children.
+ * @param border Defines a border around the Surface.
+ * @param glow Diffused shadow to be shown behind the Surface.
+ * @param content defines the [Composable] content inside the surface
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun Surface(
+    modifier: Modifier = Modifier,
+    tonalElevation: Dp = 0.dp,
+    shape: Shape = NonInteractiveSurfaceDefaults.shape,
+    color: Color = NonInteractiveSurfaceDefaults.color,
+    contentColor: Color = NonInteractiveSurfaceDefaults.contentColor,
+    border: Border = NonInteractiveSurfaceDefaults.border,
+    glow: Glow = NonInteractiveSurfaceDefaults.glow,
+    content: @Composable (BoxScope.() -> Unit)
+) {
+    SurfaceImpl(
+        modifier = modifier,
+        checked = false,
+        enabled = true,
+        tonalElevation = tonalElevation,
+        shape = shape,
+        color = color,
+        contentColor = contentColor,
+        scale = 1.0f,
+        border = border,
+        glow = glow,
+        content = content
+    )
+}
+
+/**
  * The [Surface] is a building block component that will be used for any focusable
  * element on TV such as buttons, cards, navigation, etc. This clickable Surface is similar to
  * Compose Material's Surface composable but will have more functionality that will make focus
@@ -272,7 +316,7 @@
     border: Border,
     glow: Glow,
     tonalElevation: Dp,
-    interactionSource: MutableInteractionSource,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
     content: @Composable (BoxScope.() -> Unit)
 ) {
     val focused by interactionSource.collectIsFocusedAsState()
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
index 2462a4f..8dc31ea 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
@@ -26,6 +26,38 @@
 import androidx.compose.ui.unit.dp
 
 /**
+ * Contains the default values used by a non-interactive [Surface]
+ */
+@ExperimentalTvMaterial3Api
+object NonInteractiveSurfaceDefaults {
+    /**
+     * Represents the default shape used by a non-interactive [Surface]
+     */
+    val shape: Shape @ReadOnlyComposable @Composable get() = MaterialTheme.shapes.medium
+
+    /**
+     * Represents the default container color used by a non-interactive [Surface]
+     */
+    val color: Color @ReadOnlyComposable @Composable get() = MaterialTheme.colorScheme.surface
+
+    /**
+     * Represents the default content color used by a non-interactive [Surface]
+     */
+    val contentColor: Color @ReadOnlyComposable @Composable get() =
+        MaterialTheme.colorScheme.onSurface
+
+    /**
+     * Represents the default border used by a non-interactive [Surface]
+     */
+    internal val border: Border = Border.None
+
+    /**
+     * Represents the default glow used by a non-interactive [Surface]
+     */
+    internal val glow: Glow = Glow.None
+}
+
+/**
  * Contains the default values used by clickable Surface.
  */
 @ExperimentalTvMaterial3Api
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt
index 235a961..8e0408c 100644
--- a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/CurvedSize.kt
@@ -64,11 +64,13 @@
 )
 
 /**
- * Specify the sweep (angular size) for the content.
+ * Specify the sweep (arc length) for the content in Dp. The arc length will be measured
+ * at the center of the item, except for [basicCurvedText], where it will be
+ * measured at the text baseline.
  *
  * @sample androidx.wear.compose.foundation.samples.CurvedFixedSize
  *
- * @param angularWidth Indicates the width (angular size) of the content in DP.
+ * @param angularWidth Indicates the arc length of the content in Dp.
  */
 public fun CurvedModifier.angularSizeDp(angularWidth: Dp) = this.then { child ->
     AngularWidthSizeWrapper(
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/current.txt b/wear/protolayout/protolayout-expression-pipeline/api/current.txt
index 712985a..814cdba 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/current.txt
@@ -72,8 +72,7 @@
 
 package androidx.wear.protolayout.expression.pipeline.sensor {
 
-  public interface SensorGateway extends java.io.Closeable {
-    method public void close();
+  public interface SensorGateway {
     method @UiThread public void registerSensorGatewayConsumer(int, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
     method @UiThread public void registerSensorGatewayConsumer(int, java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
     method @UiThread public void unregisterSensorGatewayConsumer(int, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt
index 712985a..814cdba 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt
@@ -72,8 +72,7 @@
 
 package androidx.wear.protolayout.expression.pipeline.sensor {
 
-  public interface SensorGateway extends java.io.Closeable {
-    method public void close();
+  public interface SensorGateway {
     method @UiThread public void registerSensorGatewayConsumer(int, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
     method @UiThread public void registerSensorGatewayConsumer(int, java.util.concurrent.Executor, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
     method @UiThread public void unregisterSensorGatewayConsumer(int, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
index f5ae829..62aa8aa 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
@@ -74,8 +74,7 @@
 
 package androidx.wear.protolayout.expression.pipeline.sensor {
 
-  public interface SensorGateway extends java.io.Closeable {
-    method public void close();
+  public interface SensorGateway {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void disableUpdates();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void enableUpdates();
     method @UiThread public void registerSensorGatewayConsumer(int, androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.Consumer);
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/sensor/SensorGateway.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/sensor/SensorGateway.java
index 6ee6c807..d6f656d 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/sensor/SensorGateway.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/sensor/SensorGateway.java
@@ -28,26 +28,14 @@
 import androidx.annotation.RestrictTo.Scope;
 import androidx.annotation.UiThread;
 
-import java.io.Closeable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
 
 /**
  * Gateway for proto layout expression library to be able to access sensor data, e.g. health data.
- *
- * <p>Implementations of this class should track a few things:
- *
- * <ul>
- *   <li>Surface lifecycle. Implementations should keep track of the surface provider, registered
- *       consumers, and deregister them all when the surface is not longer available.
- *   <li>Device state. Implementations should react to device state (i.e. ambient mode), and
- *       activity state (i.e. surface being in the foreground), and appropriately set the sampling
- *       rate of the sensor (e.g. high rate when surface is in the foreground, otherwise low-rate or
- *       off).
- * </ul>
  */
-public interface SensorGateway extends Closeable {
+public interface SensorGateway {
 
     /** Sensor data types that can be subscribed to from {@link SensorGateway}. */
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -192,8 +180,4 @@
     @UiThread
     void unregisterSensorGatewayConsumer(
             @SensorDataType int requestedDataType, @NonNull Consumer consumer);
-
-    /** See {@link Closeable#close()}. */
-    @Override
-    void close();
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
index 497638f..58f8fd6 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
@@ -405,8 +405,5 @@
                 @SensorDataType int requestedDataType, @NonNull Consumer consumer) {
             registeredConsumers.remove(consumer);
         }
-
-        @Override
-        public void close() {}
     }
 }
diff --git a/wear/protolayout/protolayout-proto/build.gradle b/wear/protolayout/protolayout-proto/build.gradle
index e48edfc..6c8c736 100644
--- a/wear/protolayout/protolayout-proto/build.gradle
+++ b/wear/protolayout/protolayout-proto/build.gradle
@@ -67,7 +67,7 @@
 
     generateProtoTasks {
         ofSourceSet("main").each { task ->
-            sourceSets.main.java.srcDir(task)
+            project.sourceSets.main.java.srcDir(task)
         }
         all().each { task ->
             task.builtins {
diff --git a/wear/tiles/tiles-proto/build.gradle b/wear/tiles/tiles-proto/build.gradle
index 8c2f80d..16bb24b 100644
--- a/wear/tiles/tiles-proto/build.gradle
+++ b/wear/tiles/tiles-proto/build.gradle
@@ -68,7 +68,7 @@
     }
     generateProtoTasks {
         ofSourceSet("main").each { task ->
-            sourceSets.main.java.srcDir(task)
+            project.sourceSets.main.java.srcDir(task)
         }
         all().each { task ->
             task.builtins {
diff --git a/wear/tiles/tiles/lint-baseline.xml b/wear/tiles/tiles/lint-baseline.xml
index 6b98413..9bf9d708 100644
--- a/wear/tiles/tiles/lint-baseline.xml
+++ b/wear/tiles/tiles/lint-baseline.xml
@@ -13,6 +13,33 @@
     <issue
         id="RequireUnstableAidlAnnotation"
         message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ResourcesData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/ResourcesData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ResourcesRequestData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/ResourcesRequestData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileAddEventData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileAddEventData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="interface TileCallback {"
         errorLine2="^">
         <location
@@ -22,6 +49,33 @@
     <issue
         id="RequireUnstableAidlAnnotation"
         message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileEnterEventData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileEnterEventData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileLeaveEventData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileLeaveEventData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="interface TileProvider {"
         errorLine2="^">
         <location
@@ -31,6 +85,33 @@
     <issue
         id="RequireUnstableAidlAnnotation"
         message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileRemoveEventData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileRemoveEventData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileRequestData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileRequestData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable TileUpdateRequestData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/tiles/TileUpdateRequestData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="interface TileUpdateRequesterService {"
         errorLine2="^">
         <location
diff --git a/wear/watchface/watchface-complications-data/lint-baseline.xml b/wear/watchface/watchface-complications-data/lint-baseline.xml
index 43bf416..11bf0b2 100644
--- a/wear/watchface/watchface-complications-data/lint-baseline.xml
+++ b/wear/watchface/watchface-complications-data/lint-baseline.xml
@@ -229,6 +229,24 @@
     <issue
         id="RequireUnstableAidlAnnotation"
         message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ComplicationData;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/wearable/complications/ComplicationData.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ComplicationProviderInfo;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/wearable/complications/ComplicationProviderInfo.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="interface IComplicationManager {"
         errorLine2="^">
         <location
diff --git a/wear/watchface/watchface-data/lint-baseline.xml b/wear/watchface/watchface-data/lint-baseline.xml
index c479c42..f5b7ee0 100644
--- a/wear/watchface/watchface-data/lint-baseline.xml
+++ b/wear/watchface/watchface-data/lint-baseline.xml
@@ -22,6 +22,105 @@
     <issue
         id="RequireUnstableAidlAnnotation"
         message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ComplicationRenderParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/ComplicationRenderParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ComplicationSlotMetadataWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/ComplicationSlotMetadataWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ComplicationStateWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/ComplicationStateWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ContentDescriptionLabel;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/wearable/watchface/accessibility/ContentDescriptionLabel.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable CrashInfoParcel;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/CrashInfoParcel.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable DefaultProviderPoliciesParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/DefaultProviderPoliciesParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable EditorStateWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/editor/data/EditorStateWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetComplicationSlotMetadataParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/GetComplicationSlotMetadataParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetUserStyleFlavorsParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/GetUserStyleFlavorsParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable GetUserStyleSchemaParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/GetUserStyleSchemaParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable HeadlessWatchFaceInstanceParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/HeadlessWatchFaceInstanceParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
         errorLine1="interface IEditorObserver {"
         errorLine2="^">
         <location
@@ -109,4 +208,148 @@
             file="src/main/aidl/androidx/wear/watchface/control/IWatchfaceReadyListener.aidl"/>
     </issue>
 
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable IdAndComplicationDataWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/IdAndComplicationDataWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable IdAndComplicationStateWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/IdAndComplicationStateWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable IdTypeAndDefaultProviderPolicyWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/IdTypeAndDefaultProviderPolicyWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ImmutableSystemState;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/ImmutableSystemState.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable ParcelableWrapper;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/wearable/watchface/ParcelableWrapper.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable RenderParametersWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/RenderParametersWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable UserStyleFlavorsWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/style/data/UserStyleFlavorsWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable UserStyleSchemaWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/style/data/UserStyleSchemaWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable UserStyleWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/style/data/UserStyleWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WallpaperInteractiveWatchFaceInstanceParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/WallpaperInteractiveWatchFaceInstanceParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WatchFaceColorsWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/WatchFaceColorsWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WatchFaceOverlayStyleWireFormat;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/WatchFaceOverlayStyleWireFormat.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WatchFaceRenderParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/WatchFaceRenderParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WatchFaceStyle;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/android/support/wearable/watchface/WatchFaceStyle.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WatchFaceSurfaceRenderParams;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/control/data/WatchFaceSurfaceRenderParams.aidl"/>
+    </issue>
+
+    <issue
+        id="RequireUnstableAidlAnnotation"
+        message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
+        errorLine1="parcelable WatchUiState;"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/aidl/androidx/wear/watchface/data/WatchUiState.aidl"/>
+    </issue>
+
 </issues>
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
index 919b83b..67ff1ec 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
@@ -16,7 +16,6 @@
 
 package com.example.androidx.webkit;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.os.Bundle;
 import android.util.Base64;
@@ -38,7 +37,6 @@
  * Activity allows setting WebViews to use UA darkening, Web theme darkening (media query vs
  * meta-tag) or both.
  */
-@SuppressLint("RestrictedApi")
 public class ForceDarkStrategyActivity extends AppCompatActivity {
     private final String mNoDarkThemeSupport = Base64.encodeToString((
                       "<html>"
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
index 873f6572..fadd41a 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerActivity.java
@@ -52,7 +52,6 @@
 /**
  * An {@link Activity} to exercise WebMessageListener related functionality.
  */
-@SuppressLint("RestrictedApi")
 public class WebMessageListenerActivity extends AppCompatActivity {
     private TextView mTextView;
     private final Uri mExampleUri = new Uri.Builder()
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerMaliciousWebsiteActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerMaliciousWebsiteActivity.java
index aae0cdb7..1fcd28f 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerMaliciousWebsiteActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/WebMessageListenerMaliciousWebsiteActivity.java
@@ -45,7 +45,6 @@
 /**
  * An {@link Activity} to show how WebMessageListener deals with malicious websites.
  */
-@SuppressLint("RestrictedApi")
 public class WebMessageListenerMaliciousWebsiteActivity extends AppCompatActivity {
     private final Uri mMaliciousUrl = new Uri.Builder().scheme("https").authority(
             "malicious.com").appendPath("androidx_webkit").appendPath("example").appendPath(
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
index ca1dd94..7cfc7ee 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
@@ -131,20 +131,34 @@
                 }
             }
             TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE -> {
-                return SplitAttributes.Builder()
-                    .setSplitType(SPLIT_TYPE_HINGE)
-                    .setLayoutDirection(
-                        if (shouldReversed) {
-                            BOTTOM_TO_TOP
-                        } else {
-                            TOP_TO_BOTTOM
-                        }
-                    ).build()
+                return if (config.screenWidthDp < 600) {
+                    SplitAttributes.Builder()
+                        .setSplitType(SPLIT_TYPE_EQUAL)
+                        .setLayoutDirection(
+                            if (shouldReversed) {
+                                BOTTOM_TO_TOP
+                            } else {
+                                TOP_TO_BOTTOM
+                            }
+                        )
+                        .build()
+                } else {
+                    SplitAttributes.Builder()
+                        .setSplitType(SPLIT_TYPE_EQUAL)
+                        .setLayoutDirection(
+                            if (shouldReversed) {
+                                RIGHT_TO_LEFT
+                            } else {
+                                LEFT_TO_RIGHT
+                            }
+                        )
+                        .build()
+                }
             }
             TAG_SHOW_DIFFERENT_LAYOUT_WITH_SIZE + SUFFIX_AND_FULLSCREEN_IN_BOOK_MODE -> {
                 return if (isBookMode) {
                     expandContainersAttrs
-                } else if (config.screenWidthDp <= 600) {
+                } else if (config.screenWidthDp < 600) {
                     SplitAttributes.Builder()
                         .setSplitType(SPLIT_TYPE_EQUAL)
                         .setLayoutDirection(
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
index 726726f..9f80a5f 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
@@ -84,6 +84,21 @@
         activityA = ComponentName(this, SplitDeviceStateActivityA::class.java.name)
         activityB = ComponentName(this, SplitDeviceStateActivityB::class.java.name)
 
+        val radioGroup = viewBinding.splitAttributesOptionsRadioGroup
+        if (componentName == activityA) {
+            // Set to the first option
+            radioGroup.check(R.id.use_default_split_attributes)
+            onCheckedChanged(radioGroup, radioGroup.checkedRadioButtonId)
+            radioGroup.setOnCheckedChangeListener(this)
+        } else {
+            // Only update split pair rule on the primary Activity. The secondary Activity can only
+            // finish itself to prevent confusing users. We only apply the rule when the Activity is
+            // launched from the primary.
+            viewBinding.chooseLayoutTextView.visibility = View.GONE
+            radioGroup.visibility = View.GONE
+            viewBinding.launchActivityToSide.text = "Finish this Activity"
+        }
+
         viewBinding.showHorizontalLayoutInTabletopCheckBox.setOnCheckedChangeListener(this)
         viewBinding.showFullscreenInBookModeCheckBox.setOnCheckedChangeListener(this)
         viewBinding.swapPrimarySecondaryPositionCheckBox.setOnCheckedChangeListener(this)
diff --git a/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml b/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml
index b996f71..099490a 100644
--- a/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml
+++ b/window/window-demos/demo/src/main/res/layout/activity_split_device_state_layout.xml
@@ -143,21 +143,6 @@
                 android:text="Swap the position of primary and secondary container" />
         </RadioGroup>
 
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:layout_marginTop="10dp"
-            android:layout_marginBottom="10dp"
-            android:background="#AAAAAA" />
-
-        <!-- Dropdown for animation background color -->
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:layout_marginTop="10dp"
-            android:layout_marginBottom="10dp"
-            android:background="#AAAAAA" />
-
         <Button
             android:id="@+id/launch_activity_to_side"
             android:layout_width="wrap_content"
@@ -176,4 +161,4 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"/>
     </LinearLayout>
-</ScrollView>
\ No newline at end of file
+</ScrollView>