Merge "Cleanup lint baseline after aosp/2995323" into androidx-main
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 733504c..2af4575 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,22 +1,22 @@
 # Global owners:
 # Unless a later match takes precedence, global owners will be # requested for
 # review when someone opens a pull request.
-*          @dlam @yigit
+*                  @dlam @yigit
 
 # Owners for each library group:
-/activity*          @jbw0033 @ianhanniballake
-/appcompat/*        @alanv
-/biometric/*        @jbolinger
-/collection/*       @dlam
-/compose/compiler/* @jimgoog @lelandrichardson
-/compose/runtime/*  @jimgoog @lelandrichardson
-/core/*             @alanv
-/datastore/*        @rohitsat13 @yigit
-/fragment/*         @jbw0033 @ianhanniballake
-/lifecycle/*        @jbw0033 @ianhanniballake
-/navigation/*       @jbw0033 @ianhanniballake @claraf3
-/paging/*           @claraf3 @ianhanniballake
-/room/*             @droid-wan-kenobi @danysantiago @svasilinets
-/work/*             @svasilinets @tikurahul
-
+/activity/         @jbw0033 @ianhanniballake
+/annotation/       @jbw0033 @ianhanniballake
+/appcompat/        @alanv
+/biometric/        @jbolinger
+/collection/       @dlam
+/compose/compiler/ @jimgoog @lelandrichardson
+/compose/runtime/  @jimgoog @lelandrichardson
+/core/             @alanv
+/datastore/        @rohitsat13 @yigit
+/fragment/         @jbw0033 @ianhanniballake
+/lifecycle/        @jbw0033 @ianhanniballake
+/navigation/       @jbw0033 @ianhanniballake @claraf3
+/paging/           @claraf3 @ianhanniballake
+/room/             @droid-wan-kenobi @danysantiago @svasilinets
+/work/             @svasilinets @tikurahul
 
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt
index def0983..1d6776c 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt
@@ -24,12 +24,12 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt
index 4af3fc8..e7bf0f8 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/PredictiveBackHandlerTest.kt
@@ -29,12 +29,12 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
diff --git a/activity/activity/api/1.9.0-beta01.txt b/activity/activity/api/1.9.0-beta01.txt
index a4c9c09..a7116a8 100644
--- a/activity/activity/api/1.9.0-beta01.txt
+++ b/activity/activity/api/1.9.0-beta01.txt
@@ -346,7 +346,7 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor @Deprecated public ActivityResultContracts.CreateDocument();
     ctor public ActivityResultContracts.CreateDocument(String mimeType);
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
@@ -354,7 +354,7 @@
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor public ActivityResultContracts.GetContent();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
@@ -368,14 +368,14 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String[] input);
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri?,android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, android.net.Uri? input);
@@ -389,7 +389,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.net.Uri> {
+  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.net.Uri?> {
     ctor public ActivityResultContracts.PickContact();
     method public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
@@ -402,7 +402,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri> {
+  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri?> {
     ctor public ActivityResultContracts.PickVisualMedia();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
@@ -492,14 +492,14 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.graphics.Bitmap> {
+  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.graphics.Bitmap?> {
     ctor public ActivityResultContracts.TakePicturePreview();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
     method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap> {
+  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap?> {
     ctor @Deprecated public ActivityResultContracts.TakeVideo();
     method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
     method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index a4c9c09..a7116a8 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -346,7 +346,7 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor @Deprecated public ActivityResultContracts.CreateDocument();
     ctor public ActivityResultContracts.CreateDocument(String mimeType);
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
@@ -354,7 +354,7 @@
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor public ActivityResultContracts.GetContent();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
@@ -368,14 +368,14 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String[] input);
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri?,android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, android.net.Uri? input);
@@ -389,7 +389,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.net.Uri> {
+  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.net.Uri?> {
     ctor public ActivityResultContracts.PickContact();
     method public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
@@ -402,7 +402,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri> {
+  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri?> {
     ctor public ActivityResultContracts.PickVisualMedia();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
@@ -492,14 +492,14 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.graphics.Bitmap> {
+  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.graphics.Bitmap?> {
     ctor public ActivityResultContracts.TakePicturePreview();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
     method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap> {
+  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap?> {
     ctor @Deprecated public ActivityResultContracts.TakeVideo();
     method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
     method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
diff --git a/activity/activity/api/restricted_1.9.0-beta01.txt b/activity/activity/api/restricted_1.9.0-beta01.txt
index 3cb9aa0..3179bef 100644
--- a/activity/activity/api/restricted_1.9.0-beta01.txt
+++ b/activity/activity/api/restricted_1.9.0-beta01.txt
@@ -345,7 +345,7 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor @Deprecated public ActivityResultContracts.CreateDocument();
     ctor public ActivityResultContracts.CreateDocument(String mimeType);
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
@@ -353,7 +353,7 @@
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor public ActivityResultContracts.GetContent();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
@@ -367,14 +367,14 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String[] input);
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri?,android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, android.net.Uri? input);
@@ -388,7 +388,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.net.Uri> {
+  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.net.Uri?> {
     ctor public ActivityResultContracts.PickContact();
     method public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
@@ -401,7 +401,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri> {
+  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri?> {
     ctor public ActivityResultContracts.PickVisualMedia();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
@@ -491,14 +491,14 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.graphics.Bitmap> {
+  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.graphics.Bitmap?> {
     ctor public ActivityResultContracts.TakePicturePreview();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
     method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap> {
+  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap?> {
     ctor @Deprecated public ActivityResultContracts.TakeVideo();
     method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
     method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index 3cb9aa0..3179bef 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -345,7 +345,7 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.CreateDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor @Deprecated public ActivityResultContracts.CreateDocument();
     ctor public ActivityResultContracts.CreateDocument(String mimeType);
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
@@ -353,7 +353,7 @@
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri> {
+  public static class ActivityResultContracts.GetContent extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,android.net.Uri?> {
     ctor public ActivityResultContracts.GetContent();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String input);
@@ -367,14 +367,14 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri> {
+  public static class ActivityResultContracts.OpenDocument extends androidx.activity.result.contract.ActivityResultContract<java.lang.String[],android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocument();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, String[] input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, String[] input);
     method public final android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.net.Uri> {
+  @RequiresApi(21) public static class ActivityResultContracts.OpenDocumentTree extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri?,android.net.Uri?> {
     ctor public ActivityResultContracts.OpenDocumentTree();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, android.net.Uri? input);
@@ -388,7 +388,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.net.Uri> {
+  public static final class ActivityResultContracts.PickContact extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.net.Uri?> {
     ctor public ActivityResultContracts.PickContact();
     method public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public android.net.Uri? parseResult(int resultCode, android.content.Intent? intent);
@@ -401,7 +401,7 @@
     method public final java.util.List<android.net.Uri> parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri> {
+  public static class ActivityResultContracts.PickVisualMedia extends androidx.activity.result.contract.ActivityResultContract<androidx.activity.result.PickVisualMediaRequest,android.net.Uri?> {
     ctor public ActivityResultContracts.PickVisualMedia();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.net.Uri?>? getSynchronousResult(android.content.Context context, androidx.activity.result.PickVisualMediaRequest input);
@@ -491,14 +491,14 @@
     method public final Boolean parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void,android.graphics.Bitmap> {
+  public static class ActivityResultContracts.TakePicturePreview extends androidx.activity.result.contract.ActivityResultContract<java.lang.Void?,android.graphics.Bitmap?> {
     ctor public ActivityResultContracts.TakePicturePreview();
     method @CallSuper public android.content.Intent createIntent(android.content.Context context, Void? input);
     method public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, Void? input);
     method public final android.graphics.Bitmap? parseResult(int resultCode, android.content.Intent? intent);
   }
 
-  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap> {
+  @Deprecated public static class ActivityResultContracts.TakeVideo extends androidx.activity.result.contract.ActivityResultContract<android.net.Uri,android.graphics.Bitmap?> {
     ctor @Deprecated public ActivityResultContracts.TakeVideo();
     method @Deprecated @CallSuper public android.content.Intent createIntent(android.content.Context context, android.net.Uri input);
     method @Deprecated public final androidx.activity.result.contract.ActivityResultContract.SynchronousResult<android.graphics.Bitmap?>? getSynchronousResult(android.content.Context context, android.net.Uri input);
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index 94d10c5..4068049 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -30,7 +30,7 @@
     api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
     api("androidx.savedstate:savedstate:1.2.1")
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     implementation("androidx.tracing:tracing:1.0.0")
     api(libs.kotlinStdlib)
 
diff --git a/activity/integration-tests/macrobenchmark/build.gradle b/activity/integration-tests/macrobenchmark/build.gradle
index a1b080f..f60cc3f 100644
--- a/activity/integration-tests/macrobenchmark/build.gradle
+++ b/activity/integration-tests/macrobenchmark/build.gradle
@@ -29,6 +29,11 @@
     experimentalProperties["android.experimental.self-instrumenting"] = true
 }
 
+// Create a release build type and make sure it's the only one enabled.
+// This is needed because we benchmark the release build type only.
+android.buildTypes { release {} }
+androidComponents { beforeVariants(selector().all()) { enabled = buildType == 'release' } }
+
 dependencies {
     implementation(project(":benchmark:benchmark-junit4"))
     implementation(project(":benchmark:benchmark-macro-junit4"))
diff --git a/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartMacroBenchmark.kt b/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartBenchmark.kt
similarity index 94%
rename from activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartMacroBenchmark.kt
rename to activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartBenchmark.kt
index 2bf9360..f28ba33 100644
--- a/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartMacroBenchmark.kt
+++ b/activity/integration-tests/macrobenchmark/src/main/java/androidx/activity/integration/macrobenchmark/ActivityStartBenchmark.kt
@@ -27,14 +27,14 @@
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
-class ActivityStartMacroBenchmark {
+class ActivityStartBenchmark {
 
     @get:Rule
     val benchmarkRule = MacrobenchmarkRule()
 
     @Test
     fun startup() = benchmarkRule.measureStartup(
-        compilationMode = CompilationMode.Full(),
+        compilationMode = CompilationMode.DEFAULT,
         startupMode = StartupMode.COLD,
         packageName = "androidx.activity.integration.macrobenchmark.target",
         metrics = listOf(StartupTimingMetric()),
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/AnnotationRetentionDetector.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/AnnotationRetentionDetector.kt
index 50643b2..0b9a0bc 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/AnnotationRetentionDetector.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/AnnotationRetentionDetector.kt
@@ -49,7 +49,7 @@
     private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
         override fun visitAnnotation(node: UAnnotation) {
             val annotated = node.uastParent as? UAnnotated ?: return
-            val isKotlin = isKotlin(annotated.sourcePsi)
+            val isKotlin = isKotlin(annotated.lang)
             val qualifiedName = node.qualifiedName
 
             if (isKotlin && qualifiedName == JAVA_REQUIRES_OPT_IN_ANNOTATION) {
@@ -69,7 +69,7 @@
          * if it does not.
          */
         private fun validateAnnotationRetention(annotated: UAnnotated) {
-            val isKotlin = isKotlin(annotated.sourcePsi)
+            val isKotlin = isKotlin(annotated.lang)
             val annotations = context.evaluator.getAllAnnotations(annotated, false)
 
             val annotationClass: String
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
index 719a480..7b3fab7 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
@@ -473,7 +473,7 @@
                 // compiler handles that already. Allow either Java or Kotlin annotations, since
                 // we can enforce both and it's possible that a Kotlin-sourced experimental library
                 // is being used from Java without the Kotlin stdlib in the classpath.
-                if (!isKotlin(usage.sourcePsi)) {
+                if (!isKotlin(usage.lang)) {
                     checkExperimentalUsage(
                         context,
                         annotation,
@@ -633,7 +633,7 @@
         val lintFixes = fix().alternatives()
         var addedFix = false
         usage.getContainingUMethod()?.let { containingMethod ->
-            val isKotlin = isKotlin(usage.sourcePsi)
+            val isKotlin = isKotlin(usage.lang)
             val optInAnnotation = if (isKotlin) {
                 "@androidx.annotation.OptIn($annotation::class)"
             } else {
diff --git a/annotation/annotation/api/1.8.0-beta01.txt b/annotation/annotation/api/1.8.0-beta01.txt
new file mode 100644
index 0000000..dababc5
--- /dev/null
+++ b/annotation/annotation/api/1.8.0-beta01.txt
@@ -0,0 +1,372 @@
+// Signature format: 4.0
+package androidx.annotation {
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AnimRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AnimatorRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AnyRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface AnyThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface ArrayRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AttrRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface BinderThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface BoolRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface CallSuper {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface CheckResult {
+    method public abstract String suggest() default "";
+    property public abstract String suggest;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface ChecksSdkIntAtLeast {
+    method public abstract int api() default -1;
+    method public abstract String codename() default "";
+    method public abstract int extension() default 0;
+    method public abstract int lambda() default -1;
+    method public abstract int parameter() default -1;
+    property public abstract int api;
+    property public abstract String codename;
+    property public abstract int extension;
+    property public abstract int lambda;
+    property public abstract int parameter;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface ColorInt {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface ColorLong {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface ColorRes {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.CONSTRUCTOR) public @interface ContentView {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.CONSTRUCTOR}) public @interface DeprecatedSinceApi {
+    method public abstract int api();
+    method public abstract String message() default "";
+    property public abstract int api;
+    property public abstract String message;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface DimenRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface Dimension {
+    method public abstract int unit() default androidx.annotation.Dimension.PX;
+    property public abstract int unit;
+    field public static final androidx.annotation.Dimension.Companion Companion;
+    field public static final int DP = 0; // 0x0
+    field public static final int PX = 1; // 0x1
+    field public static final int SP = 2; // 0x2
+  }
+
+  public static final class Dimension.Companion {
+    field public static final int DP = 0; // 0x0
+    field public static final int PX = 1; // 0x1
+    field public static final int SP = 2; // 0x2
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS}) public @interface Discouraged {
+    method public abstract String message();
+    property public abstract String message;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface DisplayContext {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface DoNotInline {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface DrawableRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public @interface EmptySuper {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface FloatRange {
+    method public abstract double from() default kotlin.jvm.internal.DoubleCompanionObject.NEGATIVE_INFINITY;
+    method public abstract boolean fromInclusive() default true;
+    method public abstract double to() default kotlin.jvm.internal.DoubleCompanionObject.POSITIVE_INFINITY;
+    method public abstract boolean toInclusive() default true;
+    property public abstract double from;
+    property public abstract boolean fromInclusive;
+    property public abstract double to;
+    property public abstract boolean toInclusive;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface FontRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface FractionRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface GravityInt {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface GuardedBy {
+    method public abstract String value();
+    property public abstract String value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface HalfFloat {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface IdRes {
+  }
+
+  @Deprecated @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface InspectableProperty {
+    method @Deprecated public abstract int attributeId() default 0;
+    method @Deprecated public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping();
+    method @Deprecated public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping();
+    method @Deprecated public abstract boolean hasAttributeId() default true;
+    method @Deprecated public abstract String name() default "";
+    method @Deprecated public abstract androidx.annotation.InspectableProperty.ValueType valueType() default androidx.annotation.InspectableProperty.ValueType.INFERRED;
+    property @Deprecated public abstract int attributeId;
+    property @Deprecated public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping;
+    property @Deprecated public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping;
+    property @Deprecated public abstract boolean hasAttributeId;
+    property @Deprecated public abstract String name;
+    property @Deprecated public abstract androidx.annotation.InspectableProperty.ValueType valueType;
+  }
+
+  @Deprecated @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS}) public static @interface InspectableProperty.EnumEntry {
+    method @Deprecated public abstract String name();
+    method @Deprecated public abstract int value();
+    property @Deprecated public abstract String name;
+    property @Deprecated public abstract int value;
+  }
+
+  @Deprecated @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS}) public static @interface InspectableProperty.FlagEntry {
+    method @Deprecated public abstract int mask() default 0;
+    method @Deprecated public abstract String name();
+    method @Deprecated public abstract int target();
+    property @Deprecated public abstract int mask;
+    property @Deprecated public abstract String name;
+    property @Deprecated public abstract int target;
+  }
+
+  @Deprecated public enum InspectableProperty.ValueType {
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType COLOR;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType GRAVITY;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INFERRED;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INT_ENUM;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INT_FLAG;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType NONE;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType RESOURCE_ID;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS) public @interface IntDef {
+    method public abstract boolean flag() default false;
+    method public abstract boolean open() default false;
+    method public abstract int[] value();
+    property public abstract boolean flag;
+    property public abstract boolean open;
+    property public abstract int[] value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface IntRange {
+    method public abstract long from() default kotlin.jvm.internal.LongCompanionObject.MIN_VALUE;
+    method public abstract long to() default kotlin.jvm.internal.LongCompanionObject.MAX_VALUE;
+    property public abstract long from;
+    property public abstract long to;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface IntegerRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface InterpolatorRes {
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.PACKAGE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FILE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface Keep {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface LayoutRes {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS) public @interface LongDef {
+    method public abstract boolean flag() default false;
+    method public abstract boolean open() default false;
+    method public abstract long[] value();
+    property public abstract boolean flag;
+    property public abstract boolean open;
+    property public abstract long[] value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface MainThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface MenuRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface NavigationRes {
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FILE}) public @interface NonNull {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface NonUiContext {
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FILE}) public @interface Nullable {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CLASS}) public @interface OpenForTesting {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface PluralsRes {
+  }
+
+  @Dimension(unit=androidx.annotation.Dimension.Companion.PX) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface Px {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface RawRes {
+  }
+
+  @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.CONSTRUCTOR}) public @interface ReplaceWith {
+    method public abstract String expression();
+    method public abstract String[] imports();
+    property public abstract String expression;
+    property public abstract String[] imports;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public @interface RequiresApi {
+    method public abstract int api() default 1;
+    method public abstract int value() default 1;
+    property public abstract int api;
+    property public abstract int value;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public @interface RequiresExtension {
+    method public abstract int extension();
+    method public abstract int version();
+    property public abstract int extension;
+    property public abstract int version;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public static @interface RequiresExtension.Container {
+    method public abstract androidx.annotation.RequiresExtension[] value();
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR}) public @interface RequiresFeature {
+    method public abstract String enforcement();
+    method public abstract String name();
+    property public abstract String enforcement;
+    property public abstract String name;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface RequiresPermission {
+    method public abstract String[] allOf();
+    method public abstract String[] anyOf();
+    method public abstract boolean conditional() default false;
+    method public abstract String value() default "";
+    property public abstract String[] allOf;
+    property public abstract String[] anyOf;
+    property public abstract boolean conditional;
+    property public abstract String value;
+  }
+
+  @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public static @interface RequiresPermission.Read {
+    method public abstract androidx.annotation.RequiresPermission value() default androidx.annotation.RequiresPermission();
+    property public abstract androidx.annotation.RequiresPermission value;
+  }
+
+  @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public static @interface RequiresPermission.Write {
+    method public abstract androidx.annotation.RequiresPermission value() default androidx.annotation.RequiresPermission();
+    property public abstract androidx.annotation.RequiresPermission value;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public @interface RestrictTo {
+    method public abstract androidx.annotation.RestrictTo.Scope[] value();
+    property public abstract androidx.annotation.RestrictTo.Scope[] value;
+  }
+
+  public enum RestrictTo.Scope {
+    enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP_PREFIX;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.CLASS}) public @interface ReturnThis {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface Size {
+    method public abstract long max() default kotlin.jvm.internal.LongCompanionObject.MAX_VALUE;
+    method public abstract long min() default kotlin.jvm.internal.LongCompanionObject.MIN_VALUE;
+    method public abstract long multiple() default 1;
+    method public abstract long value() default -1;
+    property public abstract long max;
+    property public abstract long min;
+    property public abstract long multiple;
+    property public abstract long value;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS) public @interface StringDef {
+    method public abstract boolean open() default false;
+    method public abstract String[] value();
+    property public abstract boolean open;
+    property public abstract String[] value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface StringRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface StyleRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface StyleableRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface TransitionRes {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface UiContext {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface UiThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface VisibleForTesting {
+    method public abstract int otherwise() default androidx.annotation.VisibleForTesting.PRIVATE;
+    property public abstract int otherwise;
+    field public static final androidx.annotation.VisibleForTesting.Companion Companion;
+    field public static final int NONE = 5; // 0x5
+    field public static final int PACKAGE_PRIVATE = 3; // 0x3
+    field public static final int PRIVATE = 2; // 0x2
+    field public static final int PROTECTED = 4; // 0x4
+  }
+
+  public static final class VisibleForTesting.Companion {
+    field public static final int NONE = 5; // 0x5
+    field public static final int PACKAGE_PRIVATE = 3; // 0x3
+    field public static final int PRIVATE = 2; // 0x2
+    field public static final int PROTECTED = 4; // 0x4
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface WorkerThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface XmlRes {
+  }
+
+}
+
diff --git a/annotation/annotation/api/restricted_1.8.0-beta01.txt b/annotation/annotation/api/restricted_1.8.0-beta01.txt
new file mode 100644
index 0000000..dababc5
--- /dev/null
+++ b/annotation/annotation/api/restricted_1.8.0-beta01.txt
@@ -0,0 +1,372 @@
+// Signature format: 4.0
+package androidx.annotation {
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AnimRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AnimatorRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AnyRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface AnyThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface ArrayRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface AttrRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface BinderThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface BoolRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface CallSuper {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface CheckResult {
+    method public abstract String suggest() default "";
+    property public abstract String suggest;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface ChecksSdkIntAtLeast {
+    method public abstract int api() default -1;
+    method public abstract String codename() default "";
+    method public abstract int extension() default 0;
+    method public abstract int lambda() default -1;
+    method public abstract int parameter() default -1;
+    property public abstract int api;
+    property public abstract String codename;
+    property public abstract int extension;
+    property public abstract int lambda;
+    property public abstract int parameter;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface ColorInt {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface ColorLong {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface ColorRes {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.CONSTRUCTOR) public @interface ContentView {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.CONSTRUCTOR}) public @interface DeprecatedSinceApi {
+    method public abstract int api();
+    method public abstract String message() default "";
+    property public abstract int api;
+    property public abstract String message;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface DimenRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface Dimension {
+    method public abstract int unit() default androidx.annotation.Dimension.PX;
+    property public abstract int unit;
+    field public static final androidx.annotation.Dimension.Companion Companion;
+    field public static final int DP = 0; // 0x0
+    field public static final int PX = 1; // 0x1
+    field public static final int SP = 2; // 0x2
+  }
+
+  public static final class Dimension.Companion {
+    field public static final int DP = 0; // 0x0
+    field public static final int PX = 1; // 0x1
+    field public static final int SP = 2; // 0x2
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS}) public @interface Discouraged {
+    method public abstract String message();
+    property public abstract String message;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface DisplayContext {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface DoNotInline {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface DrawableRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.FUNCTION) public @interface EmptySuper {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface FloatRange {
+    method public abstract double from() default kotlin.jvm.internal.DoubleCompanionObject.NEGATIVE_INFINITY;
+    method public abstract boolean fromInclusive() default true;
+    method public abstract double to() default kotlin.jvm.internal.DoubleCompanionObject.POSITIVE_INFINITY;
+    method public abstract boolean toInclusive() default true;
+    property public abstract double from;
+    property public abstract boolean fromInclusive;
+    property public abstract double to;
+    property public abstract boolean toInclusive;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface FontRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface FractionRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface GravityInt {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface GuardedBy {
+    method public abstract String value();
+    property public abstract String value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FIELD}) public @interface HalfFloat {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface IdRes {
+  }
+
+  @Deprecated @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER}) public @interface InspectableProperty {
+    method @Deprecated public abstract int attributeId() default 0;
+    method @Deprecated public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping();
+    method @Deprecated public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping();
+    method @Deprecated public abstract boolean hasAttributeId() default true;
+    method @Deprecated public abstract String name() default "";
+    method @Deprecated public abstract androidx.annotation.InspectableProperty.ValueType valueType() default androidx.annotation.InspectableProperty.ValueType.INFERRED;
+    property @Deprecated public abstract int attributeId;
+    property @Deprecated public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping;
+    property @Deprecated public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping;
+    property @Deprecated public abstract boolean hasAttributeId;
+    property @Deprecated public abstract String name;
+    property @Deprecated public abstract androidx.annotation.InspectableProperty.ValueType valueType;
+  }
+
+  @Deprecated @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS}) public static @interface InspectableProperty.EnumEntry {
+    method @Deprecated public abstract String name();
+    method @Deprecated public abstract int value();
+    property @Deprecated public abstract String name;
+    property @Deprecated public abstract int value;
+  }
+
+  @Deprecated @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS}) public static @interface InspectableProperty.FlagEntry {
+    method @Deprecated public abstract int mask() default 0;
+    method @Deprecated public abstract String name();
+    method @Deprecated public abstract int target();
+    property @Deprecated public abstract int mask;
+    property @Deprecated public abstract String name;
+    property @Deprecated public abstract int target;
+  }
+
+  @Deprecated public enum InspectableProperty.ValueType {
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType COLOR;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType GRAVITY;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INFERRED;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INT_ENUM;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType INT_FLAG;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType NONE;
+    enum_constant @Deprecated public static final androidx.annotation.InspectableProperty.ValueType RESOURCE_ID;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS) public @interface IntDef {
+    method public abstract boolean flag() default false;
+    method public abstract boolean open() default false;
+    method public abstract int[] value();
+    property public abstract boolean flag;
+    property public abstract boolean open;
+    property public abstract int[] value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface IntRange {
+    method public abstract long from() default kotlin.jvm.internal.LongCompanionObject.MIN_VALUE;
+    method public abstract long to() default kotlin.jvm.internal.LongCompanionObject.MAX_VALUE;
+    property public abstract long from;
+    property public abstract long to;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface IntegerRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface InterpolatorRes {
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.PACKAGE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FILE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface Keep {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface LayoutRes {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS) public @interface LongDef {
+    method public abstract boolean flag() default false;
+    method public abstract boolean open() default false;
+    method public abstract long[] value();
+    property public abstract boolean flag;
+    property public abstract boolean open;
+    property public abstract long[] value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface MainThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface MenuRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface NavigationRes {
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FILE}) public @interface NonNull {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface NonUiContext {
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FILE}) public @interface Nullable {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CLASS}) public @interface OpenForTesting {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface PluralsRes {
+  }
+
+  @Dimension(unit=androidx.annotation.Dimension.Companion.PX) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface Px {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface RawRes {
+  }
+
+  @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.CONSTRUCTOR}) public @interface ReplaceWith {
+    method public abstract String expression();
+    method public abstract String[] imports();
+    property public abstract String expression;
+    property public abstract String[] imports;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public @interface RequiresApi {
+    method public abstract int api() default 1;
+    method public abstract int value() default 1;
+    property public abstract int api;
+    property public abstract int value;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public @interface RequiresExtension {
+    method public abstract int extension();
+    method public abstract int version();
+    property public abstract int extension;
+    property public abstract int version;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Repeatable @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public static @interface RequiresExtension.Container {
+    method public abstract androidx.annotation.RequiresExtension[] value();
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR}) public @interface RequiresFeature {
+    method public abstract String enforcement();
+    method public abstract String name();
+    property public abstract String enforcement;
+    property public abstract String name;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface RequiresPermission {
+    method public abstract String[] allOf();
+    method public abstract String[] anyOf();
+    method public abstract boolean conditional() default false;
+    method public abstract String value() default "";
+    property public abstract String[] allOf;
+    property public abstract String[] anyOf;
+    property public abstract boolean conditional;
+    property public abstract String value;
+  }
+
+  @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public static @interface RequiresPermission.Read {
+    method public abstract androidx.annotation.RequiresPermission value() default androidx.annotation.RequiresPermission();
+    property public abstract androidx.annotation.RequiresPermission value;
+  }
+
+  @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public static @interface RequiresPermission.Write {
+    method public abstract androidx.annotation.RequiresPermission value() default androidx.annotation.RequiresPermission();
+    property public abstract androidx.annotation.RequiresPermission value;
+  }
+
+  @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.FILE}) public @interface RestrictTo {
+    method public abstract androidx.annotation.RestrictTo.Scope[] value();
+    property public abstract androidx.annotation.RestrictTo.Scope[] value;
+  }
+
+  public enum RestrictTo.Scope {
+    enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP_PREFIX;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
+    enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.CLASS}) public @interface ReturnThis {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS}) public @interface Size {
+    method public abstract long max() default kotlin.jvm.internal.LongCompanionObject.MAX_VALUE;
+    method public abstract long min() default kotlin.jvm.internal.LongCompanionObject.MIN_VALUE;
+    method public abstract long multiple() default 1;
+    method public abstract long value() default -1;
+    property public abstract long max;
+    property public abstract long min;
+    property public abstract long multiple;
+    property public abstract long value;
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS) public @interface StringDef {
+    method public abstract boolean open() default false;
+    method public abstract String[] value();
+    property public abstract boolean open;
+    property public abstract String[] value;
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface StringRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface StyleRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface StyleableRes {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface TransitionRes {
+  }
+
+  @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.SOURCE) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD}) public @interface UiContext {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface UiThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface VisibleForTesting {
+    method public abstract int otherwise() default androidx.annotation.VisibleForTesting.PRIVATE;
+    property public abstract int otherwise;
+    field public static final androidx.annotation.VisibleForTesting.Companion Companion;
+    field public static final int NONE = 5; // 0x5
+    field public static final int PACKAGE_PRIVATE = 3; // 0x3
+    field public static final int PRIVATE = 2; // 0x2
+    field public static final int PROTECTED = 4; // 0x4
+  }
+
+  public static final class VisibleForTesting.Companion {
+    field public static final int NONE = 5; // 0x5
+    field public static final int PACKAGE_PRIVATE = 3; // 0x3
+    field public static final int PRIVATE = 2; // 0x2
+    field public static final int PROTECTED = 4; // 0x4
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER}) public @interface WorkerThread {
+  }
+
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.VALUE_PARAMETER, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE}) public @interface XmlRes {
+  }
+
+}
+
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index e423942..1387c96 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -32,7 +32,7 @@
     api("androidx.drawerlayout:drawerlayout:1.0.0")
     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.profileinstaller:profileinstaller:1.3.1")
     implementation("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
     api("androidx.savedstate:savedstate:1.2.1")
 
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index c814811..c221d9e 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -45,7 +45,7 @@
     method public abstract Class<? extends androidx.appsearch.app.LongSerializer<?>!> serializer() default androidx.appsearch.annotation.Document.LongProperty.DefaultSerializer.class;
   }
 
-  public static final class Document.LongProperty.DefaultSerializer implements androidx.appsearch.app.LongSerializer<java.lang.Long> {
+  public static final class Document.LongProperty.DefaultSerializer implements androidx.appsearch.app.LongSerializer<java.lang.Long!> {
     ctor public Document.LongProperty.DefaultSerializer();
     method public Long deserialize(long);
     method public long serialize(Long);
@@ -66,7 +66,7 @@
     method public abstract int tokenizerType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN;
   }
 
-  public static final class Document.StringProperty.DefaultSerializer implements androidx.appsearch.app.StringSerializer<java.lang.String> {
+  public static final class Document.StringProperty.DefaultSerializer implements androidx.appsearch.app.StringSerializer<java.lang.String!> {
     ctor public Document.StringProperty.DefaultSerializer();
     method public String deserialize(String);
     method public String serialize(String);
@@ -405,7 +405,7 @@
     method public byte[] getSha256Certificate();
   }
 
-  public class PropertyPath implements java.lang.Iterable<androidx.appsearch.app.PropertyPath.PathSegment> {
+  public class PropertyPath implements java.lang.Iterable<androidx.appsearch.app.PropertyPath.PathSegment!> {
     ctor public PropertyPath(String);
     ctor public PropertyPath(java.util.List<androidx.appsearch.app.PropertyPath.PathSegment!>);
     method public androidx.appsearch.app.PropertyPath.PathSegment get(int);
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index c814811..c221d9e 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -45,7 +45,7 @@
     method public abstract Class<? extends androidx.appsearch.app.LongSerializer<?>!> serializer() default androidx.appsearch.annotation.Document.LongProperty.DefaultSerializer.class;
   }
 
-  public static final class Document.LongProperty.DefaultSerializer implements androidx.appsearch.app.LongSerializer<java.lang.Long> {
+  public static final class Document.LongProperty.DefaultSerializer implements androidx.appsearch.app.LongSerializer<java.lang.Long!> {
     ctor public Document.LongProperty.DefaultSerializer();
     method public Long deserialize(long);
     method public long serialize(Long);
@@ -66,7 +66,7 @@
     method public abstract int tokenizerType() default androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN;
   }
 
-  public static final class Document.StringProperty.DefaultSerializer implements androidx.appsearch.app.StringSerializer<java.lang.String> {
+  public static final class Document.StringProperty.DefaultSerializer implements androidx.appsearch.app.StringSerializer<java.lang.String!> {
     ctor public Document.StringProperty.DefaultSerializer();
     method public String deserialize(String);
     method public String serialize(String);
@@ -405,7 +405,7 @@
     method public byte[] getSha256Certificate();
   }
 
-  public class PropertyPath implements java.lang.Iterable<androidx.appsearch.app.PropertyPath.PathSegment> {
+  public class PropertyPath implements java.lang.Iterable<androidx.appsearch.app.PropertyPath.PathSegment!> {
     ctor public PropertyPath(String);
     ctor public PropertyPath(java.util.List<androidx.appsearch.app.PropertyPath.PathSegment!>);
     method public androidx.appsearch.app.PropertyPath.PathSegment get(int);
diff --git a/arch/core/core-common/api/restricted_current.txt b/arch/core/core-common/api/restricted_current.txt
index 4fbc435..1ef0e0f 100644
--- a/arch/core/core-common/api/restricted_current.txt
+++ b/arch/core/core-common/api/restricted_current.txt
@@ -1,13 +1,13 @@
 // Signature format: 4.0
 package androidx.arch.core.internal {
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FastSafeIterableMap<K, V> extends androidx.arch.core.internal.SafeIterableMap<K,V> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FastSafeIterableMap<K, V> extends androidx.arch.core.internal.SafeIterableMap<K!,V!> {
     ctor public FastSafeIterableMap();
     method public java.util.Map.Entry<K!,V!>? ceil(K!);
     method public boolean contains(K!);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SafeIterableMap<K, V> implements java.lang.Iterable<java.util.Map.Entry<K,V>> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SafeIterableMap<K, V> implements java.lang.Iterable<java.util.Map.Entry<K!,V!>!> {
     ctor public SafeIterableMap();
     method public java.util.Iterator<java.util.Map.Entry<K!,V!>!> descendingIterator();
     method public java.util.Map.Entry<K!,V!>? eldest();
@@ -20,7 +20,7 @@
     method public int size();
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SafeIterableMap.IteratorWithAdditions extends androidx.arch.core.internal.SafeIterableMap.SupportRemove<K,V> implements java.util.Iterator<java.util.Map.Entry<K,V>> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SafeIterableMap.IteratorWithAdditions extends androidx.arch.core.internal.SafeIterableMap.SupportRemove<K!,V!> implements java.util.Iterator<java.util.Map.Entry<K!,V!>!> {
     method public boolean hasNext();
     method public java.util.Map.Entry<K!,V!>! next();
   }
diff --git a/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml b/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml
index 5c2583a..6db0d62 100644
--- a/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml
+++ b/benchmark/baseline-profile-gradle-plugin/lint-baseline.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+<issues format="6" by="lint 8.4.0-alpha12" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha12)" variant="all" version="8.4.0-alpha12">
 
     <issue
-        id="InternalGradleApiUsage"
+        id="InternalAgpApiUsage"
         message="Avoid using internal Android Gradle Plugin APIs"
         errorLine1="import com.android.build.gradle.internal.api.DefaultAndroidSourceDirectorySet"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -11,7 +11,7 @@
     </issue>
 
     <issue
-        id="InternalGradleApiUsage"
+        id="InternalAgpApiUsage"
         message="Avoid using internal Android Gradle Plugin APIs"
         errorLine1="import com.android.build.gradle.internal.api.DefaultAndroidSourceFile"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -20,7 +20,7 @@
     </issue>
 
     <issue
-        id="InternalGradleApiUsage"
+        id="InternalAgpApiUsage"
         message="Avoid using internal Android Gradle Plugin APIs"
         errorLine1="import com.android.build.gradle.internal.tasks.BuildAnalyzer"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -29,7 +29,7 @@
     </issue>
 
     <issue
-        id="InternalGradleApiUsage"
+        id="InternalAgpApiUsage"
         message="Avoid using internal Android Gradle Plugin APIs"
         errorLine1="import com.android.build.gradle.internal.tasks.BuildAnalyzer"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -38,7 +38,7 @@
     </issue>
 
     <issue
-        id="InternalGradleApiUsage"
+        id="InternalAgpApiUsage"
         message="Avoid using internal Android Gradle Plugin APIs"
         errorLine1="import com.android.build.gradle.internal.tasks.BuildAnalyzer"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -47,7 +47,7 @@
     </issue>
 
     <issue
-        id="InternalGradleApiUsage"
+        id="InternalAgpApiUsage"
         message="Avoid using internal Android Gradle Plugin APIs"
         errorLine1="import com.android.build.gradle.internal.tasks.BuildAnalyzer"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/benchmark/benchmark-common/api/current.txt b/benchmark/benchmark-common/api/current.txt
index 8d651f3..842d2b3 100644
--- a/benchmark/benchmark-common/api/current.txt
+++ b/benchmark/benchmark-common/api/current.txt
@@ -59,6 +59,7 @@
 
   @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class TimeCapture extends androidx.benchmark.MetricCapture {
     ctor public TimeCapture();
+    ctor public TimeCapture(optional String name);
     method public void capturePaused();
     method public void captureResumed();
     method public void captureStart(long timeNs);
diff --git a/benchmark/benchmark-common/api/restricted_current.txt b/benchmark/benchmark-common/api/restricted_current.txt
index a9056a3..ee4f2bf 100644
--- a/benchmark/benchmark-common/api/restricted_current.txt
+++ b/benchmark/benchmark-common/api/restricted_current.txt
@@ -61,6 +61,7 @@
 
   @SuppressCompatibility @androidx.benchmark.ExperimentalBenchmarkConfigApi public final class TimeCapture extends androidx.benchmark.MetricCapture {
     ctor public TimeCapture();
+    ctor public TimeCapture(optional String name);
     method public void capturePaused();
     method public void captureResumed();
     method public void captureStart(long timeNs);
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
index 100979c..0c71b29 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/BenchmarkStateConfigTest.kt
@@ -33,7 +33,8 @@
         expectedWarmups: Int?,
         expectedMeasurements: Int,
         expectedIterations: Int?,
-        expectedUsesProfiler: Boolean = false
+        expectedUsesProfiler: Boolean = false,
+        expectedProfilerIterations: Int = 0
     ) {
         val state = BenchmarkState(config)
         var count = 0
@@ -58,7 +59,7 @@
         if (!usesProfiler) {
             assertEquals(calculatedIterations, count)
         } else if (config.profiler!!.requiresSingleMeasurementIteration) {
-            assertEquals(calculatedIterations + 1, count)
+            assertEquals(calculatedIterations + expectedProfilerIterations, count)
         } else {
             throw IllegalStateException("Test doesn't support validating profiler $config.profiler")
         }
@@ -78,6 +79,7 @@
             startupMode = true,
             simplifiedTimingOnlyMode = true,
             profiler = null,
+            profilerPerfCompareMode = false,
             warmupCount = 100,
             measurementCount = 1000,
             metrics = arrayOf(TimeCapture()),
@@ -94,6 +96,7 @@
             startupMode = true, // everything after is ignored
             simplifiedTimingOnlyMode = true,
             profiler = null,
+            profilerPerfCompareMode = false,
             warmupCount = 100,
             measurementCount = 1000,
             metrics = arrayOf(TimeCapture()),
@@ -111,6 +114,7 @@
             startupMode = false,
             simplifiedTimingOnlyMode = false,
             profiler = null,
+            profilerPerfCompareMode = false,
             warmupCount = null,
             measurementCount = null,
             metrics = arrayOf(TimeCapture()),
@@ -127,13 +131,14 @@
             startupMode = false,
             simplifiedTimingOnlyMode = false,
             profiler = null,
+            profilerPerfCompareMode = false,
             warmupCount = 10,
             measurementCount = 100,
             metrics = arrayOf(TimeCapture()),
         ),
         expectedWarmups = 10,
         expectedMeasurements = 105, // includes allocations
-        expectedIterations = null,
+        expectedIterations = null, // iterations are dynamic
     )
 
     @Test
@@ -143,14 +148,35 @@
             startupMode = false,
             simplifiedTimingOnlyMode = false,
             profiler = MethodTracing,
+            profilerPerfCompareMode = false,
             warmupCount = 5,
             measurementCount = 10,
             metrics = arrayOf(TimeCapture()),
         ),
         expectedWarmups = 5,
-        expectedMeasurements = 15, // profiler not measured, not accounted for here
-        expectedIterations = null,
+        expectedMeasurements = 15, // 10 timing + 5 allocations
+        expectedIterations = null, // iterations are dynamic
         expectedUsesProfiler = true,
+        expectedProfilerIterations = 1,
+    )
+
+    @Test
+    fun profilerMethodTracing_perfCompareMode() = validateConfig(
+        MicrobenchmarkPhase.Config(
+            dryRunMode = false,
+            startupMode = false,
+            simplifiedTimingOnlyMode = false,
+            profiler = MethodTracing,
+            profilerPerfCompareMode = true,
+            warmupCount = 5,
+            measurementCount = 10,
+            metrics = arrayOf(TimeCapture()),
+        ),
+        expectedWarmups = 5,
+        expectedMeasurements = 15,
+        expectedIterations = 30, // fixed iterations to be consistent between measurement/profiling
+        expectedUsesProfiler = true,
+        expectedProfilerIterations = 10,
     )
 
     @Test
@@ -160,6 +186,7 @@
             startupMode = false,
             simplifiedTimingOnlyMode = true,
             profiler = MethodTracing,
+            profilerPerfCompareMode = true,
             warmupCount = 100,
             measurementCount = 10,
             metrics = arrayOf(TimeCapture()),
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index 08a4c83..9f80460 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -76,6 +76,8 @@
     internal val profilerDefault: Boolean
     internal val profilerSampleFrequency: Int
     internal val profilerSampleDurationSeconds: Long
+    internal val profilerSkipWhenDurationRisksAnr: Boolean
+    internal val profilerPerfCompareEnable: Boolean
     internal val thermalThrottleSleepDurationSeconds: Long
     private val cpuEventCounterEnable: Boolean
     internal val cpuEventCounterMask: Int
@@ -213,6 +215,11 @@
             arguments.getBenchmarkArgument("profiling.sampleDurationSeconds")?.ifBlank { null }
                 ?.toLong()
                 ?: 5
+        profilerSkipWhenDurationRisksAnr =
+            arguments.getBenchmarkArgument("profiling.skipWhenDurationRisksAnr")?.toBoolean()
+                ?: true
+        profilerPerfCompareEnable =
+            arguments.getBenchmarkArgument("profiling.perfCompare.enable")?.toBoolean() ?: false
         if (profiler != null) {
             Log.d(
                 BenchmarkState.TAG,
@@ -261,6 +268,18 @@
         runOnMainDeadlineSeconds =
             arguments.getBenchmarkArgument("runOnMainDeadlineSeconds")?.toLong() ?: 30
         Log.d(BenchmarkState.TAG, "runOnMainDeadlineSeconds $runOnMainDeadlineSeconds")
+
+        if (arguments.getString("orchestratorService") != null) {
+            InstrumentationResults.scheduleIdeWarningOnNextReport(
+                """
+                    AndroidX Benchmark does not support running with the AndroidX Test Orchestrator.
+
+                    AndroidX benchmarks (micro and macro) produce one JSON file per test module,
+                    which together with Test Orchestrator restarting the process frequently causes
+                    benchmark output JSON files to be repeatedly overwritten during the test.
+                    """.trimIndent()
+            )
+        }
     }
 
     fun macrobenchMethodTracingEnabled(): Boolean {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
index 910e5e2..f161951 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -96,6 +96,7 @@
             dryRunMode = Arguments.dryRunMode,
             startupMode = Arguments.startupMode,
             profiler = config?.profiler?.profiler ?: Arguments.profiler,
+            profilerPerfCompareMode = Arguments.profilerPerfCompareEnable,
             warmupCount = warmupCount,
             measurementCount = Arguments.iterations ?: measurementCount,
             simplifiedTimingOnlyMode = simplifiedTimingOnlyMode,
@@ -257,10 +258,8 @@
             currentPhase.profiler?.stop()
             InMemoryTracing.endSection()
             thermalThrottleSleepSeconds += currentPhase.thermalThrottleSleepSeconds
-            if (currentPhase.loopMode.warmupManager == null && currentPhase.profiler == null) {
-                // Always save metrics, except during warmup / profiling
-                // Note that dryRunMode avoids reporting these to JSON by other means, they
-                // still should be accessible to tests
+            if (currentPhase.loopMode.warmupManager == null) {
+                // Save captured metrics except during warmup, where we intentionally discard
                 metricResults.addAll(
                     currentMetrics.captureFinished(maxIterations = currentLoopsPerMeasurement)
                 )
@@ -293,10 +292,17 @@
                 warmupEstimatedIterationTimeNs * METHOD_TRACING_ESTIMATED_SLOWDOWN_FACTOR
             if (this == MethodTracing &&
                 Looper.myLooper() == Looper.getMainLooper() &&
-                estimatedMethodTraceDurNs > METHOD_TRACING_MAX_DURATION_NS) {
+                estimatedMethodTraceDurNs > METHOD_TRACING_MAX_DURATION_NS &&
+                Arguments.profilerSkipWhenDurationRisksAnr
+            ) {
+                val expectedDurSec = estimatedMethodTraceDurNs / 1_000_000_000.0
                 InstrumentationResults.scheduleIdeWarningOnNextReport(
-                    "Skipping method trace of estimated duration" +
-                        " ${estimatedMethodTraceDurNs / 1_000_000_000.0} sec to avoid ANR"
+                    """
+                        Skipping method trace of estimated duration $expectedDurSec sec to avoid ANR
+
+                        To disable this behavior, set instrumentation arg:
+                            androidx.benchmark.profiling.skipWhenDurationRisksAnr = false
+                    """.trimIndent()
                 )
                 null
             } else {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
index 32e3e54..3956071 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import androidx.annotation.RestrictTo
 import androidx.test.platform.app.InstrumentationRegistry
+import java.util.Locale
 import org.jetbrains.annotations.TestOnly
 
 /**
@@ -139,14 +140,14 @@
         // for readability, report nanos with 10ths only if less than 100
         var output = if (nanos >= 100.0) {
             // 13 alignment is enough for ~10 seconds
-            "%,13d   ns".format(nanos.toLong())
+            "%,13d   ns".format(Locale.US, nanos.toLong())
         } else {
             // 13 + 2(.X) to match alignment above
-            "%,15.1f ns".format(nanos)
+            "%,15.1f ns".format(Locale.US, nanos)
         }
         if (allocations != null) {
             // 9 alignment is enough for ~10 million allocations
-            output += "    %8d allocs".format(allocations.toInt())
+            output += "    %8d allocs".format(Locale.US, allocations.toInt())
         }
         profilerResults.forEach {
             output += "    [${it.label}](file://${it.sanitizedOutputRelativePath})"
@@ -234,7 +235,7 @@
 
             val allMetrics = measurements.singleMetrics + measurements.sampledMetrics
             val maxLabelLength = allMetrics.maxOf { it.name.length }
-            fun Double.toDisplayString() = "%,.1f".format(this)
+            fun Double.toDisplayString() = "%,.1f".format(Locale.US, this)
 
             // max string length of any printed min/med/max is the largest max value seen. used to pad.
             val maxValueLength = allMetrics
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
index 2d7a00f..17c9996 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MetricCapture.kt
@@ -83,11 +83,11 @@
 /**
  * Time metric, which reports time in nanos, based on the time passed to [captureStop].
  *
- * Reports "timeNs"
+ * Reports elapsed time with the label from `name`, which defaults to `timeNs`.
  */
 @ExperimentalBenchmarkConfigApi
-public class TimeCapture : MetricCapture(
-    names = listOf("timeNs")
+public class TimeCapture @JvmOverloads constructor(name: String = "timeNs") : MetricCapture(
+    names = listOf(name)
 ) {
     private var currentStarted = 0L
     private var currentPausedStarted = 0L
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
index a21e9c8..d2ba4c7 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/MicrobenchmarkPhase.kt
@@ -156,13 +156,17 @@
         )
 
         fun profiledTimingPhase(
-            profiler: Profiler
+            profiler: Profiler,
+            metrics: Array<MetricCapture>,
+            loopModeOverride: LoopMode?,
+            measurementCountOverride: Int?
         ): MicrobenchmarkPhase {
-            val measurementCount = if (profiler.requiresSingleMeasurementIteration) 1 else 50
+            val measurementCount = measurementCountOverride
+                ?: if (profiler.requiresSingleMeasurementIteration) 1 else 50
             return MicrobenchmarkPhase(
                 label = "Benchmark Profiled Time",
                 measurementCount = measurementCount,
-                loopMode = if (profiler.requiresSingleMeasurementIteration) {
+                loopMode = loopModeOverride ?: if (profiler.requiresSingleMeasurementIteration) {
                     LoopMode.FixedIterations(1)
                 } else {
                     LoopMode.Duration(
@@ -173,7 +177,8 @@
                         }
                     )
                 },
-                profiler = profiler
+                profiler = profiler,
+                metrics = metrics
             )
         }
 
@@ -196,6 +201,7 @@
         val startupMode: Boolean,
         val simplifiedTimingOnlyMode: Boolean,
         val profiler: Profiler?,
+        val profilerPerfCompareMode: Boolean,
         val warmupCount: Int?,
         val measurementCount: Int?,
         val metrics: Array<MetricCapture>,
@@ -216,12 +222,21 @@
             } else if (startupMode) {
                 listOf(startupModePhase())
             } else {
+                val timingMeasurementCount = measurementCount ?: 50
+
                 val profiler = if (simplifiedTimingOnlyMode) null else profiler
                 // note that it's currently important that allocation runs for the same target
                 // duration as timing, since we only report a single value for
                 // "repeatIterations" in the output JSON. If we ever want to avoid loopMode
                 // sharing between these phases, we should update that JSON representation.
-                val loopMode = LoopMode.Duration(BenchmarkState.DEFAULT_MEASUREMENT_DURATION_NS)
+                val loopMode = if (profilerPerfCompareMode) {
+                    // single fixed iteration as a compromise choice that can be matched between
+                    // measurement and profiler, and not produce overwhelming method tracing capture
+                    // durations/file sizes
+                    LoopMode.FixedIterations(1)
+                } else {
+                    LoopMode.Duration(BenchmarkState.DEFAULT_MEASUREMENT_DURATION_NS)
+                }
                 listOfNotNull(
                     warmupPhase(
                         warmupManager = warmupManager,
@@ -236,7 +251,7 @@
                     ),
                     // Regular timing phase
                     timingMeasurementPhase(
-                        measurementCount = measurementCount ?: 50,
+                        measurementCount = timingMeasurementCount,
                         loopMode = loopMode,
                         metrics = metrics,
                         simplifiedTimingOnlyMode = simplifiedTimingOnlyMode
@@ -244,7 +259,23 @@
                     if (simplifiedTimingOnlyMode || profiler == null) {
                         null
                     } else {
-                        profiledTimingPhase(profiler)
+                        if (profilerPerfCompareMode) {
+                            // benchmark the profiler, matching the timing phases for fair compare
+                            profiledTimingPhase(
+                                profiler = profiler,
+                                metrics = arrayOf(TimeCapture("profilerTimeNs")),
+                                loopModeOverride = loopMode,
+                                measurementCountOverride = timingMeasurementCount
+                            )
+                        } else {
+                            // standard profiling
+                            profiledTimingPhase(
+                                profiler,
+                                metrics = emptyArray(),
+                                loopModeOverride = null,
+                                measurementCountOverride = null
+                            )
+                        }
                     },
                     if (simplifiedTimingOnlyMode) {
                         null // skip allocations
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
index b5f936a..e94ceb7 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
@@ -151,7 +151,11 @@
     ) {
         startMethodTracingSampling(path, bufferSize, Arguments.profilerSampleFrequency)
     } else {
-        Debug.startMethodTracing(path, bufferSize, 0)
+        // NOTE: 0x10 flag enables low-overhead wall clock timing when ART module version supports
+        // it. Note that this doesn't affect trace parsing, since this doesn't affect wall clock,
+        // it only removes the expensive thread time clock which our parser doesn't use.
+        // TODO: switch to platform-defined constant once available (b/329499422)
+        Debug.startMethodTracing(path, bufferSize, 0x10)
     }
 
     return Profiler.ResultFile(
@@ -189,15 +193,19 @@
 internal object MethodTracing : Profiler() {
     override fun start(traceUniqueName: String): ResultFile {
         hasBeenUsed = true
-        return startRuntimeMethodTracing(
-            traceFileName = traceName(traceUniqueName, "methodTracing"),
-            sampled = false,
-            profiler = this
-        )
+        inMemoryTrace("startMethodTrace") {
+            return startRuntimeMethodTracing(
+                traceFileName = traceName(traceUniqueName, "methodTracing"),
+                sampled = false,
+                profiler = this
+            )
+        }
     }
 
     override fun stop() {
-        stopRuntimeMethodTracing()
+        inMemoryTrace("stopMethodTrace") {
+            stopRuntimeMethodTracing()
+        }
     }
 
     override val requiresSingleMeasurementIteration: Boolean = true
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/AtraceTag.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/AtraceTag.kt
index ecfdba3..f386a35 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/AtraceTag.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/AtraceTag.kt
@@ -54,6 +54,7 @@
             return rooted || api >= 24
         }
     },
+    Power("power"),
     Resources("res"),
     Scheduling("sched"),
     Synchronization("sync") {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
index 533583b..561d71c 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoConfig.kt
@@ -151,6 +151,7 @@
                 AtraceTag.Idle,
                 AtraceTag.Input,
                 AtraceTag.MemReclaim,
+                AtraceTag.Power,
                 AtraceTag.Resources,
                 AtraceTag.Scheduling,
                 AtraceTag.Synchronization,
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index a0095f8..63a175a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -51,11 +51,9 @@
     var perfettoPid: Int? = null
 
     /**
-     * For now, we use --background-wait only when unbundled.
-     *
-     * Eventually, we should also use it when using bundled platform version that support it (T+?)
+     * --background-wait requires unbundled or API 33 bundled version of perfetto
      */
-    private val useBackgroundWait = unbundled
+    private val useBackgroundWait = unbundled || Build.VERSION.SDK_INT >= 33
 
     private fun perfettoStartupException(label: String, cause: Exception?): IllegalStateException {
         return IllegalStateException(
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index b9b8d3b..000fc7d 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -72,12 +72,12 @@
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public final class MemoryCountersMetric extends androidx.benchmark.macro.TraceMetric {
     ctor public MemoryCountersMetric();
-    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getResult(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
+    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getMeasurements(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
   }
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public final class MemoryUsageMetric extends androidx.benchmark.macro.TraceMetric {
     ctor public MemoryUsageMetric(androidx.benchmark.macro.MemoryUsageMetric.Mode mode, optional java.util.List<? extends androidx.benchmark.macro.MemoryUsageMetric.SubMetric> subMetrics);
-    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getResult(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
+    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getMeasurements(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
   }
 
   public enum MemoryUsageMetric.Mode {
@@ -192,14 +192,19 @@
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public abstract class TraceMetric extends androidx.benchmark.macro.Metric {
     ctor public TraceMetric();
-    method public abstract java.util.List<androidx.benchmark.macro.Metric.Measurement> getResult(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
+    method public abstract java.util.List<androidx.benchmark.macro.Metric.Measurement> getMeasurements(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
   }
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public final class TraceSectionMetric extends androidx.benchmark.macro.Metric {
-    ctor public TraceSectionMetric(String sectionName, optional String label, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode, optional boolean targetPackageOnly);
+    ctor public TraceSectionMetric(String sectionName);
+    ctor public TraceSectionMetric(String sectionName, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode);
+    ctor public TraceSectionMetric(String sectionName, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode, optional String label);
+    ctor public TraceSectionMetric(String sectionName, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode, optional String label, optional boolean targetPackageOnly);
   }
 
   public enum TraceSectionMetric.Mode {
+    enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Average;
+    enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Count;
     enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode First;
     enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Max;
     enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Min;
@@ -228,10 +233,13 @@
 
   public static final class PerfettoTraceProcessor.Session {
     method public kotlin.sequences.Sequence<androidx.benchmark.perfetto.Row> query(@org.intellij.lang.annotations.Language("sql") String query);
+    method public String queryMetricsJson(java.util.List<java.lang.String> metrics);
+    method public byte[] queryMetricsProtoBinary(java.util.List<java.lang.String> metrics);
+    method public String queryMetricsProtoText(java.util.List<java.lang.String> metrics);
     method public byte[] rawQuery(@org.intellij.lang.annotations.Language("sql") String query);
   }
 
-  @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi public final class Row implements kotlin.jvm.internal.markers.KMappedMarker java.util.Map<java.lang.String,java.lang.Object> {
+  @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi public final class Row implements kotlin.jvm.internal.markers.KMappedMarker java.util.Map<java.lang.String,java.lang.Object?> {
     ctor public Row(java.util.Map<java.lang.String,?> map);
     method public byte[] bytes(String columnName);
     method public double double(String columnName);
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index f98e021..1f0587c 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -85,12 +85,12 @@
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public final class MemoryCountersMetric extends androidx.benchmark.macro.TraceMetric {
     ctor public MemoryCountersMetric();
-    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getResult(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
+    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getMeasurements(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
   }
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public final class MemoryUsageMetric extends androidx.benchmark.macro.TraceMetric {
     ctor public MemoryUsageMetric(androidx.benchmark.macro.MemoryUsageMetric.Mode mode, optional java.util.List<? extends androidx.benchmark.macro.MemoryUsageMetric.SubMetric> subMetrics);
-    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getResult(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
+    method public java.util.List<androidx.benchmark.macro.Metric.Measurement> getMeasurements(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
   }
 
   public enum MemoryUsageMetric.Mode {
@@ -214,14 +214,19 @@
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public abstract class TraceMetric extends androidx.benchmark.macro.Metric {
     ctor public TraceMetric();
-    method public abstract java.util.List<androidx.benchmark.macro.Metric.Measurement> getResult(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
+    method public abstract java.util.List<androidx.benchmark.macro.Metric.Measurement> getMeasurements(androidx.benchmark.macro.Metric.CaptureInfo captureInfo, androidx.benchmark.perfetto.PerfettoTraceProcessor.Session traceSession);
   }
 
   @SuppressCompatibility @androidx.benchmark.macro.ExperimentalMetricApi public final class TraceSectionMetric extends androidx.benchmark.macro.Metric {
-    ctor public TraceSectionMetric(String sectionName, optional String label, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode, optional boolean targetPackageOnly);
+    ctor public TraceSectionMetric(String sectionName);
+    ctor public TraceSectionMetric(String sectionName, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode);
+    ctor public TraceSectionMetric(String sectionName, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode, optional String label);
+    ctor public TraceSectionMetric(String sectionName, optional androidx.benchmark.macro.TraceSectionMetric.Mode mode, optional String label, optional boolean targetPackageOnly);
   }
 
   public enum TraceSectionMetric.Mode {
+    enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Average;
+    enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Count;
     enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode First;
     enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Max;
     enum_constant public static final androidx.benchmark.macro.TraceSectionMetric.Mode Min;
@@ -250,10 +255,13 @@
 
   public static final class PerfettoTraceProcessor.Session {
     method public kotlin.sequences.Sequence<androidx.benchmark.perfetto.Row> query(@org.intellij.lang.annotations.Language("sql") String query);
+    method public String queryMetricsJson(java.util.List<java.lang.String> metrics);
+    method public byte[] queryMetricsProtoBinary(java.util.List<java.lang.String> metrics);
+    method public String queryMetricsProtoText(java.util.List<java.lang.String> metrics);
     method public byte[] rawQuery(@org.intellij.lang.annotations.Language("sql") String query);
   }
 
-  @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi public final class Row implements kotlin.jvm.internal.markers.KMappedMarker java.util.Map<java.lang.String,java.lang.Object> {
+  @SuppressCompatibility @androidx.benchmark.perfetto.ExperimentalPerfettoTraceProcessorApi public final class Row implements kotlin.jvm.internal.markers.KMappedMarker java.util.Map<java.lang.String,java.lang.Object?> {
     ctor public Row(java.util.Map<java.lang.String,?> map);
     method public byte[] bytes(String columnName);
     method public double double(String columnName);
diff --git a/benchmark/benchmark-macro/build.gradle b/benchmark/benchmark-macro/build.gradle
index a38320e..5715dd6 100644
--- a/benchmark/benchmark-macro/build.gradle
+++ b/benchmark/benchmark-macro/build.gradle
@@ -67,7 +67,7 @@
 
     implementation(project(":benchmark:benchmark-common"))
     implementation("androidx.core:core:1.9.0")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     implementation("androidx.tracing:tracing-ktx:1.1.0")
     implementation("androidx.tracing:tracing-perfetto:1.0.0")
     implementation("androidx.tracing:tracing-perfetto-binary:1.0.0")
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
index 7f1fa9a..b47435e 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
@@ -44,7 +44,7 @@
             .associateWith { PowerCategoryDisplayLevel.BREAKDOWN }
 
         val actualMetrics = PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            PowerMetric(PowerMetric.Energy(categories)).getResult(captureInfo, this)
+            PowerMetric(PowerMetric.Energy(categories)).getMeasurements(captureInfo, this)
         }
 
         assertEqualMeasurements(
@@ -82,7 +82,7 @@
             .associateWith { PowerCategoryDisplayLevel.TOTAL }
 
         val actualMetrics = PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            PowerMetric(PowerMetric.Power(categories)).getResult(captureInfo, this)
+            PowerMetric(PowerMetric.Power(categories)).getMeasurements(captureInfo, this)
         }
 
         assertEqualMeasurements(
@@ -116,7 +116,7 @@
         )
 
         val actualMetrics = PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            PowerMetric(PowerMetric.Power(categories)).getResult(captureInfo, this)
+            PowerMetric(PowerMetric.Power(categories)).getMeasurements(captureInfo, this)
         }
 
         assertEqualMeasurements(
@@ -144,7 +144,7 @@
             .associateWith { PowerCategoryDisplayLevel.BREAKDOWN }
 
         val actualMetrics = PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            PowerMetric(PowerMetric.Energy(categories)).getResult(captureInfo, this)
+            PowerMetric(PowerMetric.Energy(categories)).getMeasurements(captureInfo, this)
         }
 
         assertEquals(emptyList(), actualMetrics)
@@ -158,7 +158,7 @@
         val traceFile = createTempFileFromAsset("api31_battery_discharge", ".perfetto-trace")
 
         val actualMetrics = PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            PowerMetric(PowerMetric.Battery()).getResult(captureInfo, this)
+            PowerMetric(PowerMetric.Battery()).getMeasurements(captureInfo, this)
         }
 
         assertEqualMeasurements(
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt
index 719e6a8..04dda73 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt
@@ -215,7 +215,7 @@
 
         metric.configure(packageName)
         return PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            metric.getResult(
+            metric.getMeasurements(
                 captureInfo = Metric.CaptureInfo(
                     targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
                     testPackageName = "androidx.benchmark.integration.macrobenchmark.test",
@@ -239,7 +239,7 @@
         metric.configure(Packages.TEST)
 
         val measurements = PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
-            metric.getResult(
+            metric.getMeasurements(
                 captureInfo = Metric.CaptureInfo(
                     targetPackageName = Packages.TEST,
                     testPackageName = Packages.TEST,
@@ -317,7 +317,7 @@
     )!!
 
     return PerfettoTraceProcessor.runSingleSessionServer(tracePath) {
-        metric.getResult(
+        metric.getMeasurements(
             captureInfo = Metric.CaptureInfo(
                 targetPackageName = packageName,
                 testPackageName = Packages.TEST,
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceMetricTest.kt
index da73a55..2ca7cbc 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceMetricTest.kt
@@ -37,7 +37,7 @@
     )
 
     class ActivityResumeMetric : TraceMetric() {
-        override fun getResult(
+        override fun getMeasurements(
             captureInfo: CaptureInfo,
             traceSession: PerfettoTraceProcessor.Session
         ): List<Measurement> {
@@ -84,7 +84,7 @@
             metric.configure(packageName = Packages.TEST)
 
             val result = PerfettoTraceProcessor.runSingleSessionServer(tracePath) {
-                metric.getResult(
+                metric.getMeasurements(
                     captureInfo = captureInfo,
                     traceSession = this
                 )
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
index ee4b593..5bbb4e4 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
@@ -114,11 +114,11 @@
         ) {
             assumeTrue(PerfettoHelper.isAbiSupported())
 
-            val metric = TraceSectionMetric(sectionName, "testLabel", mode, targetPackageOnly)
+            val metric = TraceSectionMetric(sectionName, mode, "testLabel", targetPackageOnly)
             metric.configure(packageName = packageName)
 
             val result = PerfettoTraceProcessor.runSingleSessionServer(tracePath) {
-                metric.getResult(
+                metric.getMeasurements(
                     // note that most args are incorrect here, but currently
                     // only targetPackageName matters in this context
                     captureInfo = Metric.CaptureInfo(
@@ -131,9 +131,13 @@
                 )
             }
 
-            var measurements = listOf(Metric.Measurement("testLabel${mode.name}Ms", expectedMs))
+            var measurements = if (mode != TraceSectionMetric.Mode.Count) {
+                listOf(Metric.Measurement("testLabel${mode.name}Ms", expectedMs))
+            } else {
+                emptyList()
+            }
 
-            if (mode == TraceSectionMetric.Mode.Sum) {
+            if (mode == TraceSectionMetric.Mode.Sum || mode == TraceSectionMetric.Mode.Count) {
                 measurements = measurements + listOf(
                     Metric.Measurement("testLabelCount", expectedCount.toDouble())
                 )
@@ -163,7 +167,7 @@
                 sectionName = sectionName,
                 mode = TraceSectionMetric.Mode.First,
                 expectedMs = expectedFirstMs,
-                expectedCount = 1,
+                expectedCount = 1, // unused
                 targetPackageOnly = targetPackageOnly,
             )
             verifyMetric(
@@ -181,7 +185,7 @@
                 sectionName = sectionName,
                 mode = TraceSectionMetric.Mode.Min,
                 expectedMs = expectedMinMs,
-                expectedCount = 1,
+                expectedCount = 1, // unused
                 targetPackageOnly = targetPackageOnly,
             )
             verifyMetric(
@@ -190,7 +194,25 @@
                 sectionName = sectionName,
                 mode = TraceSectionMetric.Mode.Max,
                 expectedMs = expectedMaxMs,
-                expectedCount = 1,
+                expectedCount = 1, // unused
+                targetPackageOnly = targetPackageOnly,
+            )
+            verifyMetric(
+                tracePath = tracePath,
+                packageName = packageName,
+                sectionName = sectionName,
+                mode = TraceSectionMetric.Mode.Count,
+                expectedMs = 1.0, // unused
+                expectedCount = expectedSumCount,
+                targetPackageOnly = targetPackageOnly,
+            )
+            verifyMetric(
+                tracePath = tracePath,
+                packageName = packageName,
+                sectionName = sectionName,
+                mode = TraceSectionMetric.Mode.Average,
+                expectedMs = expectedSumMs / expectedSumCount,
+                expectedCount = 1, // unused
                 targetPackageOnly = targetPackageOnly,
             )
         }
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/PerfettoTraceProcessorTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/PerfettoTraceProcessorTest.kt
index 021fc1c..014480c 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/PerfettoTraceProcessorTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/PerfettoTraceProcessorTest.kt
@@ -38,6 +38,7 @@
 import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
+import perfetto.protos.TraceMetrics
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -249,6 +250,61 @@
     }
 
     @Test
+    fun query_includeModule() {
+        assumeTrue(isAbiSupported())
+        val traceFile = createTempFileFromAsset("api31_startup_cold", ".perfetto-trace")
+        val startups = PerfettoTraceProcessor.runServer {
+            loadTrace(PerfettoTrace(traceFile.absolutePath)) {
+                query("""
+                    INCLUDE PERFETTO MODULE android.startup.startups;
+
+                    SELECT * FROM android_startups;
+                """.trimIndent()).toList()
+            }
+        }
+        // minimal validation, just verifying query worked
+        assertEquals(1, startups.size)
+        assertEquals(
+            "androidx.benchmark.integration.macrobenchmark.target",
+            startups.single().string("package")
+        )
+    }
+
+    @Test
+    fun queryMetricsJson() {
+        assumeTrue(isAbiSupported())
+        val traceFile = createTempFileFromAsset("api31_startup_cold", ".perfetto-trace")
+        PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
+            val metrics = queryMetricsJson(listOf("android_startup"))
+            assertTrue(metrics.contains("\"android_startup\": {"))
+            assertTrue(metrics.contains("\"startup_type\": \"cold\","))
+        }
+    }
+
+    @Test
+    fun queryMetricsProtoBinary() {
+        assumeTrue(isAbiSupported())
+        val traceFile = createTempFileFromAsset("api31_startup_cold", ".perfetto-trace")
+        PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
+            val metrics =
+                TraceMetrics.ADAPTER.decode(queryMetricsProtoBinary(listOf("android_startup")))
+            val startup = metrics.android_startup!!
+            assertEquals(startup.startup.single().startup_type, "cold")
+        }
+    }
+
+    @Test
+    fun queryMetricsProtoText() {
+        assumeTrue(isAbiSupported())
+        val traceFile = createTempFileFromAsset("api31_startup_cold", ".perfetto-trace")
+        PerfettoTraceProcessor.runSingleSessionServer(traceFile.absolutePath) {
+            val metrics = queryMetricsProtoText(listOf("android_startup"))
+            assertTrue(metrics.contains("android_startup {"))
+            assertTrue(metrics.contains("startup_type: \"cold\""))
+        }
+    }
+
+    @Test
     fun validatePerfettoTraceProcessorBinariesExist() {
         val context = InstrumentationRegistry.getInstrumentation().targetContext
         val suffixes = listOf("aarch64")
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 6d83128..a1bb59d 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -326,7 +326,7 @@
                         metrics
                             // capture list of Measurements
                             .map {
-                                it.getResult(
+                                it.getMeasurements(
                                     Metric.CaptureInfo(
                                         targetPackageName = packageName,
                                         testPackageName = macrobenchPackageName,
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 260e340..cc26c8f 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -52,7 +52,7 @@
      * TODO: takes package for package level filtering, but probably want a
      *  general config object coming into [start].
      */
-    internal abstract fun getResult(
+    internal abstract fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement>
@@ -137,7 +137,18 @@
  * how much faster than the deadline a frame was.
  *
  * * `frameDurationCpuMs` - How much time the frame took to be produced on the CPU - on both the UI
- * Thread, and RenderThread.
+ * Thread, and RenderThread. Note that this doesn't account for time before the frame started
+ * (before Choreographer#doFrame), as that data isn't available in traces prior to API 31.
+ *
+ * * `frameCount` - How many total frames were produced. This is a secondary metric which can be
+ * used to understand *why* the above metrics changed. For example, when removing unneeded frames
+ * that were incorrectly invalidated to save power, `frameOverrunMs` and `frameDurationCpuMs` will
+ * often get worse, as the removed frames were trivial. Checking `frameCount` can be a useful
+ * indicator in such cases.
+ *
+ * Generally, prefer tracking and detecting regressions with `frameOverrunMs` when it is available,
+ * as it is the more complete data, and accounts for modern devices (including higher, variable
+ * framerate rendering) more naturally.
  */
 @Suppress("CanSealedSubClassBeObject")
 class FrameTimingMetric : Metric() {
@@ -145,15 +156,16 @@
     override fun start() {}
     override fun stop() {}
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
-        return FrameTimingQuery.getFrameData(
+        val frameData = FrameTimingQuery.getFrameData(
             session = traceSession,
             captureApiLevel = captureInfo.apiLevel,
             packageName = captureInfo.targetPackageName
         )
+        return frameData
             .getFrameSubMetrics(captureInfo.apiLevel)
             .filterKeys { it == SubMetric.FrameDurationCpuNs || it == SubMetric.FrameOverrunNs }
             .map {
@@ -165,7 +177,7 @@
                     },
                     dataSamples = it.value.map { timeNs -> timeNs.nsToDoubleMs() }
                 )
-            }
+            } + listOf(Measurement("frameCount", frameData.size.toDouble()))
     }
 }
 
@@ -260,7 +272,7 @@
         "gfxFrameJankPercent",
     )
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
@@ -303,7 +315,7 @@
     override fun stop() {
     }
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
@@ -343,7 +355,7 @@
     override fun stop() {
     }
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
@@ -386,10 +398,10 @@
  * package:
  * ```
  * class ActivityResumeMetric : TraceMetric() {
- *     override fun getResult(
+ *     override fun getMeasurements(
  *         captureInfo: CaptureInfo,
  *         traceSession: PerfettoTraceProcessor.Session
- *     ): Result {
+ *     ): List<Measurement> {
  *         val rowSequence = traceSession.query(
  *             """
  *             SELECT
@@ -409,9 +421,9 @@
  *         // to capture timing of every component of activity lifecycle
  *         val activityResultNs = rowSequence.firstOrNull()?.double("dur")
  *         return if (activityResultMs != null) {
- *             Result("activityResumeMs", activityResultNs / 1_000_000.0)
+ *             listOf(Measurement("activityResumeMs", activityResultNs / 1_000_000.0))
  *         } else {
- *             Result()
+ *             emptyList()
  *         }
  *     }
  * }
@@ -435,7 +447,7 @@
     /**
      * Get the metric result for a given iteration given information about the target process and a TraceProcessor session
      */
-    public abstract override fun getResult(
+    public abstract override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement>
@@ -448,12 +460,22 @@
  * Select how matching sections are resolved into a duration metric with [mode], and configure if
  * sections outside the target process are included with [targetPackageOnly].
  *
+ * The following TraceSectionMetric counts the number of JIT method compilations that occur within a
+ * trace:
+ *
+ * ```
+ * TraceSectionMetric(
+ *     sectionName = "JIT Compiling %",
+ *     mode = TraceSectionMetric.Mode.Sum
+ * )
+ * ```
+ *
  * @see androidx.tracing.Trace.beginSection
  * @see androidx.tracing.Trace.endSection
  * @see androidx.tracing.trace
  */
 @ExperimentalMetricApi
-class TraceSectionMetric(
+class TraceSectionMetric @JvmOverloads constructor(
     /**
      * Section name or pattern to match.
      *
@@ -463,15 +485,15 @@
      */
     private val sectionName: String,
     /**
-     * Metric label, defaults to [sectionName].
-     */
-    private val label: String = sectionName,
-    /**
      * Defines how slices matching [sectionName] should be confirmed to metrics, by default uses
      * [Mode.Sum] to count and sum durations of all matching trace sections.
      */
     private val mode: Mode = Mode.Sum,
     /**
+     * Metric label, defaults to [sectionName].
+     */
+    private val label: String = sectionName,
+    /**
      * Filter results to trace sections only from the target process, defaults to true.
      */
     private val targetPackageOnly: Boolean = true
@@ -489,7 +511,7 @@
          * Captures the sum of all instances of `sectionName` in the trace.
          *
          * When this mode is used, a measurement of `0` will be reported if the named section
-         * does not appear in the trace
+         * does not appear in the trace.
          */
         Sum,
 
@@ -510,6 +532,23 @@
          * not appear in the trace.
          */
         Max,
+
+        /**
+         * Counts the number of observed instances of a trace section matching `sectionName` in the
+         * trace.
+         *
+         * When this mode is used, a measurement of `0` will be reported if the named section
+         * does not appear in the trace.
+         */
+        Count,
+
+        /**
+         * Average duration of trace sections matching `sectionName` in the trace.
+         *
+         * When this mode is used, a measurement of `0` will be reported if the named section
+         * does not appear in the trace.
+         */
+        Average,
     }
 
     override fun configure(packageName: String) {
@@ -521,7 +560,7 @@
     override fun stop() {
     }
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
@@ -575,6 +614,22 @@
                     )
                 )
             }
+            Mode.Count -> {
+                listOf(
+                    Measurement(
+                        name = label + "Count",
+                        data = slices.size.toDouble()
+                    )
+                )
+            }
+            Mode.Average -> {
+                listOf(
+                    Measurement(
+                        name = label + "AverageMs",
+                        data = slices.sumOf { it.dur } / 1_000_000.0 / slices.size
+                    )
+                )
+            }
         }
     }
 }
@@ -700,7 +755,7 @@
         }
     }
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
@@ -860,7 +915,7 @@
         Gpu("GPU Memory", alreadyInKb = false)
     }
 
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
@@ -885,7 +940,7 @@
  */
 @ExperimentalMetricApi
 class MemoryCountersMetric : TraceMetric() {
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt
index 1fa38b8..6960117 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt
@@ -210,11 +210,16 @@
     /**
      * Computes the given metrics on a previously parsed trace.
      */
-    fun computeMetric(metrics: List<String>): ComputeMetricResult =
+    fun computeMetric(
+        metrics: List<String>,
+        resultFormat: ComputeMetricArgs.ResultFormat
+    ): ComputeMetricResult =
         httpRequest(
             method = METHOD_POST,
             url = PATH_COMPUTE_METRIC,
-            encodeBlock = { ComputeMetricArgs.ADAPTER.encode(it, ComputeMetricArgs(metrics)) },
+            encodeBlock = {
+                ComputeMetricArgs.ADAPTER.encode(it, ComputeMetricArgs(metrics, resultFormat))
+            },
             decodeBlock = { ComputeMetricResult.ADAPTER.decode(it) }
         )
 
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt
index af808e9..1960762 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt
@@ -29,6 +29,8 @@
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.seconds
 import org.intellij.lang.annotations.Language
+import perfetto.protos.ComputeMetricArgs
+import perfetto.protos.ComputeMetricResult
 import perfetto.protos.QueryResult
 import perfetto.protos.TraceMetrics
 
@@ -196,22 +198,87 @@
          */
         @RestrictTo(LIBRARY_GROUP) // avoids exposing Proto API
         fun getTraceMetrics(metric: String): TraceMetrics {
-            inMemoryTrace("PerfettoTraceProcessor#getTraceMetrics $metric") {
-                require(!metric.contains(" ")) {
-                    "Metric must not contain spaces: $metric"
-                }
+            val computeResult = queryAndVerifyMetricResult(
+                listOf(metric),
+                ComputeMetricArgs.ResultFormat.BINARY_PROTOBUF
+            )
+            return TraceMetrics.ADAPTER.decode(computeResult.metrics!!)
+        }
+
+        /**
+         * Computes the given metrics, returning the results as a binary proto.
+         *
+         * The proto format definition for decoding this binary format can be found
+         * [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/metrics/).
+         *
+         * See [perfetto metric docs](https://perfetto.dev/docs/quickstart/trace-analysis#trace-based-metrics)
+         * for an overview on trace based metrics.
+         */
+        fun queryMetricsProtoBinary(metrics: List<String>): ByteArray {
+            val computeResult = queryAndVerifyMetricResult(
+                metrics,
+                ComputeMetricArgs.ResultFormat.BINARY_PROTOBUF
+            )
+            return computeResult.metrics!!.toByteArray()
+        }
+
+        /**
+         * Computes the given metrics, returning the results as JSON text.
+         *
+         * The proto format definition for these metrics can be found
+         * [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/metrics/).
+         *
+         * See [perfetto metric docs](https://perfetto.dev/docs/quickstart/trace-analysis#trace-based-metrics)
+         * for an overview on trace based metrics.
+         */
+        fun queryMetricsJson(metrics: List<String>): String {
+            val computeResult = queryAndVerifyMetricResult(
+                metrics,
+                ComputeMetricArgs.ResultFormat.JSON
+            )
+            check(computeResult.metrics_as_json != null)
+            return computeResult.metrics_as_json
+        }
+
+        /**
+         * Computes the given metrics, returning the result as proto text.
+         *
+         * The proto format definition for these metrics can be found
+         * [here](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/metrics/).
+         *
+         * See [perfetto metric docs](https://perfetto.dev/docs/quickstart/trace-analysis#trace-based-metrics)
+         * for an overview on trace based metrics.
+         */
+        fun queryMetricsProtoText(metrics: List<String>): String {
+            val computeResult = queryAndVerifyMetricResult(
+                metrics,
+                ComputeMetricArgs.ResultFormat.TEXTPROTO
+            )
+            check(computeResult.metrics_as_prototext != null)
+            return computeResult.metrics_as_prototext
+        }
+
+        private fun queryAndVerifyMetricResult(
+            metrics: List<String>,
+            format: ComputeMetricArgs.ResultFormat
+        ): ComputeMetricResult {
+            val nameString = metrics.joinToString()
+            require(metrics.none { it.contains(" ") }) {
+                "Metrics must not constain spaces, metrics: $nameString"
+            }
+
+            inMemoryTrace("PerfettoTraceProcessor#getTraceMetrics $nameString") {
                 require(traceProcessor.perfettoHttpServer.isRunning()) {
                     "Perfetto trace_shell_process is not running."
                 }
 
                 // Compute metrics
-                val computeResult = traceProcessor.perfettoHttpServer.computeMetric(listOf(metric))
+                val computeResult = traceProcessor.perfettoHttpServer.computeMetric(metrics, format)
                 if (computeResult.error != null) {
                     throw IllegalStateException(computeResult.error)
                 }
 
-                // Decode and return trace metrics
-                return TraceMetrics.ADAPTER.decode(computeResult.metrics!!)
+                return computeResult
             }
         }
 
diff --git a/buildSrc-tests/lint-baseline.xml b/buildSrc-tests/lint-baseline.xml
index ac2bc86..935864f4 100644
--- a/buildSrc-tests/lint-baseline.xml
+++ b/buildSrc-tests/lint-baseline.xml
@@ -344,6 +344,60 @@
     </issue>
 
     <issue
+        id="InternalAgpApiUsage"
+        message="Avoid using internal Android Gradle Plugin APIs"
+        errorLine1="import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="InternalAgpApiUsage"
+        message="Avoid using internal Android Gradle Plugin APIs"
+        errorLine1="import com.android.build.gradle.internal.lint.LintModelWriterTask"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="InternalAgpApiUsage"
+        message="Avoid using internal Android Gradle Plugin APIs"
+        errorLine1="import com.android.build.gradle.internal.lint.VariantInputs"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="InternalAgpApiUsage"
+        message="Avoid using internal Android Gradle Plugin APIs"
+        errorLine1="import com.android.build.gradle.internal.attributes.VariantAttr"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="InternalAgpApiUsage"
+        message="Avoid using internal Android Gradle Plugin APIs"
+        errorLine1="import com.android.build.gradle.internal.publishing.AndroidArtifacts"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
+    </issue>
+
+    <issue
+        id="InternalAgpApiUsage"
+        message="Avoid using internal Android Gradle Plugin APIs"
+        errorLine1="import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType"
+        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
+    </issue>
+
+    <issue
         id="InternalGradleApiUsage"
         message="Avoid using internal Gradle APIs"
         errorLine1="import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency"
@@ -426,33 +480,6 @@
 
     <issue
         id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
-    </issue>
-
-    <issue
-        id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.lint.LintModelWriterTask"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
-    </issue>
-
-    <issue
-        id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.lint.VariantInputs"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/LintConfiguration.kt"/>
-    </issue>
-
-    <issue
-        id="InternalGradleApiUsage"
         message="Avoid using internal Gradle APIs"
         errorLine1="import org.gradle.api.internal.component.SoftwareComponentInternal"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -507,33 +534,6 @@
 
     <issue
         id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.attributes.VariantAttr"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
-    </issue>
-
-    <issue
-        id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.publishing.AndroidArtifacts"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
-    </issue>
-
-    <issue
-        id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="${:buildSrc-tests*main*MAIN*sourceProvider*0*javaDir*4}/androidx/build/testConfiguration/TestSuiteConfiguration.kt"/>
-    </issue>
-
-    <issue
-        id="InternalGradleApiUsage"
         message="Avoid using internal Gradle APIs"
         errorLine1="import org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger"
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/buildSrc-tests/src/test/java/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt b/buildSrc-tests/src/test/java/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt
index 48bc82c..b336d85 100644
--- a/buildSrc-tests/src/test/java/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt
+++ b/buildSrc-tests/src/test/java/androidx/build/buildInfo/CreateLibraryBuildInfoFileTaskTest.kt
@@ -91,6 +91,7 @@
         assertThat(buildInfo.dependencyConstraints.single().artifactId)
             .isEqualTo("core-ktx")
         assertThat(buildInfo.shouldPublishDocs).isFalse()
+        assertThat(buildInfo.isKmp).isFalse()
     }
 
     fun setupBuildInfoProject() {
@@ -135,6 +136,7 @@
                             null,
                             it.artifactId,
                             project.provider { "fakeSha" },
+                            false,
                             false
                         )
                     }
diff --git a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
index 993cec9..bf7a859 100644
--- a/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
+++ b/buildSrc/jetpad-integration/src/main/java/androidx/build/jetpad/LibraryBuildInfoFile.java
@@ -51,6 +51,7 @@
     public ArrayList<Dependency> dependencies;
     public ArrayList<Dependency> dependencyConstraints;
     public Boolean shouldPublishDocs;
+    public Boolean isKmp;
     public ArrayList<Check> checks;
 
     /**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index dcdd156..dbbc781 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -17,12 +17,10 @@
 package androidx.build
 
 import androidx.build.dependencies.KOTLIN_NATIVE_VERSION
+import com.android.build.api.dsl.CommonExtension
 import com.android.build.api.variant.AndroidComponentsExtension
-import com.android.build.gradle.AppExtension
 import com.android.build.gradle.AppPlugin
-import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.LibraryPlugin
-import com.android.build.gradle.TestedExtension
 import java.io.File
 import org.gradle.api.Plugin
 import org.gradle.api.Project
@@ -47,19 +45,12 @@
             project.extensions.create<AndroidXComposeExtension>("androidxCompose", project)
         project.plugins.all { plugin ->
             when (plugin) {
-                is LibraryPlugin -> {
-                    val library =
-                        project.extensions.findByType(LibraryExtension::class.java)
+                is AppPlugin, is LibraryPlugin -> {
+                    val commonExtension =
+                        project.extensions.findByType(CommonExtension::class.java)
                             ?: throw Exception("Failed to find Android extension")
-
-                    project.configureAndroidCommonOptions(library)
-                }
-                is AppPlugin -> {
-                    val app =
-                        project.extensions.findByType(AppExtension::class.java)
-                            ?: throw Exception("Failed to find Android extension")
-
-                    project.configureAndroidCommonOptions(app)
+                    commonExtension.defaultConfig.minSdk = 21
+                    project.configureAndroidCommonOptions()
                 }
                 is KotlinBasePluginWrapper -> {
                     configureComposeCompilerPlugin(project, extension)
@@ -73,9 +64,7 @@
     }
 
     companion object {
-        private fun Project.configureAndroidCommonOptions(testedExtension: TestedExtension) {
-            testedExtension.defaultConfig.minSdk = 21
-
+        private fun Project.configureAndroidCommonOptions() {
             extensions.findByType(AndroidComponentsExtension::class.java)!!.finalizeDsl {
                 val isPublished = androidXExtension.shouldPublish()
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 7c788c4..effbaf6 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -70,6 +70,7 @@
 import javax.inject.Inject
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
+import org.gradle.api.JavaVersion
 import org.gradle.api.JavaVersion.VERSION_11
 import org.gradle.api.JavaVersion.VERSION_17
 import org.gradle.api.JavaVersion.VERSION_1_8
@@ -97,13 +98,13 @@
 import org.gradle.build.event.BuildEventsListenerRegistry
 import org.gradle.jvm.tasks.Jar
 import org.gradle.kotlin.dsl.KotlinClosure1
-import org.gradle.kotlin.dsl.configure
 import org.gradle.kotlin.dsl.create
 import org.gradle.kotlin.dsl.dependencies
 import org.gradle.kotlin.dsl.extra
 import org.gradle.kotlin.dsl.findByType
 import org.gradle.kotlin.dsl.getByType
 import org.gradle.kotlin.dsl.withModule
+import org.gradle.kotlin.dsl.withType
 import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin
 import org.gradle.plugin.devel.tasks.ValidatePlugins
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
@@ -112,8 +113,11 @@
 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
+import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
 
 /**
  * A plugin which enables all of the Gradle customizations for AndroidX. This plugin reacts to other
@@ -184,7 +188,7 @@
         project.configureTaskTimeouts()
         project.configureMavenArtifactUpload(
             androidXExtension, androidXKmpExtension, componentFactory) {
-            project.addCreateLibraryBuildInfoFileTasks(androidXExtension)
+            project.addCreateLibraryBuildInfoFileTasks(androidXExtension, androidXKmpExtension)
         }
         project.publishInspectionArtifacts()
         project.configureExternalDependencyLicenseCheck()
@@ -443,18 +447,57 @@
         project.configureKtfmt()
 
         project.afterEvaluate {
-            project.tasks.withType(KotlinCompile::class.java).configureEach { task ->
-                if (androidXExtension.type == LibraryType.COMPILER_PLUGIN) {
-                    task.kotlinOptions.jvmTarget = "11"
-                } else if (
-                    androidXExtension.type.compilationTarget == CompilationTarget.HOST &&
-                    androidXExtension.type != LibraryType.ANNOTATION_PROCESSOR_UTILS
-                ) {
-                    task.kotlinOptions.jvmTarget = "17"
-                } else {
-                    task.kotlinOptions.jvmTarget = "1.8"
+            val targetsAndroid =
+                project.plugins.hasPlugin(LibraryPlugin::class.java) ||
+                    project.plugins.hasPlugin(AppPlugin::class.java) ||
+                    project.plugins.hasPlugin(TestPlugin::class.java) ||
+                    @Suppress("UnstableApiUsage")
+                    project.plugins.hasPlugin(KotlinMultiplatformAndroidPlugin::class.java)
+            val defaultJavaTargetVersion =
+                getDefaultTargetJavaVersion(androidXExtension.type).toString()
+            if (plugin is KotlinMultiplatformPluginWrapper) {
+                project.extensions.getByType<KotlinMultiplatformExtension>().apply {
+                    targets.withType<KotlinAndroidTarget> {
+                        compilations.configureEach {
+                            it.kotlinOptions.jvmTarget = defaultJavaTargetVersion
+                        }
+                    }
+                    targets.withType<KotlinJvmTarget> {
+                        val defaultJavaTargetVersionForNonAndroidTargets =
+                            getDefaultTargetJavaVersion(
+                                libraryType = androidXExtension.type,
+                                projectName = project.name,
+                                targetName = name
+                            ).toString()
+                        compilations.configureEach {
+                            it.compileJavaTaskProvider?.configure { javaCompile ->
+                                javaCompile.targetCompatibility =
+                                    defaultJavaTargetVersionForNonAndroidTargets
+                                javaCompile.sourceCompatibility =
+                                    defaultJavaTargetVersionForNonAndroidTargets
+                            }
+                            it.kotlinOptions {
+                                jvmTarget = defaultJavaTargetVersionForNonAndroidTargets
+                                // Set jdk-release version for non-Android KMP targets
+                                freeCompilerArgs += listOf(
+                                    "-Xjdk-release=$defaultJavaTargetVersionForNonAndroidTargets",
+                                )
+                            }
+                        }
+                    }
                 }
-
+            } else {
+                project.tasks.withType(KotlinJvmCompile::class.java).configureEach { task ->
+                    task.kotlinOptions.jvmTarget = defaultJavaTargetVersion
+                    if (!targetsAndroid) {
+                        // Set jdk-release version for non-Android JVM projects
+                        task.kotlinOptions.freeCompilerArgs += listOf(
+                            "-Xjdk-release=$defaultJavaTargetVersion",
+                        )
+                    }
+                }
+            }
+            project.tasks.withType(KotlinCompile::class.java).configureEach { task ->
                 val kotlinCompilerArgs =
                     mutableListOf(
                         "-Xskip-metadata-version-check",
@@ -483,13 +526,9 @@
                 logScriptSources(task, project)
             }
 
-            val isAndroidProject =
-                project.plugins.hasPlugin(LibraryPlugin::class.java) ||
-                    project.plugins.hasPlugin(AppPlugin::class.java) ||
-                    project.plugins.hasPlugin(KotlinMultiplatformAndroidPlugin::class.java)
             // Explicit API mode is broken for Android projects
             // https://youtrack.jetbrains.com/issue/KT-37652
-            if (androidXExtension.shouldEnforceKotlinStrictApiMode() && !isAndroidProject) {
+            if (androidXExtension.shouldEnforceKotlinStrictApiMode() && !targetsAndroid) {
                 project.tasks.withType(KotlinCompile::class.java).configureEach { task ->
                     // Workaround for https://youtrack.jetbrains.com/issue/KT-37652
                     if (task.name.endsWith("TestKotlin")) return@configureEach
@@ -500,7 +539,6 @@
         }
         if (plugin is KotlinMultiplatformPluginWrapper) {
             KonanPrebuiltsSetup.configureKonanDirectory(project)
-            KmpLinkTaskWorkaround.serializeLinkTasks(project)
             project.afterEvaluate {
                 val libraryExtension = project.extensions.findByType<LibraryExtension>()
                 if (libraryExtension != null) {
@@ -854,30 +892,30 @@
         SdkResourceGenerator.generateForHostTest(project)
     }
 
+    private fun getDefaultTargetJavaVersion(
+        libraryType: LibraryType,
+        projectName: String? = null,
+        targetName: String? = null
+    ): JavaVersion {
+        return when {
+            projectName != null && projectName.contains("desktop") -> VERSION_17
+            targetName != null && targetName == "desktop" -> VERSION_17
+            libraryType == LibraryType.COMPILER_PLUGIN -> VERSION_11
+            libraryType.compilationTarget == CompilationTarget.HOST -> VERSION_17
+            else -> VERSION_1_8
+        }
+    }
+
     private fun configureWithJavaPlugin(project: Project, androidXExtension: AndroidXExtension) {
         project.configureErrorProneForJava()
 
         // Force Java 1.8 source- and target-compatibility for all Java libraries.
         val javaExtension = project.extensions.getByType<JavaPluginExtension>()
         project.afterEvaluate {
-            if (androidXExtension.type == LibraryType.COMPILER_PLUGIN) {
-                javaExtension.apply {
-                    sourceCompatibility = VERSION_11
-                    targetCompatibility = VERSION_11
-                }
-            } else if (
-                androidXExtension.type.compilationTarget == CompilationTarget.HOST &&
-                androidXExtension.type != LibraryType.ANNOTATION_PROCESSOR_UTILS
-            ) {
-                javaExtension.apply {
-                    sourceCompatibility = VERSION_17
-                    targetCompatibility = VERSION_17
-                }
-            } else {
-                javaExtension.apply {
-                    sourceCompatibility = VERSION_1_8
-                    targetCompatibility = VERSION_1_8
-                }
+            javaExtension.apply {
+                val defaultTargetJavaVersion = getDefaultTargetJavaVersion(androidXExtension.type)
+                sourceCompatibility = defaultTargetJavaVersion
+                targetCompatibility = defaultTargetJavaVersion
             }
             if (!project.plugins.hasPlugin(KotlinBasePluginWrapper::class.java)) {
                 project.configureSourceJarForJava()
@@ -977,7 +1015,7 @@
         defaultConfig.targetSdk = project.defaultAndroidConfig.targetSdk
         defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 
-        testOptions.animationsDisabled = true
+        testOptions.animationsDisabled = !project.isMacrobenchmark()
         testOptions.unitTests.isReturnDefaultValues = true
         testOptions.unitTests.all { task ->
             task.configureForRobolectric()
@@ -1491,6 +1529,10 @@
     return this.plugins.hasPlugin(BenchmarkPlugin::class.java)
 }
 
+fun Project.isMacrobenchmark(): Boolean {
+    return this.path.endsWith("macrobenchmark")
+}
+
 /**
  * Returns a string that is a valid filename and loosely based on the project name The value
  * returned for each project will be distinct
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index 70fbc32..967d798 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -283,34 +283,14 @@
     fun addNativeLibrariesToJniLibs(
         androidTarget: KotlinAndroidTarget,
         nativeCompilation: MultiTargetNativeCompilation,
-        variantBuildType: String = "debug",
         forTest: Boolean = false
     ) = nativeLibraryBundler.addNativeLibrariesToJniLibs(
         androidTarget = androidTarget,
         nativeCompilation = nativeCompilation,
-        variantBuildType = variantBuildType,
         forTest = forTest
     )
 
     /**
-     * Convenience method to add native libraries to the jniLibs input of an Android instrumentation
-     * test.
-     *
-     * @see addNativeLibrariesToJniLibs
-     */
-    @JvmOverloads
-    fun addNativeLibrariesToTestJniLibs(
-        androidTarget: KotlinAndroidTarget,
-        nativeCompilation: MultiTargetNativeCompilation,
-        variantBuildType: String = "debug",
-    ) = addNativeLibrariesToJniLibs(
-        androidTarget = androidTarget,
-        nativeCompilation = nativeCompilation,
-        variantBuildType = variantBuildType,
-        forTest = true
-    )
-
-    /**
      * Convenience method to add bundle native libraries with a test jar.
      *
      * @see addNativeLibrariesToResources
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index 73da6d1..1e8f047 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -27,6 +27,7 @@
 import androidx.build.playground.VerifyPlaygroundGradleConfigurationTask
 import androidx.build.studio.StudioTask.Companion.registerStudioTask
 import androidx.build.testConfiguration.registerOwnersServiceTasks
+import androidx.build.uptodatedness.TaskUpToDateValidator
 import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
 import java.io.File
@@ -40,9 +41,11 @@
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.bundling.ZipEntryCompression
+import org.gradle.build.event.BuildEventsListenerRegistry
 import org.gradle.kotlin.dsl.extra
 
 abstract class AndroidXRootImplPlugin : Plugin<Project> {
+    @get:javax.inject.Inject abstract val registry: BuildEventsListenerRegistry
     override fun apply(project: Project) {
         if (!project.isRoot) {
             throw Exception("This plugin should only be applied to root project")
@@ -128,7 +131,6 @@
 
         registerOwnersServiceTasks()
 
-        project.configureRootProjectForKmpLink()
         // If useMaxDepVersions is set, iterate through all the project and substitute any androidx
         // artifact dependency with the local tip of tree version of the library.
         if (project.usingMaxDepVersions()) {
@@ -174,6 +176,8 @@
 
         project.zipComposeCompilerMetrics()
         project.zipComposeCompilerReports()
+
+        TaskUpToDateValidator.setup(project, registry)
     }
 
     private fun Project.setDependencyVersions() {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/KmpLinkTaskWorkaround.kt b/buildSrc/private/src/main/kotlin/androidx/build/KmpLinkTaskWorkaround.kt
deleted file mode 100644
index c8990fc..0000000
--- a/buildSrc/private/src/main/kotlin/androidx/build/KmpLinkTaskWorkaround.kt
+++ /dev/null
@@ -1,56 +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.build
-
-import org.gradle.api.Project
-import org.gradle.api.services.BuildService
-import org.gradle.api.services.BuildServiceParameters
-import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
-
-/**
- * Name of the service we use to limit the number of concurrent kmp link tasks
- */
-public const val KMP_LINK_SERVICE_NAME = "androidxKmpLinkService"
-
-// service for limiting the number of concurrent kmp link tasks b/309990481
-interface AndroidXKmpLinkService : BuildService<BuildServiceParameters.None>
-
-fun Project.configureRootProjectForKmpLink() {
-    project.gradle.sharedServices.registerIfAbsent(
-        KMP_LINK_SERVICE_NAME,
-        AndroidXKmpLinkService::class.java,
-        { spec ->
-            spec.maxParallelUsages.set(1)
-        }
-    )
-}
-
-object KmpLinkTaskWorkaround {
-    // b/309990481
-    fun serializeLinkTasks(
-        project: Project
-    ) {
-        project.tasks.withType(
-            KotlinNativeLink::class.java
-        ).configureEach { task ->
-            task.usesService(
-                task.project.gradle.sharedServices.registrations
-                    .getByName(KMP_LINK_SERVICE_NAME).service
-            )
-        }
-    }
-}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index 8e05eea..6a357b2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -146,7 +146,7 @@
             task.androidXDependencySet.set(
                 project.provider {
                     val dependencies = mutableSetOf<AndroidXDependency>()
-                    project.configurations.filter(::shouldVerifyConfiguration).forEach {
+                    project.configurations.filter(project::shouldVerifyConfiguration).forEach {
                         configuration ->
                         configuration.allDependencies.filter(::shouldVerifyDependency).forEach {
                             dependency ->
@@ -165,11 +165,12 @@
             )
             task.cacheEvenIfNoOutputs()
         }
+
     addToBuildOnServer(taskProvider)
     return taskProvider
 }
 
-private fun shouldVerifyConfiguration(configuration: Configuration): Boolean {
+private fun Project.shouldVerifyConfiguration(configuration: Configuration): Boolean {
     // Only verify configurations that are exported to POM. In an ideal world, this would be an
     // inclusion derived from the mappings used by the Maven Publish Plugin; however, since we
     // don't have direct access to those, this should remain an exclusion list.
@@ -217,11 +218,15 @@
     if (name.endsWith("DependenciesMetadata")) return false
 
     // don't verify test configurations of KMP projects
-    if (name.contains("JvmTest")) return false
-    if (name.contains("commonTest")) return false
-    if (name.contains("nativeTest")) return false
     if (name.contains("TestCompilation")) return false
     if (name.contains("TestCompile")) return false
+    if (name.contains("commonTest", ignoreCase = true)) return false
+    if (name.contains("nativeTest", ignoreCase = true)) return false
+    if (multiplatformExtension?.targets
+            ?.any { name.contains("${it.name}Test", ignoreCase = true) } == true
+    ) {
+        return false
+    }
 
     return true
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
index 461b94a..3512027 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/buildInfo/CreateLibraryBuildInfoFileTask.kt
@@ -17,6 +17,7 @@
 package androidx.build.buildInfo
 
 import androidx.build.AndroidXExtension
+import androidx.build.AndroidXMultiplatformExtension
 import androidx.build.LibraryGroup
 import androidx.build.docs.CheckTipOfTreeDocsTask.Companion.requiresDocs
 import androidx.build.getBuildInfoDirectory
@@ -107,6 +108,9 @@
     /** Whether the project should be included in docs-public/build.gradle. */
     @get:Input abstract val shouldPublishDocs: Property<Boolean>
 
+    /** Whether the artifact is from a KMP project. */
+    @get:Input abstract val kmp: Property<Boolean>
+
     private fun writeJsonToFile(info: LibraryBuildInfoFile) {
         val resolvedOutputFile: File = outputFile.get()
         val outputDir = resolvedOutputFile.parentFile
@@ -145,6 +149,7 @@
             if (dependencyConstraintList.isPresent) ArrayList(dependencyConstraintList.get())
             else ArrayList()
         libraryBuildInfoFile.shouldPublishDocs = shouldPublishDocs.get()
+        libraryBuildInfoFile.isKmp = kmp.get()
         return libraryBuildInfoFile
     }
 
@@ -169,6 +174,7 @@
             variant: VariantPublishPlan,
             shaProvider: Provider<String>,
             shouldPublishDocs: Boolean,
+            isKmp: Boolean,
         ): TaskProvider<CreateLibraryBuildInfoFileTask> {
             return project.tasks.register(
                 TASK_NAME + variant.taskSuffix,
@@ -209,6 +215,7 @@
                     variant.dependencyConstraints.map { it.asBuildInfoDependencies() }
                 )
                 task.shouldPublishDocs.set(shouldPublishDocs)
+                task.kmp.set(isKmp)
             }
         }
 
@@ -249,8 +256,11 @@
 }
 
 // Tasks that create a json files of a project's variant's dependencies
-fun Project.addCreateLibraryBuildInfoFileTasks(extension: AndroidXExtension) {
-    extension.ifReleasing {
+fun Project.addCreateLibraryBuildInfoFileTasks(
+    androidXExtension: AndroidXExtension,
+    androidXKmpExtension: AndroidXMultiplatformExtension,
+) {
+    androidXExtension.ifReleasing {
         configure<PublishingExtension> {
             // Unfortunately, dependency information is only available through internal API
             // (See https://github.com/gradle/gradle/issues/21345).
@@ -259,10 +269,11 @@
                 // main publication.  We do not track these aliases.
                 if (!mavenPub.isAlias) {
                     createTaskForComponent(
-                        mavenPub,
-                        extension.mavenGroup,
-                        mavenPub.artifactId,
-                        extension.requiresDocs(),
+                        pub = mavenPub,
+                        libraryGroup = androidXExtension.mavenGroup,
+                        artifactId = mavenPub.artifactId,
+                        shouldPublishDocs = androidXExtension.requiresDocs(),
+                        isKmp = androidXKmpExtension.supportedPlatforms.isNotEmpty(),
                     )
                 }
             }
@@ -275,6 +286,7 @@
     libraryGroup: LibraryGroup?,
     artifactId: String,
     shouldPublishDocs: Boolean,
+    isKmp: Boolean,
 ) {
     val task =
         createBuildInfoTask(
@@ -283,6 +295,7 @@
             artifactId,
             getHeadShaProvider(project),
             shouldPublishDocs,
+            isKmp
         )
     rootProject.tasks.named(CreateLibraryBuildInfoFileTask.TASK_NAME).configure {
         it.dependsOn(task)
@@ -296,6 +309,7 @@
     artifactId: String,
     shaProvider: Provider<String>,
     shouldPublishDocs: Boolean,
+    isKmp: Boolean,
 ): TaskProvider<CreateLibraryBuildInfoFileTask> {
     val kmpTaskSuffix = computeTaskSuffix(name, artifactId)
     return CreateLibraryBuildInfoFileTask.setup(
@@ -320,6 +334,7 @@
         // There's a build_info file for each KMP platform, but only the artifact without a platform
         // suffix is listed in docs-public/build.gradle.
         shouldPublishDocs = shouldPublishDocs && kmpTaskSuffix == "",
+        isKmp = isKmp,
     )
 }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt b/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt
index 8d3766c..52a4cf2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/clang/NativeLibraryBundler.kt
@@ -70,25 +70,22 @@
 
     /**
      * Adds the shared library outputs from [nativeCompilation] to the jni libs dependency of
-     * the [androidTarget]'s [variantBuildType].
+     * the [androidTarget].
      *
      * @see CombineObjectFilesTask for details.
      */
     fun addNativeLibrariesToJniLibs(
         androidTarget: KotlinAndroidTarget,
         nativeCompilation: MultiTargetNativeCompilation,
-        variantBuildType: String,
         forTest: Boolean
     ) {
         project.androidExtension.onVariants(
-            project.androidExtension.selector().withBuildType(
-                variantBuildType
-            )
+            project.androidExtension.selector().all()
         ) { variant ->
             fun setup(name: String, jniLibsSources: SourceDirectories.Layered?) {
                 checkNotNull(jniLibsSources) {
                     "Cannot find jni libs sources for variant: " +
-                        "$variant($variantBuildType / $forTest)"
+                        "$variant (forTest=$forTest)"
                 }
                 val combineTask = project.tasks.register(
                     "createJniLibsDirectoryFor".appendCapitalized(
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
index e2d62fc..3eaafa9 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
@@ -139,16 +139,26 @@
                 ?.let { metadataFile ->
                     val metadata =
                         gson.fromJson(metadataFile.readText(), ProjectStructureMetadata::class.java)
-                    metadata.sourceSets.map { sourceSet ->
+                    metadata.sourceSets.mapNotNull { sourceSet ->
+                        val sourceDir = multiplatformSourcesDir.get().asFile.resolve(sourceSet.name)
+                        if (!sourceDir.exists()) return@mapNotNull null
                         val analysisPlatform =
                             DokkaAnalysisPlatform.valueOf(sourceSet.analysisPlatform.uppercase())
-                        val sourceDir = multiplatformSourcesDir.get().asFile.resolve(sourceSet.name)
                         DokkaInputModels.SourceSet(
                             id = sourceSetIdForSourceSet(sourceSet.name),
                             displayName = sourceSet.name,
                             analysisPlatform = analysisPlatform.jsonName,
                             sourceRoots = objects.fileCollection().from(sourceDir),
-                            samples = objects.fileCollection(),
+                            // TODO(b/181224204): KMP samples aren't supported, dackka assumes all
+                            // samples are in common
+                            samples = if (analysisPlatform == DokkaAnalysisPlatform.COMMON) {
+                                objects.fileCollection().from(
+                                    samplesDir,
+                                    frameworkSamplesDir.get().asFile
+                                )
+                            } else {
+                                objects.fileCollection()
+                            },
                             includes = objects.fileCollection().from(includesFiles(sourceDir)),
                             classpath = dependenciesClasspath,
                             externalDocumentationLinks = externalDocs,
@@ -194,7 +204,6 @@
         val linksConfiguration = ""
         val jsonMap =
             mapOf(
-                "moduleName" to "",
                 "outputDir" to destinationDir.get().asFile.path,
                 "globalLinks" to linksConfiguration,
                 "sourceSets" to sourceSets(),
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt b/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
index fb1f4d0..3cb7bbf 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
@@ -19,9 +19,10 @@
 import androidx.build.getAndroidJar
 import androidx.build.multiplatformExtension
 import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
-import java.io.File
 import org.gradle.api.Project
+import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.file.FileCollection
+import org.gradle.api.provider.Provider
 import org.gradle.api.tasks.SourceSet
 import org.gradle.kotlin.dsl.get
 import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
@@ -34,7 +35,10 @@
     // Source files to process
     val sourcePaths: FileCollection,
 
-    // Dependencies of [sourcePaths].
+    // Source files from the KMP common module of this project
+    val commonModuleSourcePaths: FileCollection,
+
+    // Dependencies (compiled classes) of [sourcePaths].
     val dependencyClasspath: FileCollection,
 
     // Android's boot classpath.
@@ -49,10 +53,16 @@
             bootClasspath: FileCollection
         ): JavaCompileInputs {
             val sourceCollection = getSourceCollection(variant, project)
+            val commonModuleSourceCollection = getCommonModuleSourceCollection(variant, project)
 
             val dependencyClasspath = variant.getCompileClasspath(null).filter { it.exists() }
 
-            return JavaCompileInputs(sourceCollection, dependencyClasspath, bootClasspath)
+            return JavaCompileInputs(
+                sourcePaths = sourceCollection,
+                commonModuleSourcePaths = commonModuleSourceCollection,
+                dependencyClasspath = dependencyClasspath,
+                bootClasspath = bootClasspath
+            )
         }
 
         /**
@@ -70,17 +80,17 @@
                         .trimIndent()
                 }
             val jvmTarget = kmpExtension.targets.requirePlatform(KotlinPlatformType.jvm)
-            val sourceCollection =
-                project.files(
-                    project.provider {
-                        jvmTarget.sourceFiles(
-                            compilationName = KotlinCompilation.MAIN_COMPILATION_NAME
-                        )
-                    }
-                )
+            val jvmCompilation = jvmTarget.findCompilation(
+                compilationName = KotlinCompilation.MAIN_COMPILATION_NAME
+            )
+
+            val sourceCollection = project.sourceFiles(jvmCompilation)
+
+            val commonModuleSourcePaths = project.commonModuleSourcePaths(jvmCompilation)
 
             return JavaCompileInputs(
                 sourcePaths = sourceCollection,
+                commonModuleSourcePaths = commonModuleSourcePaths,
                 dependencyClasspath =
                     jvmTarget.compilations[KotlinCompilation.MAIN_COMPILATION_NAME]
                         .compileDependencyFiles,
@@ -105,17 +115,14 @@
             val target = kmpExtension.targets.withType(
                 KotlinMultiplatformAndroidTarget::class.java
             ).single()
-            val sourceCollection =
-                project.files(
-                    project.provider {
-                        target.sourceFiles(
-                            compilationName = KotlinCompilation.MAIN_COMPILATION_NAME
-                        )
-                    }
-                )
+            val compilation = target.findCompilation(KotlinCompilation.MAIN_COMPILATION_NAME)
+            val sourceCollection = project.sourceFiles(compilation)
+
+            val commonModuleSourcePaths = project.commonModuleSourcePaths(compilation)
 
             return JavaCompileInputs(
                 sourcePaths = sourceCollection,
+                commonModuleSourcePaths = commonModuleSourcePaths,
                 dependencyClasspath =
                 target.compilations[KotlinCompilation.MAIN_COMPILATION_NAME]
                     .compileDependencyFiles,
@@ -128,7 +135,12 @@
             val sourcePaths: FileCollection =
                 project.files(project.provider { sourceSet.allSource.srcDirs })
             val dependencyClasspath = sourceSet.compileClasspath
-            return JavaCompileInputs(sourcePaths, dependencyClasspath, project.getAndroidJar())
+            return JavaCompileInputs(
+                sourcePaths = sourcePaths,
+                commonModuleSourcePaths = project.files(),
+                dependencyClasspath = dependencyClasspath,
+                bootClasspath = project.getAndroidJar()
+            )
         }
 
         @Suppress("DEPRECATION") // BaseVariant, SourceKind
@@ -137,30 +149,57 @@
             project: Project
         ): FileCollection {
             // If the project has the kotlin-multiplatform plugin, we want to return a combined
+            // collection of all the source files inside '*main' source sets. i.e., given a module
+            // with a common and Android source set, this will look inside commonMain and
+            // androidMain.
+            val taskDependencies = mutableListOf<Any>(variant.javaCompileProvider)
+            val sourceCollection: ConfigurableFileCollection =
+                project.multiplatformExtension?.let { kmpExtension ->
+                    project.sourceFiles(
+                        kmpExtension.targets
+                            .requirePlatform(KotlinPlatformType.androidJvm)
+                            .findCompilation(compilationName = variant.name)
+                    )
+                }
+                    ?: project.files(
+                           project.provider {
+                               variant
+                                   .getSourceFolders(com.android.build.gradle.api.SourceKind.JAVA)
+                                   .map { folder ->
+                                       for (builtBy in folder.builtBy) {
+                                           taskDependencies.add(builtBy)
+                                       }
+                                       folder.dir
+                                   }
+                           }
+                       )
+
+            for (dep in taskDependencies) {
+                sourceCollection.builtBy(dep)
+            }
+            return sourceCollection
+        }
+
+        @Suppress("DEPRECATION") // BaseVariant, SourceKind
+        private fun getCommonModuleSourceCollection(
+            variant: com.android.build.gradle.api.BaseVariant,
+            project: Project
+        ): FileCollection {
+            // If the project has the kotlin-multiplatform plugin, we want to return a combined
             // collection of all the source files inside '*main' source sets. I.e, given a module
             // with a common and Android source set, this will look inside commonMain and
             // androidMain.
             val taskDependencies = mutableListOf<Any>(variant.javaCompileProvider)
-            val sourceFiles =
+            val sourceCollection: ConfigurableFileCollection =
                 project.multiplatformExtension?.let { kmpExtension ->
-                    project.provider {
+                    project.commonModuleSourcePaths(
                         kmpExtension.targets
                             .requirePlatform(KotlinPlatformType.androidJvm)
-                            .sourceFiles(compilationName = variant.name)
-                    }
+                            .findCompilation(compilationName = variant.name)
+                    )
                 }
-                    ?: project.provider {
-                        variant
-                            .getSourceFolders(com.android.build.gradle.api.SourceKind.JAVA)
-                            .map { folder ->
-                                for (builtBy in folder.builtBy) {
-                                    taskDependencies.add(builtBy)
-                                }
-                                folder.dir
-                            }
-                    }
+                    ?: project.files()
 
-            val sourceCollection = project.files(sourceFiles)
             for (dep in taskDependencies) {
                 sourceCollection.builtBy(dep)
             }
@@ -174,26 +213,58 @@
          * @param compilationName The name of the compilation. A target might have separate
          *   compilations (e.g. main vs test for jvm or debug vs release for Android)
          */
-        private fun KotlinTarget.sourceFiles(compilationName: String): List<File> {
-            val selectedCompilation =
-                checkNotNull(compilations.findByName(compilationName)) {
+        private fun KotlinTarget.findCompilation(
+            compilationName: String
+        ): Provider<KotlinCompilation<*>> {
+            return project.provider {
+                val selectedCompilation =
+                    checkNotNull(compilations.findByName(compilationName)) {
+                        """
+                    Cannot find $compilationName compilation configuration of $name in
+                    ${project.parent}.
+                    Available compilations: ${compilations.joinToString(", ") { it.name }}
                     """
-                Cannot find $compilationName compilation configuration of $name in
-                ${project.parent}.
-                Available compilations: ${compilations.joinToString(", ") { it.name }}
-                """
-                        .trimIndent()
-                }
-            return selectedCompilation.allKotlinSourceSets
-                .flatMap { it.kotlin.sourceDirectories }
-                .also {
-                    require(it.isNotEmpty()) {
-                        """
-                        Didn't find any source sets for $selectedCompilation in ${project.path}.
-                        """
                             .trimIndent()
                     }
+                selectedCompilation
+            }
+        }
+
+        private fun Project.sourceFiles(
+            kotlinCompilation: Provider<KotlinCompilation<*>>
+        ): ConfigurableFileCollection {
+            return project.files(
+                    project.provider {
+                        kotlinCompilation.get().allKotlinSourceSets
+                            .flatMap {
+                                it.kotlin.sourceDirectories
+                            }
+                            .also {
+                                require(it.isNotEmpty()) {
+                                    """
+                                    Didn't find any source sets for $kotlinCompilation in ${project.path}.
+                                    """
+                                        .trimIndent()
+                                }
+                            }
+                    }
+                )
+        }
+
+        private fun Project.commonModuleSourcePaths(
+            kotlinCompilation: Provider<KotlinCompilation<*>>
+        ): ConfigurableFileCollection {
+            return project.files(
+                project.provider {
+                    kotlinCompilation.get().allKotlinSourceSets
+                        .filter {
+                            it.dependsOn.isEmpty()
+                        }
+                        .flatMap {
+                            it.kotlin.sourceDirectories.files
+                        }
                 }
+            )
         }
 
         /**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiTask.kt
index f432494..7c59f11 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/GenerateApiTask.kt
@@ -92,7 +92,12 @@
         check(bootClasspath.files.isNotEmpty()) { "Android boot classpath not set." }
         check(sourcePaths.files.isNotEmpty()) { "Source paths not set." }
 
-        val inputs = JavaCompileInputs(sourcePaths, dependencyClasspath, bootClasspath)
+        val inputs = JavaCompileInputs(
+            sourcePaths = sourcePaths,
+            commonModuleSourcePaths = commonModuleSourcePaths,
+            dependencyClasspath = dependencyClasspath,
+            bootClasspath = bootClasspath
+        )
 
         val levelsArgs =
             getGenerateApiLevelsArgs(
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
index 3f18ee4..1bd89e1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaRunner.kt
@@ -250,9 +250,7 @@
     generateApiConfigs.forEach { (generateApiMode, apiLintMode) ->
         generateApi(
             metalavaClasspath,
-            files.bootClasspath,
-            files.dependencyClasspath,
-            files.sourcePaths.files,
+            files,
             apiLocation,
             generateApiMode,
             apiLintMode,
@@ -271,9 +269,7 @@
  */
 private fun generateApi(
     metalavaClasspath: FileCollection,
-    bootClasspath: FileCollection,
-    dependencyClasspath: FileCollection,
-    sourcePaths: Collection<File>,
+    files: JavaCompileInputs,
     outputLocation: ApiLocation,
     generateApiMode: GenerateApiMode,
     apiLintMode: ApiLintMode,
@@ -285,9 +281,10 @@
 ) {
     val args =
         getGenerateApiArgs(
-            bootClasspath,
-            dependencyClasspath,
-            sourcePaths,
+            files.bootClasspath,
+            files.dependencyClasspath,
+            files.sourcePaths.files,
+            files.commonModuleSourcePaths.files,
             outputLocation,
             generateApiMode,
             apiLintMode,
@@ -305,6 +302,7 @@
     bootClasspath: FileCollection,
     dependencyClasspath: FileCollection,
     sourcePaths: Collection<File>,
+    commonModuleSourcePaths: Collection<File>,
     outputLocation: ApiLocation?,
     generateApiMode: GenerateApiMode,
     apiLintMode: ApiLintMode,
@@ -318,10 +316,17 @@
             (bootClasspath.files + dependencyClasspath.files).joinToString(File.pathSeparator),
             "--source-path",
             sourcePaths.filter { it.exists() }.joinToString(File.pathSeparator),
-            "--format=v4",
-            "--warnings-as-errors"
         )
 
+    val existentCommonModuleSourcePaths = commonModuleSourcePaths.filter { it.exists() }
+    if (existentCommonModuleSourcePaths.isNotEmpty()) {
+        args += listOf("--common-source-path", existentCommonModuleSourcePaths.joinToString(":"))
+    }
+    args += listOf(
+        "--format=v4",
+        "--warnings-as-errors"
+    )
+
     pathToManifest?.let { args += listOf("--manifest", pathToManifest) }
 
     if (outputLocation != null) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt
index 66e7772..17e6ba4 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTask.kt
@@ -45,13 +45,17 @@
     /** Android's boot classpath */
     @get:Classpath lateinit var bootClasspath: FileCollection
 
-    /** Dependencies of [sourcePaths]. */
+    /** Dependencies (compiled classes) of [sourcePaths]. */
     @get:Classpath lateinit var dependencyClasspath: FileCollection
 
     /** Source files against which API signatures will be validated. */
     @get:[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
     var sourcePaths: FileCollection = project.files()
 
+    /** Multiplatform source files from the module's common sourceset */
+    @get:[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
+    var commonModuleSourcePaths: FileCollection = project.files()
+
     @get:[Optional InputFile PathSensitive(PathSensitivity.NONE)]
     abstract val manifestPath: RegularFileProperty
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index 38f7732..9902dd5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -210,6 +210,7 @@
 
     private fun applyInputs(inputs: JavaCompileInputs, task: MetalavaTask) {
         task.sourcePaths = inputs.sourcePaths
+        task.commonModuleSourcePaths = inputs.commonModuleSourcePaths
         task.dependsOn(inputs.sourcePaths)
         task.dependencyClasspath = inputs.dependencyClasspath
         task.bootClasspath = inputs.bootClasspath
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt
index 1dcc6f9..739fe9a 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/RegenerateOldApisTask.kt
@@ -110,7 +110,14 @@
         val jars = getJars(runnerProject, mavenId)
         val sources = getSources(runnerProject, mavenId + ":sources")
 
-        return JavaCompileInputs(sources, jars, project.getAndroidJar())
+        return JavaCompileInputs(
+            sourcePaths = sources,
+            // TODO(b/330721660) parse META-INF/kotlin-project-structure-metadata.json for
+            // common sources
+            commonModuleSourcePaths = project.files(),
+            dependencyClasspath = jars,
+            bootClasspath = project.getAndroidJar()
+        )
     }
 
     fun getJars(runnerProject: Project, mavenId: String): FileCollection {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
index 29bb09f..47998b0 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
@@ -61,6 +61,7 @@
                 bootClasspath,
                 dependencyClasspath,
                 sourcePaths.files.filter { it.exists() },
+                commonModuleSourcePaths.files.filter { it.exists() },
                 null,
                 GenerateApiMode.PublicApi,
                 ApiLintMode.CheckBaseline(baselineFile, targetsJavaConsumers.get()),
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index 8fe822f..c890c2a 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -93,10 +93,10 @@
 
     @get:Internal abstract val testLoader: Property<BuiltArtifactsLoader>
 
-    @get:Input abstract val testProjectPath: Property<String>
-
     @get:Input abstract val minSdk: Property<Int>
 
+    @get:Input abstract val macrobenchmark: Property<Boolean>
+
     @get:Input abstract val hasBenchmarkPlugin: Property<Boolean>
 
     @get:Input abstract val testRunner: Property<String>
@@ -192,7 +192,7 @@
                 // they run with dryRunMode to check crashes don't happen, without measurement
                 configBuilder.tag("androidx_unit_tests")
             }
-        } else if (testProjectPath.get().endsWith("macrobenchmark")) {
+        } else if (macrobenchmark.get()) {
             // macro benchmarks do not have a dryRunMode, so we don't run them in presubmit
             configBuilder.isMacrobenchmark(true)
             configBuilder.tag("macrobenchmarks")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index 9d04ef0..898da44 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -25,6 +25,7 @@
 import androidx.build.getPrivacySandboxFilesDirectory
 import androidx.build.getSupportRootFolder
 import androidx.build.hasBenchmarkPlugin
+import androidx.build.isMacrobenchmark
 import androidx.build.isPresubmitBuild
 import androidx.build.multiplatformExtension
 import com.android.build.api.artifact.Artifacts
@@ -124,10 +125,9 @@
             task.presubmit.set(isPresubmitBuild())
             task.instrumentationArgs.putAll(instrumentationRunnerArgs)
             task.minSdk.set(minSdk)
-            val hasBenchmarkPlugin = hasBenchmarkPlugin()
-            task.hasBenchmarkPlugin.set(hasBenchmarkPlugin)
+            task.hasBenchmarkPlugin.set(hasBenchmarkPlugin())
+            task.macrobenchmark.set(isMacrobenchmark())
             task.testRunner.set(testRunner)
-            task.testProjectPath.set(path)
             // Skip task if getTestSourceSetsForAndroid is empty, even if
             //  androidXExtension.deviceTests.enabled is set to true
             task.androidTestSourceCodeCollection.from(getTestSourceSetsForAndroid())
diff --git a/buildSrc/shared.gradle b/buildSrc/shared.gradle
index b135cf6..3923507 100644
--- a/buildSrc/shared.gradle
+++ b/buildSrc/shared.gradle
@@ -37,7 +37,8 @@
         jvmTarget = "17"
         freeCompilerArgs += [
                 "-Werror",
-                "-Xskip-metadata-version-check"
+                "-Xskip-metadata-version-check",
+                "-Xjdk-release=17",
         ]
         languageVersion = "1.8"
         apiVersion = "1.8"
diff --git a/busytown/androidx.sh b/busytown/androidx.sh
index 19e56cd..20ea0f3 100755
--- a/busytown/androidx.sh
+++ b/busytown/androidx.sh
@@ -23,12 +23,11 @@
       -Pandroidx.enableComposeCompilerMetrics=true \
       -Pandroidx.enableComposeCompilerReports=true \
       -Pandroidx.constraints=true \
-      --no-daemon \
-      --profile "$@"; then
+      --no-daemon "$@"; then
     EXIT_VALUE=1
   fi
 
-  # Parse performance profile reports (generated with the --profile option above) and re-export
+  # Parse performance profile reports (generated with the --profile option) and re-export
   # the metrics in an easily machine-readable format for tracking
   impl/parse_profile_data.sh
 fi
diff --git a/busytown/androidx_incremental.sh b/busytown/androidx_incremental.sh
index 0c0a486..949b04b 100755
--- a/busytown/androidx_incremental.sh
+++ b/busytown/androidx_incremental.sh
@@ -63,10 +63,7 @@
   EXIT_VALUE=1
 else
     # Run Gradle
-    # TODO: when b/278730831 ( https://youtrack.jetbrains.com/issue/KT-58547 ) is resolved, remove "-Pkotlin.incremental=false"
     if impl/build.sh $DIAGNOSE_ARG buildOnServer checkExternalLicenses listTaskOutputs exportSboms \
-        --profile \
-        -Pkotlin.incremental=false \
         "$@"; then
     echo build succeeded
     EXIT_VALUE=0
@@ -75,7 +72,7 @@
     EXIT_VALUE=1
     fi
 
-    # Parse performance profile reports (generated with the --profile option above) and re-export the metrics in an easily machine-readable format for tracking
+    # Parse performance profile reports (generated with the --profile option) and re-export the metrics in an easily machine-readable format for tracking
     impl/parse_profile_data.sh
 fi
 
diff --git a/busytown/impl/build-studio-and-androidx.sh b/busytown/impl/build-studio-and-androidx.sh
index d37dd88..1511b7c 100755
--- a/busytown/impl/build-studio-and-androidx.sh
+++ b/busytown/impl/build-studio-and-androidx.sh
@@ -99,5 +99,5 @@
   export USE_ANDROIDX_REMOTE_BUILD_CACHE=gcp
 fi
 
-$SCRIPTS_DIR/impl/build.sh $androidxArguments --profile --dependency-verification=off -Pandroidx.validateNoUnrecognizedMessages=false
+$SCRIPTS_DIR/impl/build.sh $androidxArguments --dependency-verification=off -Pandroidx.validateNoUnrecognizedMessages=false
 echo "Completing $0 at $(date)"
diff --git a/busytown/impl/build.sh b/busytown/impl/build.sh
index e1ffc07..050e210 100755
--- a/busytown/impl/build.sh
+++ b/busytown/impl/build.sh
@@ -56,11 +56,9 @@
   if eval "$*"; then
     return 0
   else
-    echo "Gradle command failed:" >&2
     # Echo the Gradle command formatted for ease of reading.
-    # Put each argument on its own line because some arguments may be long.
-    # Also put "\" at the end of non-final lines so the command can be copy-pasted
-    echo "$*" | sed 's/ / \\\n/g' | sed 's/^/    /' >&2
+    echo "Gradle command failed:" >&2
+    echo "    $*" >&2
     return 1
   fi
 }
diff --git a/camera/camera-camera2-pipe-integration/OWNERS b/camera/camera-camera2-pipe-integration/OWNERS
index c445688..6df0fb2 100644
--- a/camera/camera-camera2-pipe-integration/OWNERS
+++ b/camera/camera-camera2-pipe-integration/OWNERS
@@ -2,3 +2,8 @@
 codelogic@google.com
 sushilnath@google.com
 lnishan@google.com
+fungja@google.com
+scottnien@google.com
+trevormcguire@google.com
+kailianc@google.com
+wenhungteng@google.com
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/api/current.txt b/camera/camera-camera2-pipe-integration/api/current.txt
index 62060e3..e9d0832 100644
--- a/camera/camera-camera2-pipe-integration/api/current.txt
+++ b/camera/camera-camera2-pipe-integration/api/current.txt
@@ -55,7 +55,7 @@
     method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT> key);
   }
 
-  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions> {
+  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions?> {
     ctor public CaptureRequestOptions.Builder();
     method public androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions build();
     method public <ValueT> androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT> key);
diff --git a/camera/camera-camera2-pipe-integration/api/restricted_current.txt b/camera/camera-camera2-pipe-integration/api/restricted_current.txt
index 62060e3..e9d0832 100644
--- a/camera/camera-camera2-pipe-integration/api/restricted_current.txt
+++ b/camera/camera-camera2-pipe-integration/api/restricted_current.txt
@@ -55,7 +55,7 @@
     method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT> key);
   }
 
-  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions> {
+  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions?> {
     ctor public CaptureRequestOptions.Builder();
     method public androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions build();
     method public <ValueT> androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT> key);
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
index bfb2ddb..2029c95 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapter.kt
@@ -21,10 +21,13 @@
 import androidx.camera.camera2.pipe.CameraDevices
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.internal.CameraCompatibilityFilter.isBackwardCompatible
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.InitializationException
 import androidx.camera.core.concurrent.CameraCoordinator
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
 import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode
@@ -54,12 +57,24 @@
     var concurrentModeOn = false
 
     init {
-        concurrentCameraIdsSet = cameraDevices.awaitConcurrentCameraIds()!!.toMutableSet()
-        for (cameraIdSet in concurrentCameraIdsSet) {
+        val concurrentCameraIds = cameraDevices.awaitConcurrentCameraIds()!!.toMutableSet()
+        for (cameraIdSet in concurrentCameraIds) {
             val cameraIdsList = cameraIdSet.toList()
             if (cameraIdsList.size >= 2) {
                 val cameraId1: String = cameraIdsList[0].value
                 val cameraId2: String = cameraIdsList[1].value
+                var isBackwardCompatible = false
+                try {
+                    isBackwardCompatible = isBackwardCompatible(cameraId1, cameraDevices) &&
+                        isBackwardCompatible(cameraId2, cameraDevices)
+                } catch (e: InitializationException) {
+                    Log.debug { "Concurrent camera id pair: " +
+                        "($cameraId1, $cameraId2) is not backward compatible" }
+                }
+                if (!isBackwardCompatible) {
+                    continue
+                }
+                concurrentCameraIdsSet.add(cameraIdSet)
                 if (!concurrentCameraIdMap.containsKey(cameraId1)) {
                     concurrentCameraIdMap[cameraId1] = mutableListOf()
                 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
index 49e9ebe..ccc9ca7 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
@@ -22,6 +22,7 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.AutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.compat.workaround.InactiveSurfaceCloser
 import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection
+import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlash
 import dagger.Module
 
@@ -31,6 +32,7 @@
         AutoFlashAEModeDisabler.Bindings::class,
         InactiveSurfaceCloser.Bindings::class,
         MeteringRegionCorrection.Bindings::class,
+        UseFlashModeTorchFor3aUpdate.Bindings::class,
         UseTorchAsFlash.Bindings::class,
     ],
 )
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
index 22cc9ce..54af34b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
@@ -98,6 +98,9 @@
         if (TextureViewIsClosedQuirk.isEnabled(cameraMetadata)) {
             quirks.add(TextureViewIsClosedQuirk())
         }
+        if (TorchFlashRequiredFor3aUpdateQuirk.isEnabled(cameraMetadata)) {
+            quirks.add(TorchFlashRequiredFor3aUpdateQuirk(cameraMetadata))
+        }
         if (YuvImageOnePixelShiftQuirk.isEnabled()) {
             quirks.add(YuvImageOnePixelShiftQuirk())
         }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchFlashRequiredFor3aUpdateQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchFlashRequiredFor3aUpdateQuirk.kt
new file mode 100644
index 0000000..aa53ceb
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchFlashRequiredFor3aUpdateQuirk.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT
+import android.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.impl.isExternalFlashAeModeSupported
+import androidx.camera.core.impl.Quirk
+
+/**
+ * QuirkSummary
+ * - Bug Id: 294870640
+ * - Description: Quirk denoting the devices where [CaptureRequest.FLASH_MODE_TORCH] has
+ *  to be set for 3A states to be updated with good values (in some cases, AWB scanning is not
+ *  triggered at all). This results in problems like color tint or bad exposure in captured image
+ *  during captures where lighting condition changes (e.g. screen flash capture). This maybe
+ *  required even if a flash unit is not available (e.g. with front camera) and
+ *  [CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER] has been requested. If
+ *  [CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH] is supported, it can be used instead and thus
+ *  setting `FLASH_MODE_TORCH` won't be required.
+ * - Device(s): Pixel 6A, 6 PRO, 7, 7A, 7 PRO, 8, 8 PRO.
+ */
+@SuppressLint("CameraXQuirksClassDetector") // TODO: b/270421716 - enable when kotlin is supported.
+@RequiresApi(21) // TODO: b/200306659 - Remove and replace with annotation on package-info.java
+class TorchFlashRequiredFor3aUpdateQuirk(private val cameraMetadata: CameraMetadata) : Quirk {
+    /**
+     * Returns whether [CaptureRequest.FLASH_MODE_TORCH] is required to be set.
+     *
+     * This will check if the [CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH] is supported, which
+     * is more recommended than using a quirk like using `FLASH_MODE_TORCH`.
+     */
+    fun isFlashModeTorchRequired() = !cameraMetadata.isExternalFlashAeModeSupported()
+
+    companion object {
+        private val AFFECTED_PIXEL_MODELS: List<String> = mutableListOf(
+            "PIXEL 6A",
+            "PIXEL 6 PRO",
+            "PIXEL 7",
+            "PIXEL 7A",
+            "PIXEL 7 PRO",
+            "PIXEL 8",
+            "PIXEL 8 PRO"
+        )
+
+        fun isEnabled(cameraMetadata: CameraMetadata) = isAffectedModel(cameraMetadata)
+
+        private fun isAffectedModel(cameraMetadata: CameraMetadata) =
+            isAffectedPixelModel() && cameraMetadata.isFrontCamera
+
+        private fun isAffectedPixelModel(): Boolean {
+            AFFECTED_PIXEL_MODELS.forEach { model ->
+                if (Build.MODEL.uppercase() == model) {
+                    return true
+                }
+            }
+            return false
+        }
+
+        private val CameraMetadata.isFrontCamera: Boolean
+            get() = this[CameraCharacteristics.LENS_FACING] == LENS_FACING_FRONT
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/UseFlashModeTorchFor3aUpdate.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/UseFlashModeTorchFor3aUpdate.kt
new file mode 100644
index 0000000..e3a8c7a
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/UseFlashModeTorchFor3aUpdate.kt
@@ -0,0 +1,58 @@
+/*
+ * 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:RequiresApi(21) // TODO: b/200306659 - Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CaptureRequest
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.TorchFlashRequiredFor3aUpdateQuirk
+import dagger.Module
+import dagger.Provides
+
+/**
+ * Workaround to use [CaptureRequest.FLASH_MODE_TORCH] for 3A operation.
+ *
+ * @see TorchFlashRequiredFor3aUpdateQuirk
+ */
+interface UseFlashModeTorchFor3aUpdate {
+    fun shouldUseFlashModeTorch(): Boolean
+
+    @Module
+    abstract class Bindings {
+        companion object {
+            @Provides
+            fun provideUseFlashModeTorchFor3aUpdate(
+                cameraQuirks: CameraQuirks
+            ): UseFlashModeTorchFor3aUpdate =
+                if (cameraQuirks.quirks.contains(TorchFlashRequiredFor3aUpdateQuirk::class.java))
+                    UseFlashModeTorchFor3aUpdateImpl
+                else
+                    NotUseFlashModeTorchFor3aUpdate
+        }
+    }
+}
+
+object UseFlashModeTorchFor3aUpdateImpl : UseFlashModeTorchFor3aUpdate {
+    /** Returns true for torch should be used as flash. */
+    override fun shouldUseFlashModeTorch() = true
+}
+
+object NotUseFlashModeTorchFor3aUpdate : UseFlashModeTorchFor3aUpdate {
+    override fun shouldUseFlashModeTorch() = false
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraMetadataIntegration.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraMetadataIntegration.kt
new file mode 100644
index 0000000..080008e
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraMetadataIntegration.kt
@@ -0,0 +1,114 @@
+/*
+ * 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:RequiresApi(21) // TODO: b/200306659 - Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+
+/**
+ * Contains the CameraX-specific logic for [CameraMetadata].
+ */
+
+val CameraMetadata.availableAfModes
+        get() = getOrDefault(
+            CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES,
+            intArrayOf(CaptureRequest.CONTROL_AF_MODE_OFF)
+        ).asList()
+
+val CameraMetadata.availableAeModes
+    get() = getOrDefault(
+        CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AE_MODE_OFF)
+    ).asList()
+
+val CameraMetadata.availableAwbModes
+    get() = getOrDefault(
+        CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AWB_MODE_OFF)
+    ).asList()
+
+/**
+ * If preferredMode not available, priority is CONTINUOUS_PICTURE > AUTO > OFF
+ */
+fun CameraMetadata.getSupportedAfMode(preferredMode: Int) = when {
+    availableAfModes.contains(preferredMode) -> {
+        preferredMode
+    }
+
+    availableAfModes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) -> {
+        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+    }
+
+    availableAfModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO) -> {
+        CaptureRequest.CONTROL_AF_MODE_AUTO
+    }
+
+    else -> {
+        CaptureRequest.CONTROL_AF_MODE_OFF
+    }
+}
+
+/**
+ * If preferredMode not available, priority is AE_ON > AE_OFF
+ */
+fun CameraMetadata.getSupportedAeMode(preferredMode: Int) = when {
+    availableAeModes.contains(preferredMode) -> {
+        preferredMode
+    }
+
+    availableAeModes.contains(CaptureRequest.CONTROL_AE_MODE_ON) -> {
+        CaptureRequest.CONTROL_AE_MODE_ON
+    }
+
+    else -> {
+        CaptureRequest.CONTROL_AE_MODE_OFF
+    }
+}
+
+private fun CameraMetadata.isAeModeSupported(aeMode: Int) = getSupportedAeMode(aeMode) == aeMode
+
+/** Returns whether [CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH] is supported. */
+fun CameraMetadata.isExternalFlashAeModeSupported() =
+    Build.VERSION.SDK_INT >= 28 &&
+        isAeModeSupported(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
+
+/**
+ * If preferredMode not available, priority is AWB_AUTO > AWB_OFF
+ */
+fun CameraMetadata.getSupportedAwbMode(preferredMode: Int) = when {
+    availableAwbModes.contains(preferredMode) -> {
+        preferredMode
+    }
+
+    availableAwbModes.contains(CaptureRequest.CONTROL_AWB_MODE_AUTO) -> {
+        CaptureRequest.CONTROL_AWB_MODE_AUTO
+    }
+
+    else -> {
+        CaptureRequest.CONTROL_AWB_MODE_OFF
+    }
+}
+
+fun <T> CameraMetadata?.getOrDefault(
+    key: CameraCharacteristics.Key<T>,
+    default: T
+) = this?.getOrDefault(key, default) ?: default
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
index 125f776..3eb4b54 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
@@ -21,6 +21,7 @@
 import androidx.camera.camera2.pipe.core.Log.warn
 import androidx.camera.camera2.pipe.integration.adapter.awaitUntil
 import androidx.camera.camera2.pipe.integration.adapter.propagateTo
+import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.CameraControl
 import androidx.camera.core.ImageCapture
@@ -48,8 +49,11 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @CameraScope
 class FlashControl @Inject constructor(
+    private val cameraProperties: CameraProperties,
     private val state3AControl: State3AControl,
     private val threads: UseCaseThreads,
+    private val torchControl: TorchControl,
+    private val useFlashModeTorchFor3aUpdate: UseFlashModeTorchFor3aUpdate,
 ) : UseCaseCameraControl {
     private var _useCaseCamera: UseCaseCamera? = null
     override var useCaseCamera: UseCaseCamera?
@@ -148,32 +152,17 @@
         val pendingTasks = mutableListOf<Deferred<Unit>>()
 
         // Invoke ScreenFlash#apply and wait later for its listener to be completed
-        applyScreenFlash(
-            TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS)
-        ).let {
-            pendingTasks.add(it)
-        }
+        pendingTasks.add(
+            applyScreenFlash(
+                TimeUnit.SECONDS.toMillis(ImageCapture.SCREEN_FLASH_UI_APPLY_TIMEOUT_SECONDS)
+            )
+        )
 
-        // Enable external flash AE mode if possible
-        val isExternalFlashAeModeSupported = state3AControl.isExternalFlashAeModeSupported()
-        debug {
-            "startScreenFlashCaptureTasks: isExternalFlashAeModeSupported = " +
-                "$isExternalFlashAeModeSupported"
-        }
-        if (isExternalFlashAeModeSupported) {
-            state3AControl.tryExternalFlashAeMode = true
-            state3AControl.updateSignal?.let {
-                debug {
-                    "startScreenFlashCaptureTasks: need to wait for state3AControl.updateSignal"
-                }
-                pendingTasks.add(it)
-                it.invokeOnCompletion {
-                    debug { "startScreenFlashCaptureTasks: state3AControl.updateSignal completed" }
-                }
-            }
-        }
+        // Try to set external flash AE mode if possible
+        setExternalFlashAeModeAsync()?.let { pendingTasks.add(it) }
 
-        // TODO: b/326170400 - Enable torch mode if TorchFlashRequiredFor3aUpdateQuirk added
+        // Set FLASH_MODE_TORCH for quirks
+        setTorchForScreenFlash()?.let { pendingTasks.add(it) }
 
         pendingTasks.awaitAll()
     }
@@ -215,18 +204,77 @@
         }
     }
 
+    /**
+     * Tries to set external flash AE mode if possible.
+     *
+     * @return A [Deferred] that reports the completion of the operation, `null` if not supported.
+     */
+    private fun setExternalFlashAeModeAsync(): Deferred<Unit>? {
+        val isExternalFlashAeModeSupported =
+            cameraProperties.metadata.isExternalFlashAeModeSupported()
+        debug {
+            "setExternalFlashAeModeAsync: isExternalFlashAeModeSupported = " +
+                "$isExternalFlashAeModeSupported"
+        }
+
+        if (!isExternalFlashAeModeSupported) {
+            return null
+        }
+
+        state3AControl.tryExternalFlashAeMode = true
+        return state3AControl.updateSignal?.also {
+            debug {
+                "setExternalFlashAeModeAsync: need to wait for state3AControl.updateSignal"
+            }
+            it.invokeOnCompletion {
+                debug { "setExternalFlashAeModeAsync: state3AControl.updateSignal completed" }
+            }
+        }
+    }
+
+    /**
+     * Enables the torch mode for screen flash capture when required.
+     *
+     * Since this is required due to a device quirk despite lacking physical flash unit, the
+     * `ignoreFlashUnitAvailability` parameter is set to `true` while invoking
+     * [TorchControl.setTorchAsync].
+     *
+     * @return A [Deferred] that reports the completion of the operation, `null` if not required.
+     */
+    private fun setTorchForScreenFlash(): Deferred<Unit>? {
+        val shouldUseFlashModeTorch = useFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()
+        debug {
+            "setTorchIfRequired: shouldUseFlashModeTorch = $shouldUseFlashModeTorch"
+        }
+
+        if (!shouldUseFlashModeTorch) {
+            return null
+        }
+
+        return torchControl.setTorchAsync(torch = true, ignoreFlashUnitAvailability = true).also {
+            debug {
+                "setTorchIfRequired: need to wait for torch control to be completed"
+            }
+            it.invokeOnCompletion {
+                debug { "setTorchIfRequired: torch control completed" }
+            }
+        }
+    }
+
     suspend fun stopScreenFlashCaptureTasks() {
         withContext(Dispatchers.Main) {
             screenFlash?.clear()
             debug { "screenFlashPostCapture: ScreenFlash.clear() invoked" }
         }
 
-        if (state3AControl.isExternalFlashAeModeSupported()) {
+        if (cameraProperties.metadata.isExternalFlashAeModeSupported()) {
             // Disable external flash AE mode, ok to complete whenever
             state3AControl.tryExternalFlashAeMode = false
         }
 
-        // TODO: b/326170400 - Disable torch mode if TorchFlashRequiredFor3aUpdateQuirk added
+        if (useFlashModeTorchFor3aUpdate.shouldUseFlashModeTorch()) {
+            torchControl.setTorchAsync(torch = false, ignoreFlashUnitAvailability = true)
+        }
     }
 
     @Module
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
index db8129e..861f79f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
@@ -195,8 +195,9 @@
         val processorSessionConfig = synchronized(lock) {
             if (isClosed()) return@launch configure(null)
             try {
-                DeferrableSurfaces.incrementAll(deferrableSurfaces)
-                postviewDeferrableSurface?.incrementUseCount()
+                val surfacesToIncrement = ArrayList(deferrableSurfaces)
+                postviewDeferrableSurface?.let { surfacesToIncrement.add(it) }
+                DeferrableSurfaces.incrementAll(surfacesToIncrement)
             } catch (exception: DeferrableSurface.SurfaceClosedException) {
                 sessionConfigAdapter.reportSurfaceInvalid(exception.deferrableSurface)
                 return@launch configure(null)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
index fb05144..99f069e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
@@ -16,10 +16,8 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
-import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
 import android.hardware.camera2.CaptureRequest
-import android.os.Build
 import android.util.Range
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
@@ -47,7 +45,7 @@
 class State3AControl @Inject constructor(
     val cameraProperties: CameraProperties,
     private val aeModeDisabler: AutoFlashAEModeDisabler,
-    private val aeFpsRange: AeFpsRange
+    private val aeFpsRange: AeFpsRange,
 ) : UseCaseCameraControl, UseCaseCamera.RunningUseCasesChangeListener {
     private var _useCaseCamera: UseCaseCamera? = null
     override var useCaseCamera: UseCaseCamera?
@@ -74,19 +72,6 @@
         }
     }
 
-    private val afModes = cameraProperties.metadata.getOrDefault(
-        CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES,
-        intArrayOf(CaptureRequest.CONTROL_AF_MODE_OFF)
-    ).asList()
-    private val aeModes = cameraProperties.metadata.getOrDefault(
-        CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES,
-        intArrayOf(CaptureRequest.CONTROL_AE_MODE_OFF)
-    ).asList()
-    private val awbModes = cameraProperties.metadata.getOrDefault(
-        CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES,
-        intArrayOf(CaptureRequest.CONTROL_AWB_MODE_OFF)
-    ).asList()
-
     private val lock = Any()
 
     @GuardedBy("lock")
@@ -134,7 +119,7 @@
 
         // Overwrite AE mode to ON_EXTERNAL_FLASH only if required and explicitly supported
         if (tryExternalFlashAeMode) {
-            val isSupported = isExternalFlashAeModeSupported()
+            val isSupported = cameraProperties.metadata.isExternalFlashAeModeSupported()
             debug { "State3AControl.invalidate: trying external flash AE mode" +
                 ", supported = $isSupported" }
             if (isSupported) {
@@ -154,10 +139,16 @@
         val preferAfMode = preferredFocusMode ?: getDefaultAfMode()
 
         val parameters: MutableMap<CaptureRequest.Key<*>, Any> = mutableMapOf(
-            CaptureRequest.CONTROL_AE_MODE to getSupportedAeMode(preferAeMode),
-            CaptureRequest.CONTROL_AF_MODE to getSupportedAfMode(preferAfMode),
-            CaptureRequest.CONTROL_AWB_MODE to getSupportedAwbMode(
-                CaptureRequest.CONTROL_AWB_MODE_AUTO))
+            CaptureRequest.CONTROL_AE_MODE to cameraProperties.metadata.getSupportedAeMode(
+                preferAeMode
+            ),
+            CaptureRequest.CONTROL_AF_MODE to cameraProperties.metadata.getSupportedAfMode(
+                preferAfMode
+            ),
+            CaptureRequest.CONTROL_AWB_MODE to cameraProperties.metadata.getSupportedAwbMode(
+                CaptureRequest.CONTROL_AWB_MODE_AUTO
+            )
+        )
 
         preferredAeFpsRange?.let {
             parameters[CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE] = it
@@ -188,67 +179,6 @@
         else -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
     }
 
-    /**
-     * If preferredMode not available, priority is CONTINUOUS_PICTURE > AUTO > OFF
-     */
-    private fun getSupportedAfMode(preferredMode: Int) = when {
-        afModes.contains(preferredMode) -> {
-            preferredMode
-        }
-
-        afModes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) -> {
-            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
-        }
-
-        afModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO) -> {
-            CaptureRequest.CONTROL_AF_MODE_AUTO
-        }
-
-        else -> {
-            CaptureRequest.CONTROL_AF_MODE_OFF
-        }
-    }
-
-    /**
-     * If preferredMode not available, priority is AE_ON > AE_OFF
-     */
-    private fun getSupportedAeMode(preferredMode: Int) = when {
-        aeModes.contains(preferredMode) -> {
-            preferredMode
-        }
-
-        aeModes.contains(CaptureRequest.CONTROL_AE_MODE_ON) -> {
-            CaptureRequest.CONTROL_AE_MODE_ON
-        }
-
-        else -> {
-            CaptureRequest.CONTROL_AE_MODE_OFF
-        }
-    }
-
-    private fun isAeModeSupported(aeMode: Int) = getSupportedAeMode(aeMode) == aeMode
-
-    fun isExternalFlashAeModeSupported() =
-        Build.VERSION.SDK_INT >= 28 &&
-            isAeModeSupported(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
-
-    /**
-     * If preferredMode not available, priority is AWB_AUTO > AWB_OFF
-     */
-    private fun getSupportedAwbMode(preferredMode: Int) = when {
-        awbModes.contains(preferredMode) -> {
-            preferredMode
-        }
-
-        awbModes.contains(CaptureRequest.CONTROL_AWB_MODE_AUTO) -> {
-            CaptureRequest.CONTROL_AWB_MODE_AUTO
-        }
-
-        else -> {
-            CaptureRequest.CONTROL_AWB_MODE_OFF
-        }
-    }
-
     private fun Collection<UseCase>.updateTemplate() {
         SessionConfigAdapter(this).getValidSessionConfigOrNull()?.let {
             val templateType = it.repeatingCaptureConfig.templateType
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
index 683ef5f..1699de3 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
@@ -76,10 +76,22 @@
 
     private var _updateSignal: CompletableDeferred<Unit>? = null
 
-    fun setTorchAsync(torch: Boolean, cancelPreviousTask: Boolean = true): Deferred<Unit> {
+    /**
+     * Turn the torch on or off.
+     *
+     * @param torch Whether the torch should be on or off.
+     * @param cancelPreviousTask Whether to cancel the previous task if it's running.
+     * @param ignoreFlashUnitAvailability Whether to ignore the flash unit availability. When true,
+     *      torch mode setting will be attempted even if a physical flash unit is not available.
+     */
+    fun setTorchAsync(
+        torch: Boolean,
+        cancelPreviousTask: Boolean = true,
+        ignoreFlashUnitAvailability: Boolean = false
+    ): Deferred<Unit> {
         val signal = CompletableDeferred<Unit>()
 
-        if (!hasFlashUnit) {
+        if (!ignoreFlashUnitAvailability && !hasFlashUnit) {
             return signal.createFailureResult(IllegalStateException("No flash unit"))
         }
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilter.kt
index 014101e..e734590 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraCompatibilityFilter.kt
@@ -52,7 +52,7 @@
     }
 
     @JvmStatic
-    private fun isBackwardCompatible(cameraId: String, cameraDevices: CameraDevices): Boolean {
+    fun isBackwardCompatible(cameraId: String, cameraDevices: CameraDevices): Boolean {
         // Always returns true to not break robolectric tests because the cameras setup in
         // robolectric don't have REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE capability
         // by default.
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
index 442f7b1..c190c68 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraCoordinatorAdapterTest.kt
@@ -17,6 +17,8 @@
 package androidx.camera.camera2.pipe.integration.adapter
 
 import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata
 import android.os.Build
 import androidx.camera.camera2.pipe.CameraBackendId
 import androidx.camera.camera2.pipe.CameraGraph
@@ -43,14 +45,23 @@
 import org.mockito.kotlin.whenever
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.util.ReflectionHelpers
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class CameraCoordinatorAdapterTest {
 
-    private val cameraMetadata0 = FakeCameraMetadata(cameraId = CameraId("0"))
-    private val cameraMetadata1 = FakeCameraMetadata(cameraId = CameraId("1"))
+    private val cameraMetadata0 = FakeCameraMetadata(cameraId = CameraId("0"),
+        characteristics = mapOf(
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES to
+                intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE),
+        ))
+    private val cameraMetadata1 = FakeCameraMetadata(cameraId = CameraId("1"),
+        characteristics = mapOf(
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES to
+                intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE),
+        ))
     private val cameraMetadata2 = FakeCameraMetadata(cameraId = CameraId("2"))
 
     private val cameraDevices = FakeCameraDevices(
@@ -98,10 +109,14 @@
             ),
         )
     )
-    private val cameraCoordinatorAdapter = CameraCoordinatorAdapter(cameraPipe, cameraDevices)
+    private lateinit var cameraCoordinatorAdapter: CameraCoordinatorAdapter
 
     @Before
     fun setUp() {
+        // Customizes Build.FINGERPRINT to be not "fingerprint", so that cameras without
+        // REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE will be filtered.
+        ReflectionHelpers.setStaticField(Build::class.java, "FINGERPRINT", "fake-fingerprint")
+        cameraCoordinatorAdapter = CameraCoordinatorAdapter(cameraPipe, cameraDevices)
         cameraCoordinatorAdapter.registerCamera("0", mockCameraInternalAdapter0)
         cameraCoordinatorAdapter.registerCamera("1", mockCameraInternalAdapter1)
         cameraCoordinatorAdapter.registerCamera("2", mockCameraInternalAdapter2)
@@ -110,9 +125,8 @@
     @Test
     fun getConcurrentCameraSelectors() {
         val cameraSelectors = cameraCoordinatorAdapter.concurrentCameraSelectors
-        assertThat(cameraSelectors.size).isEqualTo(2)
+        assertThat(cameraSelectors.size).isEqualTo(1)
         assertThat(cameraSelectors[0].size).isEqualTo(2)
-        assertThat(cameraSelectors[1].size).isEqualTo(2)
     }
 
     @Test
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchFlashRequiredFor3aUpdateQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchFlashRequiredFor3aUpdateQuirkTest.kt
new file mode 100644
index 0000000..35bd790
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchFlashRequiredFor3aUpdateQuirkTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.impl.Quirks
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowBuild
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.StreamConfigurationMapBuilder
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = 21)
+class TorchFlashRequiredFor3aUpdateQuirkTest(
+    private val model: String,
+    private val lensFacing: Int,
+    private val externalFlashAeModeSupported: Boolean,
+    private val enabled: Boolean
+) {
+    companion object {
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(
+            name = "Model: {0}, lens facing: {1}, external ae mode: {2}, enabled: {3}"
+        )
+        fun data() = listOf(
+            arrayOf("Pixel 3a", CameraCharacteristics.LENS_FACING_FRONT, false, false),
+            arrayOf("Pixel 4", CameraCharacteristics.LENS_FACING_FRONT, true, false),
+            arrayOf("Pixel 6", CameraCharacteristics.LENS_FACING_FRONT, false, false),
+            arrayOf("Pixel 6A", CameraCharacteristics.LENS_FACING_BACK, false, false),
+            arrayOf("Pixel 6A", CameraCharacteristics.LENS_FACING_FRONT, false, true),
+            arrayOf("Pixel 7 pro", CameraCharacteristics.LENS_FACING_FRONT, false, true),
+            arrayOf("Pixel 8", CameraCharacteristics.LENS_FACING_FRONT, false, true),
+            arrayOf("SM-A320FL", CameraCharacteristics.LENS_FACING_FRONT, false, false),
+        )
+    }
+
+    private fun getCameraQuirks(
+        lensFacing: Int,
+        externalFlashAeModeSupported: Boolean,
+    ): Quirks {
+        val characteristicsMap = mutableMapOf<CameraCharacteristics.Key<*>, Any?>().apply {
+            this[CameraCharacteristics.LENS_FACING] = lensFacing
+
+            this[CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES] =
+                if (externalFlashAeModeSupported) {
+                    intArrayOf(CameraMetadata.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
+                } else intArrayOf(
+                    CameraMetadata.CONTROL_AE_MODE_ON
+                )
+        }.toMap()
+
+        val cameraCharacteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+        val shadowCharacteristics =
+            Shadow.extract<ShadowCameraCharacteristics>(cameraCharacteristics)
+        characteristicsMap.forEach { entry ->
+            shadowCharacteristics.set(entry.key, entry.value)
+        }
+
+        val cameraMetadata = FakeCameraMetadata(characteristicsMap)
+
+        return CameraQuirks(
+            cameraMetadata,
+            StreamConfigurationMapCompat(
+                StreamConfigurationMapBuilder.newBuilder().build(),
+                OutputSizesCorrector(
+                    cameraMetadata,
+                    StreamConfigurationMapBuilder.newBuilder().build()
+                )
+            ),
+        ).quirks
+    }
+
+    @Test
+    fun canEnableQuirkCorrectly() {
+        // Arrange
+        ShadowBuild.setModel(model)
+        val cameraQuirks = getCameraQuirks(lensFacing, externalFlashAeModeSupported)
+
+        // Act
+        val isFlashModeTorchRequired =
+            cameraQuirks.get(TorchFlashRequiredFor3aUpdateQuirk::class.java)
+                ?.isFlashModeTorchRequired() ?: false
+
+        // Verify
+        Truth.assertThat(isFlashModeTorchRequired).isEqualTo(enabled)
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CameraMetadataIntegrationTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CameraMetadataIntegrationTest.kt
new file mode 100644
index 0000000..2dd94aa
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CameraMetadataIntegrationTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata
+import android.os.Build
+import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@DoNotInstrument
+class CameraMetadataIntegrationTest {
+    private lateinit var cameraMetadata: androidx.camera.camera2.pipe.CameraMetadata
+
+    private fun initCameraMetadata(
+        cameraCharacteristics: Map<CameraCharacteristics.Key<*>, Any?> = emptyMap()
+    ) {
+        cameraMetadata = FakeCameraMetadata(cameraCharacteristics)
+    }
+
+    @Before
+    fun setUp() {
+        initCameraMetadata()
+    }
+
+    @Test
+    fun getSupportedAeMode_returnsPreferredMode_whenSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
+                CameraMetadata.CONTROL_AE_MODE_ON,
+                CameraMetadata.CONTROL_AE_MODE_OFF,
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAeMode(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+        ).isEqualTo(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+    }
+
+    @Test
+    fun getSupportedAeMode_returnsAeModeOnIfSupported_whenPreferredModeNotSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AE_MODE_ON,
+                CameraMetadata.CONTROL_AE_MODE_OFF,
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAeMode(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+        ).isEqualTo(CameraMetadata.CONTROL_AE_MODE_ON)
+    }
+
+    @Test
+    fun getSupportedAeMode_returnsAeModeOff_whenPreferredModeAndAeModeOnNotSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AE_MODE_OFF,
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAeMode(CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+        ).isEqualTo(CameraMetadata.CONTROL_AE_MODE_OFF)
+    }
+
+    @Test
+    @Config(maxSdk = 27)
+    fun isExternalFlashAeModeSupported_returnsFalseWhenBelowApiLevel28() {
+        assertThat(cameraMetadata.isExternalFlashAeModeSupported()).isFalse()
+    }
+
+    @Test
+    @Config(minSdk = 28)
+    fun isExternalFlashAeModeSupported_returnsFalseWhenNotSupported() {
+        assertThat(cameraMetadata.isExternalFlashAeModeSupported()).isFalse()
+    }
+
+    @Test
+    @Config(minSdk = 28)
+    fun isExternalFlashAeModeSupported_returnsTrueWhenSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AE_MODE_ON_EXTERNAL_FLASH,
+            )
+        ))
+
+        assertThat(cameraMetadata.isExternalFlashAeModeSupported()).isTrue()
+    }
+
+    @Test
+    fun getSupportedAfMode_returnsPreferredMode_whenSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO,
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAfMode(CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+        ).isEqualTo(CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+    }
+
+    @Test
+    fun getSupportedAfMode_returnsContinuousPictureIfSupported_whenPreferredModeNotSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+                CameraMetadata.CONTROL_AF_MODE_AUTO,
+                CameraMetadata.CONTROL_AF_MODE_OFF
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAfMode(CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+        ).isEqualTo(CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+    }
+
+    @Test
+    fun getSupportedAfMode_returnsAfModeAutoIfSupported_whenNoPreferredModeAndContinuousPicture() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AF_MODE_AUTO,
+                CameraMetadata.CONTROL_AF_MODE_OFF
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAfMode(CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+        ).isEqualTo(CameraMetadata.CONTROL_AF_MODE_AUTO)
+    }
+
+    @Test
+    fun getSupportedAfMode_returnsAfModeOff_whenNoPreferredModeAndContinuousPictureAndAfModeAuto() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AF_MODE_OFF
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAfMode(CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+        ).isEqualTo(CameraMetadata.CONTROL_AF_MODE_OFF)
+    }
+
+    @Test
+    fun getSupportedAwbMode_returnsPreferredMode_whenSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AWB_MODE_DAYLIGHT,
+                CameraMetadata.CONTROL_AWB_MODE_AUTO,
+                CameraMetadata.CONTROL_AWB_MODE_OFF
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAwbMode(CameraMetadata.CONTROL_AWB_MODE_DAYLIGHT)
+        ).isEqualTo(CameraMetadata.CONTROL_AWB_MODE_DAYLIGHT)
+    }
+
+    @Test
+    fun getSupportedAwbMode_returnsAwbModeAutoIfSupported_whenPreferredModeNotSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AWB_MODE_AUTO,
+                CameraMetadata.CONTROL_AWB_MODE_OFF
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAwbMode(CameraMetadata.CONTROL_AWB_MODE_DAYLIGHT)
+        ).isEqualTo(CameraMetadata.CONTROL_AWB_MODE_AUTO)
+    }
+
+    @Test
+    fun getSupportedAwbMode_returnsAwbModeOff_whenPreferredModeAndAwbModeAutoNotSupported() {
+        initCameraMetadata(mapOf(
+            CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES to intArrayOf(
+                CameraMetadata.CONTROL_AWB_MODE_OFF
+            )
+        ))
+
+        assertThat(
+            cameraMetadata.getSupportedAwbMode(CameraMetadata.CONTROL_AWB_MODE_DAYLIGHT)
+        ).isEqualTo(CameraMetadata.CONTROL_AWB_MODE_OFF)
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index d2772f5..4ad90e72 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -46,6 +46,7 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.AeFpsRange
 import androidx.camera.camera2.pipe.integration.compat.workaround.CapturePipelineTorchCorrection
 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
+import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.integration.compat.workaround.UseTorchAsFlashImpl
@@ -272,18 +273,11 @@
                         )
                     )
                 )
-            )
+            ),
         ).apply {
             useCaseCamera = fakeUseCaseCamera
         }
 
-        flashControl = FlashControl(
-            state3AControl = state3AControl,
-            threads = fakeUseCaseThreads,
-        ).apply {
-            setScreenFlash(this@CapturePipelineTest.screenFlash)
-        }
-
         torchControl = TorchControl(
             fakeCameraProperties,
             state3AControl,
@@ -298,6 +292,16 @@
             fakeRequestControl.torchUpdateEventList.clear()
         }
 
+        flashControl = FlashControl(
+            cameraProperties = fakeCameraProperties,
+            state3AControl = state3AControl,
+            threads = fakeUseCaseThreads,
+            torchControl = torchControl,
+            useFlashModeTorchFor3aUpdate = NotUseFlashModeTorchFor3aUpdate,
+        ).apply {
+            setScreenFlash(this@CapturePipelineTest.screenFlash)
+        }
+
         fakeUseCaseCameraState = UseCaseCameraState(
             fakeUseCaseGraphConfig,
             fakeUseCaseThreads,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt
index 36838d5..d7b27cc 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt
@@ -25,13 +25,16 @@
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
 import androidx.camera.camera2.pipe.integration.compat.workaround.AeFpsRange
 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
+import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.integration.compat.workaround.UseFlashModeTorchFor3aUpdateImpl
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCamera
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.CameraControl
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.TorchState
 import androidx.camera.testing.impl.mocks.MockScreenFlash
 import androidx.testutils.MainDispatcherRule
 import androidx.testutils.assertThrows
@@ -94,6 +97,7 @@
         )
     )
     private lateinit var state3AControl: State3AControl
+    private lateinit var torchControl: TorchControl
     private lateinit var flashControl: FlashControl
 
     private val screenFlash = MockScreenFlash()
@@ -103,7 +107,10 @@
         createFlashControl()
     }
 
-    private fun createFlashControl(addExternalFlashAeMode: Boolean = false) {
+    private fun createFlashControl(
+        addExternalFlashAeMode: Boolean = false,
+        useFlashModeTorch: Boolean = false,
+    ) {
         val aeAvailableModes = mutableListOf(
             CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
             CaptureRequest.CONTROL_AE_MODE_ON,
@@ -120,18 +127,34 @@
                 CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES to aeAvailableModes.toIntArray()
             )
         )
+        val cameraProperties = FakeCameraProperties(metadata)
 
         state3AControl = State3AControl(
-            FakeCameraProperties(metadata),
+            cameraProperties,
             NoOpAutoFlashAEModeDisabler,
-            aeFpsRange
+            aeFpsRange,
+        ).apply {
+            useCaseCamera = fakeUseCaseCamera
+        }
+
+        torchControl = TorchControl(
+            cameraProperties,
+            state3AControl,
+            fakeUseCaseThreads
         ).apply {
             useCaseCamera = fakeUseCaseCamera
         }
 
         flashControl = FlashControl(
+            cameraProperties = cameraProperties,
             state3AControl = state3AControl,
             threads = fakeUseCaseThreads,
+            torchControl = torchControl,
+            useFlashModeTorchFor3aUpdate = if (useFlashModeTorch) {
+                UseFlashModeTorchFor3aUpdateImpl
+            } else {
+                NotUseFlashModeTorchFor3aUpdate
+            },
         )
         flashControl.useCaseCamera = fakeUseCaseCamera
         flashControl.setScreenFlash(screenFlash)
@@ -143,14 +166,21 @@
         val fakeCameraProperties = FakeCameraProperties()
 
         val flashControl = FlashControl(
+            fakeCameraProperties,
             State3AControl(
                 fakeCameraProperties,
                 NoOpAutoFlashAEModeDisabler,
-                aeFpsRange
+                aeFpsRange,
             ).apply {
                 useCaseCamera = fakeUseCaseCamera
             },
             fakeUseCaseThreads,
+            TorchControl(
+                fakeCameraProperties,
+                state3AControl,
+                fakeUseCaseThreads
+            ),
+            NotUseFlashModeTorchFor3aUpdate
         )
 
         assertThrows<CameraControl.OperationCanceledException> {
@@ -370,6 +400,44 @@
     }
 
     @Test
+    fun torchNotEnabledAtScreenFlashCapture_whenNotRequired() = runTest {
+        createFlashControl(addExternalFlashAeMode = false, useFlashModeTorch = false)
+
+        flashControl.startScreenFlashCaptureTasks()
+
+        assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.OFF)
+    }
+
+    @Test
+    fun torchEnabledAtScreenFlashCapture_whenRequired() = runTest {
+        createFlashControl(addExternalFlashAeMode = false, useFlashModeTorch = true)
+
+        flashControl.startScreenFlashCaptureTasks()
+
+        assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.ON)
+    }
+
+    @Test
+    fun torchEnabled_whenScreenFlashCaptureApplyNotCompleted() = runTest {
+        createFlashControl(addExternalFlashAeMode = false, useFlashModeTorch = true)
+        screenFlash.setApplyCompletedInstantly(false)
+
+        flashControl.startScreenFlashCaptureTasks()
+
+        assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.ON)
+    }
+
+    @Test
+    fun torchDisabledAtScreenFlashCaptureStop_whenRequired() = runTest {
+        createFlashControl(addExternalFlashAeMode = false, useFlashModeTorch = true)
+        flashControl.startScreenFlashCaptureTasks()
+
+        flashControl.stopScreenFlashCaptureTasks()
+
+        assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.OFF)
+    }
+
+    @Test
     fun screenFlashClearInvokedInMainThread_whenStopped() = runTest {
         withContext(Dispatchers.IO) { // ensures initial call is not from main thread
             flashControl.stopScreenFlashCaptureTasks()
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
index 7e05158..e708509 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/StillCaptureRequestTest.kt
@@ -22,6 +22,7 @@
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseFlashModeTorchFor3aUpdate
 import androidx.camera.camera2.pipe.integration.compat.workaround.NotUseTorchAsFlash
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
@@ -91,9 +92,18 @@
 
     private lateinit var fakeUseCaseCamera: UseCaseCamera
 
+    private val torchControl = TorchControl(
+        fakeCameraProperties,
+        fakeState3AControl,
+        fakeUseCaseThreads
+    )
+
     private val flashControl = FlashControl(
+        fakeCameraProperties,
         fakeState3AControl,
         fakeUseCaseThreads,
+        torchControl,
+        NotUseFlashModeTorchFor3aUpdate,
     )
 
     private val stillCaptureRequestControl = StillCaptureRequestControl(
@@ -438,24 +448,28 @@
             threads = fakeUseCaseThreads,
             sessionProcessorManager = null,
         )
+        val torchControl = TorchControl(
+            fakeCameraProperties,
+            fakeState3AControl,
+            fakeUseCaseThreads
+        )
         requestControl = UseCaseCameraRequestControlImpl(
             capturePipeline = CapturePipelineImpl(
                 configAdapter = fakeConfigAdapter,
                 cameraProperties = fakeCameraProperties,
                 requestListener = ComboRequestListener(),
                 threads = fakeUseCaseThreads,
-                torchControl = TorchControl(
-                    fakeCameraProperties,
-                    fakeState3AControl,
-                    fakeUseCaseThreads
-                ),
+                torchControl = torchControl,
                 useCaseGraphConfig = fakeUseCaseGraphConfig,
                 useCaseCameraState = fakeUseCaseCameraState,
                 useTorchAsFlash = NotUseTorchAsFlash,
                 sessionProcessorManager = null,
                 flashControl = FlashControl(
+                    cameraProperties = fakeCameraProperties,
                     state3AControl = fakeState3AControl,
                     threads = fakeUseCaseThreads,
+                    torchControl = torchControl,
+                    useFlashModeTorchFor3aUpdate = NotUseFlashModeTorchFor3aUpdate,
                 ),
             ),
             state = fakeUseCaseCameraState,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
index e5d846e..f4cdd2d 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
@@ -109,7 +109,7 @@
             State3AControl(
                 fakeCameraProperties,
                 NoOpAutoFlashAEModeDisabler,
-                aeFpsRange
+                aeFpsRange,
             ).apply {
                 useCaseCamera = fakeUseCaseCamera
             },
@@ -130,7 +130,7 @@
                 State3AControl(
                     fakeCameraProperties,
                     NoOpAutoFlashAEModeDisabler,
-                    aeFpsRange
+                    aeFpsRange,
                 ).apply {
                     useCaseCamera = fakeUseCaseCamera
                 },
@@ -151,7 +151,7 @@
             State3AControl(
                 fakeCameraProperties,
                 NoOpAutoFlashAEModeDisabler,
-                aeFpsRange
+                aeFpsRange,
             ).apply {
 
                 useCaseCamera = fakeUseCaseCamera
@@ -175,7 +175,7 @@
                 State3AControl(
                     fakeCameraProperties,
                     NoOpAutoFlashAEModeDisabler,
-                    aeFpsRange
+                    aeFpsRange,
                 ).apply {
                     useCaseCamera = fakeUseCaseCamera
                 },
@@ -198,6 +198,30 @@
     }
 
     @Test
+    fun enableTorch_torchStateOn_whenNoFlashUnit_butFlashUnitAvailabilityIsIgnored() = runBlocking {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val fakeCameraProperties = FakeCameraProperties()
+
+        val torchControl = TorchControl(
+            fakeCameraProperties,
+            State3AControl(
+                fakeCameraProperties,
+                NoOpAutoFlashAEModeDisabler,
+                aeFpsRange,
+            ).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
+            fakeUseCaseThreads,
+        ).also {
+            it.useCaseCamera = fakeUseCaseCamera
+            it.setTorchAsync(torch = true, ignoreFlashUnitAvailability = true)
+        }
+
+        // LiveData is updated synchronously. Don't need to wait for the result of the setTorchAsync
+        Truth.assertThat(torchControl.torchStateLiveData.value).isEqualTo(TorchState.ON)
+    }
+
+    @Test
     fun disableTorch_TorchStateOff() {
         torchControl.setTorchAsync(true)
         // LiveData is updated synchronously. Don't need to wait for the result of the setTorchAsync
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 50cde53..ebb8d55 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
@@ -22,6 +22,7 @@
 import androidx.camera.camera2.pipe.GraphState
 import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
@@ -72,6 +73,14 @@
         setSurfaceResults[stream] = surface
     }
 
+    override fun getAudioRestriction(): AudioRestrictionMode? {
+        throw NotImplementedError("Not used in testing")
+    }
+
+    override fun setAudioRestriction(mode: AudioRestrictionMode) {
+        throw NotImplementedError("Not used in testing")
+    }
+
     override fun start() {
         throw NotImplementedError("Not used in testing")
     }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
index 4691ba1..427d14d 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
@@ -121,7 +121,7 @@
         val state3AControl = State3AControl(
             cameraProperties,
             NoOpAutoFlashAEModeDisabler,
-            AeFpsRange(fakeCameraQuirks)
+            AeFpsRange(fakeCameraQuirks),
         ).apply {
             useCaseCamera = fakeUseCaseCamera
         }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeState3AControlCreator.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeState3AControlCreator.kt
index 3b30f5d..055e7f4 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeState3AControlCreator.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeState3AControlCreator.kt
@@ -46,7 +46,7 @@
                     )
                 )
             )
-        )
+        ),
     ).apply {
         this.useCaseCamera = useCaseCamera
     }
diff --git a/camera/camera-camera2-pipe-testing/OWNERS b/camera/camera-camera2-pipe-testing/OWNERS
index c445688..9a1b187 100644
--- a/camera/camera-camera2-pipe-testing/OWNERS
+++ b/camera/camera-camera2-pipe-testing/OWNERS
@@ -2,3 +2,5 @@
 codelogic@google.com
 sushilnath@google.com
 lnishan@google.com
+fungja@google.com
+trevormcguire@google.com
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/OWNERS b/camera/camera-camera2-pipe/OWNERS
index c445688..9a1b187 100644
--- a/camera/camera-camera2-pipe/OWNERS
+++ b/camera/camera-camera2-pipe/OWNERS
@@ -2,3 +2,5 @@
 codelogic@google.com
 sushilnath@google.com
 lnishan@google.com
+fungja@google.com
+trevormcguire@google.com
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
index 997ad8f..9ab0906 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraBackend.kt
@@ -35,9 +35,13 @@
     val cameraStatus: Flow<CameraStatus>
 
     abstract class CameraStatus internal constructor() {
-        object CameraPrioritiesChanged : CameraStatus()
+        object CameraPrioritiesChanged : CameraStatus() {
+            override fun toString(): String = "CameraPrioritiesChanged"
+        }
 
-        class CameraAvailable(val cameraId: CameraId) : CameraStatus()
+        class CameraAvailable(val cameraId: CameraId) : CameraStatus() {
+            override fun toString(): String = "CameraAvailable(camera=$cameraId"
+        }
     }
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
index 76c367db..27550b3 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraDevices.kt
@@ -179,7 +179,7 @@
      * @return The parsed Camera1 id, or null if the value cannot be parsed as a Camera1 id.
      */
     inline fun toCamera1Id(): Int? = value.toIntOrNull()
-    override fun toString(): String = "Camera $value"
+    override fun toString(): String = "CameraId-$value"
 }
 
 /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraError.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraError.kt
index 43f5736..58c19f7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraError.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraError.kt
@@ -186,6 +186,8 @@
             return topMethodName == "_enableShutterSound"
         }
     }
+
+    override fun toString(): String = "CameraError($value)"
 }
 
 // TODO(b/276918807): When we have CameraProperties, handle the exception on a more granular level.
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 4f42a45..55aa136 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
@@ -35,6 +35,7 @@
 import androidx.camera.camera2.pipe.GraphState.GraphStateStarting
 import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
 import androidx.camera.camera2.pipe.GraphState.GraphStateStopping
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode
 import androidx.camera.camera2.pipe.core.Log
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
@@ -154,6 +155,21 @@
     fun setSurface(stream: StreamId, surface: Surface?)
 
     /**
+     * CameraPipe allows setting the global audio restriction through [CameraPipe] and audio
+     * restrictions on individual [CameraGraph]s. When multiple settings are present, the highest
+     * level of audio restriction across global and individual [CameraGraph]s is used as the
+     * device's audio restriction
+     *
+     * Returns the mode of audio restriction associated with the [CameraGraph].
+     */
+    fun getAudioRestriction(): AudioRestrictionMode?
+
+    /**
+     * Sets the audio restriction of CameraGraph.
+     */
+    fun setAudioRestriction(mode: AudioRestrictionMode)
+
+    /**
      * This defines the configuration, flags, and pre-defined structure of a [CameraGraph] instance.
      * Note that for parameters, null is considered a valid value, and unset keys are ignored.
      *
@@ -659,6 +675,6 @@
     class GraphStateError(val cameraError: CameraError, val willAttemptRetry: Boolean) :
         GraphState() {
         override fun toString(): String =
-            super.toString() + "(cameraError = $cameraError, willAttemptRetry = $willAttemptRetry)"
+            super.toString() + "(cameraError=$cameraError, willAttemptRetry=$willAttemptRetry)"
     }
 }
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 51dc922..cef1f7c 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
@@ -123,13 +123,16 @@
             surfaceToken = SurfaceToken(surface)
             val newUseCount = (useCountMap[surface] ?: 0) + 1
             useCountMap[surface] = newUseCount
-            Log.debug {
-                "registerSurface: surface=$surface, " +
-                    "surfaceToken=$surfaceToken, newUseCount=$newUseCount" +
-                    (if (DEBUG) " from ${Log.readStackTrace()}" else "")
+            if (DEBUG) {
+                Log.debug {
+                    "registerSurface: surface=$surface, " +
+                        "surfaceToken=$surfaceToken, newUseCount=$newUseCount" +
+                        (if (DEBUG) " from ${Log.readStackTrace()}" else "")
+                }
             }
+
             if (newUseCount == 1) {
-                Log.debug { "Surface $surface has become active" }
+                Log.debug { "$surface for $surfaceToken is active" }
                 listenersToInvoke = listeners.toList()
             }
         }
@@ -148,13 +151,16 @@
             checkNotNull(useCount) { "Surface $surface ($surfaceToken) has no use count" }
             val newUseCount = useCount - 1
             useCountMap[surface] = newUseCount
-            Log.debug {
-                "onTokenClosed: surface=$surface, " +
-                    "surfaceToken=$surfaceToken, newUseCount=$newUseCount" +
-                    (if (DEBUG) " from ${Log.readStackTrace()}" else "")
+
+            if (DEBUG) {
+                Log.debug {
+                    "onTokenClosed: surface=$surface, " +
+                        "surfaceToken=$surfaceToken, newUseCount=$newUseCount" +
+                        (if (DEBUG) " from ${Log.readStackTrace()}" else "")
+                }
             }
             if (newUseCount == 0) {
-                Log.debug { "Surface $surface has become inactive" }
+                Log.debug { "$surface for $surfaceToken is inactive" }
                 listenersToInvoke = listeners.toList()
                 useCountMap.remove(surface)
             }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
index dd42328..7e64fd1 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
@@ -52,7 +52,7 @@
         }
 
         override fun toString(): String {
-            return name
+            return "Metadata.Key($name)"
         }
     }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
index 76585d3..6e00d10 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
@@ -243,14 +243,17 @@
 
     override fun toString(): String {
         val parametersString =
-            if (parameters.isEmpty()) "" else ", parameters=${Debug.formatParameterMap(parameters)}"
+            if (parameters.isEmpty()) {
+                ""
+            } else {
+                ", parameters=${Debug.formatParameterMap(parameters, limit = 5)}"
+            }
         val extrasString =
-            if (extras.isEmpty()) "" else ", extras=${Debug.formatParameterMap(extras)}"
+            if (extras.isEmpty()) "" else ", extras=${Debug.formatParameterMap(extras, limit = 5)}"
         val templateString = if (template == null) "" else ", template=$template"
         // Ignore listener count, always include stream list (required), and use super.toString to
         // reference the class name.
-        return "Request@${super.hashCode().toString(16)}(streams=$streams" +
-            "$parametersString$extrasString$templateString)"
+        return "Request(streams=$streams$templateString$parametersString$extrasString)"
     }
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt
index 5da8a72..d009ee5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/StreamFormat.kt
@@ -134,6 +134,6 @@
                 YUY2 -> return "YUY2"
                 YV12 -> return "YV12"
             }
-            return "UNKNOWN-${this.value.toString(16)}"
+            return "UNKNOWN(${this.value.toString(16)})"
         }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AudioRestrictionController.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AudioRestrictionController.kt
new file mode 100644
index 0000000..481baa9
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/AudioRestrictionController.kt
@@ -0,0 +1,109 @@
+/*
+* Copyright 2024 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package androidx.camera.camera2.pipe.compat
+
+import android.hardware.camera2.CameraDevice
+import androidx.annotation.GuardedBy
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode.Companion.AUDIO_RESTRICTION_NONE
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode.Companion.AUDIO_RESTRICTION_VIBRATION
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode.Companion.AUDIO_RESTRICTION_VIBRATION_SOUND
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Class that keeps the global audio restriction mode and audio restriction mode on each
+ * CameraGraph, and computes the final audio restriction mode based on the settings.
+ */
+@Singleton
+@RequiresApi(30)
+class AudioRestrictionController @Inject constructor() {
+    private val lock = Any()
+    var globalAudioRestrictionMode: AudioRestrictionMode = AUDIO_RESTRICTION_NONE
+        get() = synchronized(lock) { field }
+        set(value: AudioRestrictionMode) {
+            synchronized(lock) {
+                field = value
+                updateListenersMode()
+            }
+        }
+
+    private val audioRestrictionModeMap: MutableMap<CameraGraph, AudioRestrictionMode> =
+        mutableMapOf()
+    private val activeListeners: MutableSet<Listener> = mutableSetOf()
+
+    fun getCameraGraphAudioRestriction(cameraGraph: CameraGraph): AudioRestrictionMode {
+        return audioRestrictionModeMap.getOrDefault(cameraGraph, AUDIO_RESTRICTION_NONE)
+    }
+
+    fun setCameraGraphAudioRestriction(cameraGraph: CameraGraph, mode: AudioRestrictionMode) {
+        synchronized(lock) {
+            audioRestrictionModeMap[cameraGraph] = mode
+            updateListenersMode()
+        }
+    }
+
+    fun removeCameraGraph(cameraGraph: CameraGraph) {
+        synchronized(lock) {
+            audioRestrictionModeMap.remove(cameraGraph)
+            updateListenersMode()
+        }
+    }
+
+    @GuardedBy("lock")
+    private fun computeAudioRestrictionMode(): AudioRestrictionMode {
+        if (audioRestrictionModeMap.containsValue(AUDIO_RESTRICTION_VIBRATION_SOUND) ||
+            globalAudioRestrictionMode == AUDIO_RESTRICTION_VIBRATION_SOUND
+        ) {
+            return AUDIO_RESTRICTION_VIBRATION_SOUND
+        }
+        if (audioRestrictionModeMap.containsValue(AUDIO_RESTRICTION_VIBRATION) ||
+            globalAudioRestrictionMode == AUDIO_RESTRICTION_VIBRATION
+        ) {
+            return AUDIO_RESTRICTION_VIBRATION
+        }
+        return AUDIO_RESTRICTION_NONE
+    }
+
+    fun addListener(listener: Listener) {
+        synchronized(lock) {
+            activeListeners.add(listener)
+            val mode = computeAudioRestrictionMode()
+            listener.onCameraAudioRestrictionUpdated(mode)
+        }
+    }
+
+    fun removeListener(listener: Listener?) {
+        synchronized(lock) {
+            activeListeners.remove(listener)
+        }
+    }
+
+    @GuardedBy("lock")
+    private fun updateListenersMode() {
+        val mode = computeAudioRestrictionMode()
+        for (listener in activeListeners) {
+            listener.onCameraAudioRestrictionUpdated(mode)
+        }
+    }
+
+    interface Listener {
+        /** @see CameraDevice.getCameraAudioRestriction */
+        fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode)
+    }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
index 74851e8..815114b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
@@ -176,12 +176,12 @@
     private val _keys: Lazy<Set<CameraCharacteristics.Key<*>>> =
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
-                Debug.trace("Camera-${camera.value}#keys") {
+                Debug.trace("$camera#keys") {
                     characteristics.keys.orEmpty().toSet()
                 }
             } catch (e: AssertionError) {
                 Log.warn(e) {
-                    "Failed to getKeys from Camera-${camera.value}"
+                    "Failed to getKeys from $camera}"
                 }
                 emptySet()
             }
@@ -190,12 +190,12 @@
     private val _requestKeys: Lazy<Set<CaptureRequest.Key<*>>> =
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
-                Debug.trace("Camera-${camera.value}#availableCaptureRequestKeys") {
+                Debug.trace("$camera#availableCaptureRequestKeys") {
                     characteristics.availableCaptureRequestKeys.orEmpty().toSet()
                 }
             } catch (e: AssertionError) {
                 Log.warn(e) {
-                    "Failed to getAvailableCaptureRequestKeys from Camera-${camera.value}"
+                    "Failed to getAvailableCaptureRequestKeys from $camera"
                 }
                 emptySet()
             }
@@ -204,12 +204,12 @@
     private val _resultKeys: Lazy<Set<CaptureResult.Key<*>>> =
         lazy(LazyThreadSafetyMode.PUBLICATION) {
             try {
-                Debug.trace("Camera-${camera.value}#availableCaptureResultKeys") {
+                Debug.trace("$camera#availableCaptureResultKeys") {
                     characteristics.availableCaptureResultKeys.orEmpty().toSet()
                 }
             } catch (e: AssertionError) {
                 Log.warn(e) {
-                    "Failed to getAvailableCaptureResultKeys from Camera-${camera.value}"
+                    "Failed to getAvailableCaptureResultKeys from $camera"
                 }
                 emptySet()
             }
@@ -221,7 +221,7 @@
                 emptySet()
             } else {
                 try {
-                    Debug.trace("Camera-${camera.value}#physicalCameraIds") {
+                    Debug.trace("$camera#physicalCameraIds") {
                         val ids = Api28Compat.getPhysicalCameraIds(characteristics)
                         Log.info { "Loaded physicalCameraIds from $camera: $ids" }
 
@@ -231,10 +231,10 @@
                             .toSet()
                     }
                 } catch (e: AssertionError) {
-                    Log.warn(e) { "Failed to getPhysicalCameraIds from Camera-${camera.value}" }
+                    Log.warn(e) { "Failed to getPhysicalCameraIds from $camera" }
                     emptySet()
                 } catch (e: NullPointerException) {
-                    Log.warn(e) { "Failed to getPhysicalCameraIds from Camera-${camera.value}" }
+                    Log.warn(e) { "Failed to getPhysicalCameraIds from $camera" }
                     emptySet()
                 }
             }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
index d2d8495..cf2716c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CaptureSequenceProcessor.kt
@@ -310,6 +310,9 @@
     }
 
     override fun close() = synchronized(lock) {
+        if (closed) {
+            return@synchronized
+        }
         // Close should not shut down
         Debug.trace("$this#close") {
             if (shouldWaitForRepeatingRequest) {
@@ -329,10 +332,11 @@
             }
             closed = true
         }
+        imageWriter?.close()
     }
 
     override fun toString(): String {
-        return "Camera2RequestProcessor-$debugId"
+        return "Camera2CaptureSequenceProcessor-$debugId"
     }
 
     /** The [ImageWriterWrapper] is created once per capture session when the capture
@@ -460,15 +464,13 @@
 
                 val surface = surfaceMap[stream]
                 if (surface != null) {
-                    Log.debug { "  Binding $stream to $surface" }
-
                     // TODO(codelogic) There should be a more efficient way to do these lookups than
                     // having two maps.
                     surfaceToStreamMap[surface] = stream
                     streamToSurfaceMap[stream] = surface
                     hasSurface = true
                 } else if (REQUIRE_SURFACE_FOR_ALL_STREAMS) {
-                    Log.info { "  Failed to bind surface to $stream" }
+                    Log.info { "  Failed to bind surface for $stream" }
 
                     // If requireStreams is set we are required to map every stream to a valid
                     // Surface object for this request. If this condition is violated, then we
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 abf3a2f..8b5fc4b 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
@@ -19,6 +19,7 @@
 import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCaptureSession
 import android.hardware.camera2.CameraDevice
+import android.os.Build
 import android.view.Surface
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraId
@@ -37,6 +38,7 @@
         cameraDevice: CameraDevice? = null,
         closeUnderError: Boolean = false,
         androidCameraState: AndroidCameraState,
+        audioRestriction: AudioRestrictionController?
     )
 }
 
@@ -51,24 +53,42 @@
         cameraDevice: CameraDevice?,
         closeUnderError: Boolean,
         androidCameraState: AndroidCameraState,
+        audioRestriction: AudioRestrictionController?
     ) {
-        Log.debug { "Closing $cameraDeviceWrapper and/or $cameraDevice" }
         val unwrappedCameraDevice = cameraDeviceWrapper?.unwrapAs(CameraDevice::class)
         if (unwrappedCameraDevice != null) {
             cameraDevice?.let {
                 check(unwrappedCameraDevice.id == it.id) {
-                    "Unwrapped camera device has camera ID ${unwrappedCameraDevice.id}, " + "" +
-                        "but the accompanied camera device has camera ID ${it.id}"
+                    "Unwrapped camera device has camera ID ${unwrappedCameraDevice.id}, " +
+                        "but the wrapped camera device has camera ID ${it.id}!"
                 }
             }
-            closeCameraDevice(unwrappedCameraDevice, closeUnderError, androidCameraState)
+            closeCameraDevice(
+                unwrappedCameraDevice,
+                closeUnderError,
+                androidCameraState
+            )
             cameraDeviceWrapper.onDeviceClosed()
+            /**
+             * Only remove the audio restriction when CameraDeviceWrapper is present.
+             * When closeCamera is called without a CameraDeviceWrapper, that means a wrapper
+             * hadn't been created for the opened camera.
+             */
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                audioRestriction?.removeListener(cameraDeviceWrapper)
+            }
 
             // We only need to close the device once (don't want to create another capture session).
             // Return here.
             return
         }
-        cameraDevice?.let { closeCameraDevice(it, closeUnderError, androidCameraState) }
+        cameraDevice?.let {
+            closeCameraDevice(
+                it,
+                closeUnderError,
+                androidCameraState
+            )
+        }
     }
 
     private fun closeCameraDevice(
@@ -84,16 +104,15 @@
                 Log.debug { "Empty capture session quirk completed" }
             }
         }
-        Log.debug { "Closing $cameraDevice" }
         Threading.runBlockingWithTimeout(threads.backgroundDispatcher, 5000L) {
             cameraDevice.closeWithTrace()
         }
         if (camera2Quirks.shouldWaitForCameraDeviceOnClosed(cameraId)) {
-            Log.debug { "Waiting for camera device to be completely closed" }
+            Log.debug { "Waiting for OnClosed from $cameraId" }
             if (androidCameraState.awaitCameraDeviceClosed(timeoutMillis = 5000)) {
-                Log.debug { "Camera device is closed" }
+                Log.debug { "Received OnClosed for $cameraId" }
             } else {
-                Log.warn { "Failed to wait for camera device to close after 5000ms" }
+                Log.warn { "Failed to close $cameraId after 500ms" }
             }
         }
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index ce3d323..42577fc 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -100,7 +100,7 @@
     }
 
     override fun awaitCameraMetadata(cameraId: CameraId): CameraMetadata {
-        return Debug.trace("Camera-${cameraId.value}#awaitMetadata") {
+        return Debug.trace("$cameraId#awaitMetadata") {
             synchronized(cache) {
                 val existing = cache[cameraId.value]
                 if (existing != null) {
@@ -120,7 +120,7 @@
         extension: Int
     ): CameraExtensionMetadata {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            return Debug.trace("Camera-${cameraId.value}#awaitExtensionMetadata") {
+            return Debug.trace("$cameraId#awaitExtensionMetadata") {
                 synchronized(extensionCache) {
                     val existing = extensionCache[cameraId.value]
                     if (existing != null) {
@@ -147,7 +147,7 @@
     ): Camera2CameraMetadata {
         val start = Timestamps.now(timeSource)
 
-        return Debug.trace("Camera-${cameraId.value}#readCameraMetadata") {
+        return Debug.trace("$cameraId#readCameraMetadata") {
             try {
                 Log.debug { "Loading metadata for $cameraId" }
                 val cameraManager =
@@ -217,7 +217,7 @@
     ): Camera2CameraExtensionMetadata {
         val start = Timestamps.now(timeSource)
 
-        return Debug.trace("Camera-${cameraId.value}#readCameraExtensionMetadata") {
+        return Debug.trace("$cameraId#readCameraExtensionMetadata") {
             try {
                 Log.debug { "Loading extension metadata for $cameraId" }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt
index fd97a9b..c7460ba 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CameraDeviceWrapper.kt
@@ -50,7 +50,7 @@
  * This interface has been modified to correct nullness, adjust exceptions, and to return or produce
  * wrapper interfaces instead of the native Camera2 types.
  */
-internal interface CameraDeviceWrapper : UnsafeWrapper {
+internal interface CameraDeviceWrapper : UnsafeWrapper, AudioRestrictionController.Listener {
     /** @see [CameraDevice.getId] */
     val cameraId: CameraId
 
@@ -110,11 +110,7 @@
 
     /** @see CameraDevice.getCameraAudioRestriction */
     @RequiresApi(Build.VERSION_CODES.R)
-    fun getCameraAudioRestriction(): AudioRestrictionMode
-
-    /** @see CameraDevice.setCameraAudioRestriction */
-    @RequiresApi(Build.VERSION_CODES.R)
-    fun setCameraAudioRestriction(mode: AudioRestrictionMode)
+    fun getCameraAudioRestriction(): AudioRestrictionMode?
 }
 
 internal fun CameraDevice?.closeWithTrace() {
@@ -466,8 +462,10 @@
     }
 
     @RequiresApi(Build.VERSION_CODES.R)
-    override fun setCameraAudioRestriction(mode: AudioRestrictionMode) {
-        Api30Compat.setCameraAudioRestriction(cameraDevice, mode.value)
+    override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
+        catchAndReportCameraExceptions(cameraId, cameraErrorListener) {
+            Api30Compat.setCameraAudioRestriction(cameraDevice, mode.value)
+        }
     }
 
     override fun onDeviceClosed() {
@@ -481,6 +479,8 @@
             CameraDevice::class -> cameraDevice as T
             else -> null
         }
+
+    override fun toString(): String = "AndroidCameraDevice(camera=$cameraId)"
 }
 
 /**
@@ -647,11 +647,14 @@
     }
 
     @RequiresApi(30)
-    override fun setCameraAudioRestriction(mode: AudioRestrictionMode) {
-        androidCameraDevice.setCameraAudioRestriction(mode)
+    override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
+        androidCameraDevice.onCameraAudioRestrictionUpdated(mode)
     }
 }
 
+/**
+ * @see [CameraDevice.AUDIO_RESTRICTION_NONE] and other constants.
+ */
 @JvmInline
 value class AudioRestrictionMode(val value: Int) {
     companion object {
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 4a8dfcd..bc9bbcf 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
@@ -82,7 +82,7 @@
     )
     override fun openCamera(cameraId: CameraId, stateCallback: StateCallback) {
         val instance = cameraManager.get()
-        Debug.trace("CameraDevice-${cameraId.value}#openCamera") {
+        Debug.trace("$cameraId#openCamera") {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                 Api28Compat.openCamera(
                     instance, cameraId.value, threads.camera2Executor, stateCallback
@@ -173,6 +173,7 @@
         cameraId: CameraId,
         attempts: Int,
         requestTimestamp: TimestampNs,
+        audioRestriction: AudioRestrictionController? = null
     ): OpenCameraResult {
         val metadata = camera2MetadataProvider.getCameraMetadata(cameraId)
         val cameraState =
@@ -186,7 +187,10 @@
                 camera2DeviceCloser,
                 threads,
                 cameraInteropConfig?.cameraDeviceStateCallback,
-                cameraInteropConfig?.cameraSessionStateCallback
+                cameraInteropConfig?.cameraSessionStateCallback,
+                /** interopExtensionSessionStateCallback= */
+                null,
+                audioRestriction
             )
 
         try {
@@ -229,6 +233,7 @@
     private val timeSource: TimeSource,
     private val devicePolicyManager: DevicePolicyManagerWrapper,
     private val cameraInteropConfig: CameraPipe.CameraInteropConfig?,
+    private val audioRestriction: AudioRestrictionController? = null
 ) {
     internal suspend fun openCameraWithRetry(
         cameraId: CameraId,
@@ -245,6 +250,7 @@
                     cameraId,
                     attempts,
                     requestTimestamp,
+                    audioRestriction
                 )
             val elapsed = Timestamps.now(timeSource) - requestTimestamp
             with(result) {
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 eb3baea..3df1e09 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
@@ -22,6 +22,7 @@
 import android.hardware.camera2.CameraCaptureSession.StateCallback
 import android.hardware.camera2.CameraDevice
 import android.hardware.camera2.CameraExtensionSession
+import android.os.Build
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraError
@@ -252,7 +253,8 @@
     private val threads: Threads,
     private val interopDeviceStateCallback: CameraDevice.StateCallback? = null,
     private val interopSessionStateCallback: StateCallback? = null,
-    private val interopExtensionSessionStateCallback: CameraExtensionSession.StateCallback? = null
+    private val interopExtensionSessionStateCallback: CameraExtensionSession.StateCallback? = null,
+    private val audioRestriction: AudioRestrictionController? = null
 ) : CameraDevice.StateCallback() {
     private val debugId = androidCameraDebugIds.incrementAndGet()
     private val lock = Any()
@@ -309,7 +311,7 @@
         val openedTimestamp = Timestamps.now(timeSource)
         openTimestampNanos = openedTimestamp
 
-        Debug.traceStart { "Camera-${cameraId.value}#onOpened" }
+        Debug.traceStart { "$cameraId#onOpened" }
         Log.info {
             val attemptDuration = openedTimestamp - requestTimestampNanos
             val totalDuration = openedTimestamp - attemptTimestampNanos
@@ -334,25 +336,28 @@
             camera2DeviceCloser.closeCamera(
                 cameraDevice = cameraDevice,
                 closeUnderError = currentCloseInfo.errorCode != null,
-                androidCameraState = this
+                androidCameraState = this,
+                audioRestriction = audioRestriction
             )
             return
         }
 
         // Update _state.value _without_ holding the lock. This may block the calling thread for a
         // while if it synchronously calls createCaptureSession.
+        val androidCameraDevice = AndroidCameraDevice(
+            metadata,
+            cameraDevice,
+            cameraId,
+            cameraErrorListener,
+            interopSessionStateCallback,
+            interopExtensionSessionStateCallback,
+            threads
+        )
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            audioRestriction?.addListener(androidCameraDevice)
+        }
         _state.value =
-            CameraStateOpen(
-                AndroidCameraDevice(
-                    metadata,
-                    cameraDevice,
-                    cameraId,
-                    cameraErrorListener,
-                    interopSessionStateCallback,
-                    interopExtensionSessionStateCallback,
-                    threads
-                )
-            )
+            CameraStateOpen(androidCameraDevice)
 
         // Check to see if we received close() or other events in the meantime.
         val closeInfo =
@@ -365,7 +370,8 @@
             camera2DeviceCloser.closeCamera(
                 cameraDevice = cameraDevice,
                 closeUnderError = closeInfo.errorCode != null,
-                androidCameraState = this
+                androidCameraState = this,
+                audioRestriction = audioRestriction
             )
             _state.value = computeClosedState(closeInfo)
         }
@@ -374,7 +380,7 @@
 
     override fun onDisconnected(cameraDevice: CameraDevice) {
         check(cameraDevice.id == cameraId.value)
-        Debug.traceStart { "Camera-${cameraId.value}#onDisconnected" }
+        Debug.traceStart { "$cameraId#onDisconnected" }
         Log.debug { "$cameraId: onDisconnected" }
         cameraDeviceClosed.countDown()
 
@@ -391,7 +397,7 @@
 
     override fun onError(cameraDevice: CameraDevice, errorCode: Int) {
         check(cameraDevice.id == cameraId.value)
-        Debug.traceStart { "Camera-${cameraId.value}#onError-$errorCode" }
+        Debug.traceStart { "$cameraId#onError-$errorCode" }
         Log.debug { "$cameraId: onError $errorCode" }
         cameraDeviceClosed.countDown()
 
@@ -405,7 +411,7 @@
 
     override fun onClosed(cameraDevice: CameraDevice) {
         check(cameraDevice.id == cameraId.value)
-        Debug.traceStart { "Camera-${cameraId.value}#onClosed" }
+        Debug.traceStart { "$cameraId#onClosed" }
         Log.debug { "$cameraId: onClosed" }
         cameraDeviceClosed.countDown()
 
@@ -471,6 +477,7 @@
                 cameraDevice,
                 closeUnderError = closeInfo.errorCode != null,
                 androidCameraState = this,
+                audioRestriction = audioRestriction
             )
             _state.value = computeClosedState(closeInfo)
         }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
index 7b7ffb4..967eb19 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
@@ -30,6 +30,7 @@
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.core.Threads
+import androidx.camera.camera2.pipe.graph.CameraGraphId
 import androidx.camera.camera2.pipe.graph.CameraGraphImpl
 import androidx.camera.camera2.pipe.graph.GraphListener
 import androidx.camera.camera2.pipe.graph.GraphProcessor
@@ -104,6 +105,12 @@
     companion object {
         @CameraGraphScope
         @Provides
+        fun provideCameraGraphId(): CameraGraphId {
+            return CameraGraphId.nextId()
+        }
+
+        @CameraGraphScope
+        @Provides
         @ForCameraGraph
         fun provideCameraGraphCoroutineScope(threads: Threads): CoroutineScope {
             return CoroutineScope(threads.lightweightDispatcher.plus(CoroutineName("CXCP-Graph")))
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
index bf120f1..2548cff 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Debug.kt
@@ -25,6 +25,7 @@
 import android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
+import android.hardware.camera2.params.MeteringRectangle
 import android.os.Build
 import android.os.Trace
 import androidx.annotation.RequiresApi
@@ -73,17 +74,7 @@
                 append("$name: (None)\n")
             } else {
                 append("${name}\n")
-                val parametersString: List<Pair<String, Any?>> =
-                    parameters.map {
-                        when (val key = it.key) {
-                            is CameraCharacteristics.Key<*> -> key.name
-                            is CaptureRequest.Key<*> -> key.name
-                            is CaptureResult.Key<*> -> key.name
-                            else -> key.toString()
-                        } to it.value
-                    }
-                parametersString
-                    .sortedBy { it.first }
+                parametersToSortedStringPairs(parameters)
                     .forEach { append("  ${it.first.padEnd(50, ' ')} ${it.second}\n") }
             }
         }
@@ -94,23 +85,36 @@
      *
      * Example: `[abc.xyz=1, abc.zyx=something]`
      */
-    fun formatParameterMap(parameters: Map<*, Any?>): String {
-        return parameters.map {
-            when (val key = it.key) {
-                is CameraCharacteristics.Key<*> -> key.name
-                is CaptureRequest.Key<*> -> key.name
-                is CaptureResult.Key<*> -> key.name
-                else -> key.toString()
-            } to it.value
-        }
-            .sortedBy { it.first }
+    fun formatParameterMap(parameters: Map<*, Any?>, limit: Int = -1): String {
+        return parametersToSortedStringPairs(parameters)
             .joinToString(
-                separator = ", ",
                 prefix = "{",
-                postfix = "}"
+                postfix = "}",
+                limit = limit
             ) { "${it.first}=${it.second}" }
     }
 
+    private fun parametersToSortedStringPairs(
+        parameters: Map<*, Any?>
+    ): List<Pair<String, String>> = parameters.map {
+        keyNameToString(it.key) to valueToString(it.value)
+    }.sortedBy { it.first }
+
+    private fun keyNameToString(key: Any?): String = when (key) {
+        is CameraCharacteristics.Key<*> -> key.name
+        is CaptureRequest.Key<*> -> key.name
+        is CaptureResult.Key<*> -> key.name
+        else -> key.toString()
+    }
+
+    /* Utility for cleaning up some verbose value types for logs */
+    private fun valueToString(value: Any?): String = when (value) {
+        is MeteringRectangle -> "[x=${value.x}, y=${value.y}, " +
+            "w=${value.width}, h=${value.height}, weight=${value.meteringWeight}"
+
+        else -> value.toString()
+    }
+
     fun formatCameraGraphProperties(
         metadata: CameraMetadata,
         graphConfig: CameraGraph.Config,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphId.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphId.kt
new file mode 100644
index 0000000..80040f7
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/CameraGraphId.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.graph
+
+import androidx.camera.camera2.pipe.CameraGraph
+import kotlinx.atomicfu.atomic
+
+/**
+ * Identifier for a specific [CameraGraph] that can be used to standardize toString methods and as a
+ * key in maps without holding a reference to a [CameraGraph] object, which could lead to accidental
+ * memory leaks and circular dependencies.
+ */
+internal class CameraGraphId private constructor(private val name: String) {
+    override fun toString(): String = name
+
+    companion object {
+        private val cameraGraphIds = atomic(0)
+
+        /**
+         * Create the next CameraGraphId based on a global incrementing counter. This is
+         * intentionally worded as "CameraGraph" instead of "CameraGraphId" since it is used
+         * directly as the toString representation for a [CameraGraph].
+         */
+        fun nextId(): CameraGraphId {
+            return CameraGraphId("CameraGraph-${cameraGraphIds.incrementAndGet()}")
+        }
+    }
+}
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 b9f4539..d6b45bc 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
@@ -26,6 +26,8 @@
 import androidx.camera.camera2.pipe.GraphState
 import androidx.camera.camera2.pipe.StreamGraph
 import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.compat.AudioRestrictionController
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode
 import androidx.camera.camera2.pipe.config.CameraGraphScope
 import androidx.camera.camera2.pipe.core.Debug
 import androidx.camera.camera2.pipe.core.Log
@@ -47,8 +49,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.sync.Mutex
 
-internal val cameraGraphIds = atomic(0)
-
 @RequiresApi(21)
 @CameraGraphScope
 internal class CameraGraphImpl
@@ -56,6 +56,7 @@
 constructor(
     graphConfig: CameraGraph.Config,
     metadata: CameraMetadata,
+    private val cameraGraphId: CameraGraphId,
     private val graphLifecycleManager: GraphLifecycleManager,
     private val graphProcessor: GraphProcessor,
     private val graphListener: GraphListener,
@@ -67,8 +68,8 @@
     private val listener3A: Listener3A,
     private val frameDistributor: FrameDistributor,
     private val frameCaptureQueue: FrameCaptureQueue,
+    private val audioRestriction: AudioRestrictionController? = null
 ) : CameraGraph {
-    private val debugId = cameraGraphIds.incrementAndGet()
     private val sessionMutex = Mutex()
     private val controller3A = Controller3A(graphProcessor, metadata, graphState3A, listener3A)
     private val closed = atomic(false)
@@ -206,6 +207,19 @@
         Debug.traceStop()
     }
 
+    override fun getAudioRestriction(): AudioRestrictionMode? {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            return audioRestriction?.getCameraGraphAudioRestriction(this)
+        }
+        return null
+    }
+
+    override fun setAudioRestriction(mode: AudioRestrictionMode) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            audioRestriction?.setCameraGraphAudioRestriction(this, mode)
+        }
+    }
+
     override fun close() {
         if (closed.compareAndSet(expect = false, update = true)) {
             Debug.traceStart { "$this#close" }
@@ -215,9 +229,12 @@
             frameDistributor.close()
             frameCaptureQueue.close()
             surfaceGraph.close()
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                audioRestriction?.removeCameraGraph(this)
+            }
             Debug.traceStop()
         }
     }
 
-    override fun toString(): String = "CameraGraph-$debugId"
+    override fun toString(): String = cameraGraphId.toString()
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
index fc67120..1524e89 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
@@ -131,6 +131,7 @@
 @Inject
 constructor(
     private val threads: Threads,
+    private val cameraGraphId: CameraGraphId,
     private val cameraGraphConfig: CameraGraph.Config,
     private val graphState3A: GraphState3A,
     @ForCameraGraph private val graphScope: CoroutineScope,
@@ -648,4 +649,6 @@
             }
         }
     }
+
+    override fun toString(): String = "GraphProcessor(cameraGraph: $cameraGraphId)"
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
index de2945f..ed55f4a 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/StreamGraphImpl.kt
@@ -280,7 +280,7 @@
     }
 
     override fun toString(): String {
-        return "StreamGraphImpl $_streamMap"
+        return "StreamGraph($_streamMap)"
     }
 
     /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt
index b4eefe3..36e09ab 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/media/AndroidImageWriter.kt
@@ -71,8 +71,7 @@
     }
 
     override fun toString(): String {
-        return "ImageWriter-${StreamFormat(imageWriter.format).name}-" +
-            "inputStreamId$inputStreamId"
+        return "ImageWriter-${StreamFormat(imageWriter.format).name}-$inputStreamId"
     }
 
     companion object {
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/RequestTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/RequestTest.kt
index 16faf80..e6b3c6e 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/RequestTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/RequestTest.kt
@@ -60,9 +60,7 @@
     @Test
     fun requestHasNiceLoggingString() {
         val request1 = Request(listOf(StreamId(1)))
-        val request2 = Request(listOf(StreamId(1)))
 
-        assertThat("$request1").isNotEqualTo("$request2")
         assertThat("$request1").contains("1")
         assertThat("$request1").contains("Request")
 
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/AudioRestrictionControllerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/AudioRestrictionControllerTest.kt
new file mode 100644
index 0000000..5421440
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/compat/AudioRestrictionControllerTest.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.compat
+
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode.Companion.AUDIO_RESTRICTION_VIBRATION
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode.Companion.AUDIO_RESTRICTION_VIBRATION_SOUND
+import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.R)
+class AudioRestrictionControllerTest {
+    private val cameraGraph1: CameraGraph = mock()
+    private val cameraGraph2: CameraGraph = mock()
+    private val listener1: AudioRestrictionController.Listener = mock()
+    private val listener2: AudioRestrictionController.Listener = mock()
+
+    @Test
+    fun setAudioRestrictionMode_ListenerUpdatedToHighestMode() {
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+        audioRestriction.addListener(listener2)
+
+        audioRestriction.setCameraGraphAudioRestriction(cameraGraph1, AUDIO_RESTRICTION_VIBRATION)
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(AUDIO_RESTRICTION_VIBRATION)
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(AUDIO_RESTRICTION_VIBRATION)
+
+        audioRestriction.setCameraGraphAudioRestriction(
+            cameraGraph2,
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+    }
+
+    @Test
+    fun setGlobalAudioRestrictionMode_ListenerUpdatedToHighestMode() {
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+        audioRestriction.addListener(listener2)
+
+        audioRestriction.setCameraGraphAudioRestriction(cameraGraph1, AUDIO_RESTRICTION_VIBRATION)
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(AUDIO_RESTRICTION_VIBRATION)
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(AUDIO_RESTRICTION_VIBRATION)
+
+        audioRestriction.globalAudioRestrictionMode = AUDIO_RESTRICTION_VIBRATION_SOUND
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+    }
+
+    @Test
+    fun setAudioRestrictionMode_lowerModeNotOverrideHigherMode() {
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+
+        audioRestriction.setCameraGraphAudioRestriction(
+            cameraGraph1,
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        audioRestriction.setCameraGraphAudioRestriction(cameraGraph2, AUDIO_RESTRICTION_VIBRATION)
+
+        // Whenever a setter method is called, an update should be called on the listener
+        verify(listener1, times(2)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        verify(listener1, never()).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION
+        )
+    }
+
+    @Test
+    fun setGlobalAudioRestrictionMode_lowerModeNotOverrideHigherMode() {
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+
+        audioRestriction.setCameraGraphAudioRestriction(
+            cameraGraph1,
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        audioRestriction.globalAudioRestrictionMode = AUDIO_RESTRICTION_VIBRATION
+
+        // Whenever a setter method is called, an update should be called on the listener
+        verify(listener1, times(2)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        verify(listener1, never()).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION
+        )
+    }
+
+    @Test
+    fun removeCameraGraphAudioRestriction_associatedModeUpdated() {
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+
+        audioRestriction.setCameraGraphAudioRestriction(
+            cameraGraph1,
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+        audioRestriction.setCameraGraphAudioRestriction(cameraGraph2, AUDIO_RESTRICTION_VIBRATION)
+
+        verify(listener1, times(2)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION_SOUND
+        )
+
+        audioRestriction.removeCameraGraph(cameraGraph1)
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(
+            AUDIO_RESTRICTION_VIBRATION
+        )
+    }
+
+    @Test
+    fun addListenerAfterUpdateMode_newListenerUpdated() {
+        val mode = AUDIO_RESTRICTION_VIBRATION
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+
+        audioRestriction.setCameraGraphAudioRestriction(cameraGraph1, mode)
+        audioRestriction.addListener(listener2)
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(mode)
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(mode)
+    }
+
+    @Test
+    fun setRestrictionBeforeAddingListener_listenerSetToUpdatedMode() {
+        val mode = AUDIO_RESTRICTION_VIBRATION
+        val audioRestriction = AudioRestrictionController()
+
+        audioRestriction.globalAudioRestrictionMode = mode
+        audioRestriction.addListener(listener1)
+        audioRestriction.addListener(listener2)
+
+        verify(listener1, times(1)).onCameraAudioRestrictionUpdated(mode)
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(mode)
+    }
+
+    @Test
+    fun removedListener_noLongerUpdated() {
+        val mode = AUDIO_RESTRICTION_VIBRATION
+        val audioRestriction = AudioRestrictionController()
+        audioRestriction.addListener(listener1)
+        audioRestriction.addListener(listener2)
+        audioRestriction.removeListener(listener1)
+
+        audioRestriction.setCameraGraphAudioRestriction(cameraGraph1, mode)
+
+        verify(listener1, times(0)).onCameraAudioRestrictionUpdated(mode)
+        verify(listener2, times(1)).onCameraAudioRestrictionUpdated(mode)
+    }
+}
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
index 24c59ce..5563e78 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/CameraGraphImplTest.kt
@@ -29,6 +29,8 @@
 import androidx.camera.camera2.pipe.CameraSurfaceManager
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.StreamFormat
+import androidx.camera.camera2.pipe.compat.AudioRestrictionController
+import androidx.camera.camera2.pipe.compat.AudioRestrictionMode
 import androidx.camera.camera2.pipe.internal.CameraBackendsImpl
 import androidx.camera.camera2.pipe.internal.FrameCaptureQueue
 import androidx.camera.camera2.pipe.internal.FrameDistributor
@@ -42,6 +44,7 @@
 import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceUntilIdle
@@ -113,10 +116,19 @@
             cameraSurfaceManager,
             emptyMap()
         )
+        val audioRestriction: AudioRestrictionController? =
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                AudioRestrictionController()
+            } else {
+                null
+            }
+
+        val cameraGraphId = CameraGraphId.nextId()
         val graph =
             CameraGraphImpl(
                 graphConfig,
                 metadata,
+                cameraGraphId,
                 graphLifecycleManager,
                 fakeGraphProcessor,
                 fakeGraphProcessor,
@@ -127,7 +139,8 @@
                 GraphState3A(),
                 Listener3A(),
                 frameDistributor,
-                frameCaptureQueue
+                frameCaptureQueue,
+                audioRestriction
             )
         stream1 =
             checkNotNull(graph.streams[stream1Config]) {
@@ -259,4 +272,13 @@
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
         verify(fakeSurfaceListener, times(1)).onSurfaceInactive(eq(imageReader1.surface))
     }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.R)
+    fun setAudioRestriction_setValueSuccessfully() = runTest {
+        val mode = AudioRestrictionMode(0)
+        val cameraGraph = initializeCameraGraphImpl(this)
+        cameraGraph.setAudioRestriction(mode)
+        assertEquals(mode, cameraGraph.getAudioRestriction())
+    }
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
index 21931e0..74f6f70 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
@@ -78,6 +78,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -99,6 +100,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -122,6 +124,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -149,6 +152,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -168,6 +172,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -199,6 +204,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -236,6 +242,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -260,6 +267,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -294,6 +302,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -321,6 +330,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -343,6 +353,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -378,6 +389,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -410,6 +422,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -436,6 +449,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -462,6 +476,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -501,6 +516,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -520,6 +536,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
@@ -539,6 +556,7 @@
         val graphProcessor =
             GraphProcessorImpl(
                 FakeThreads.fromTestScope(this),
+                CameraGraphId.nextId(),
                 FakeGraphConfigs.graphConfig,
                 graphState3A,
                 this,
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceCloser.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceCloser.kt
index 7b6cc46..6236a42 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceCloser.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCamera2DeviceCloser.kt
@@ -17,16 +17,21 @@
 package androidx.camera.camera2.pipe.testing
 
 import android.hardware.camera2.CameraDevice
+import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.compat.AndroidCameraState
+import androidx.camera.camera2.pipe.compat.AudioRestrictionController
 import androidx.camera.camera2.pipe.compat.Camera2DeviceCloser
 import androidx.camera.camera2.pipe.compat.CameraDeviceWrapper
 
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 internal class FakeCamera2DeviceCloser : Camera2DeviceCloser {
     override fun closeCamera(
         cameraDeviceWrapper: CameraDeviceWrapper?,
         cameraDevice: CameraDevice?,
         closeUnderError: Boolean,
         androidCameraState: AndroidCameraState,
+        audioRestriction: AudioRestrictionController?
     ) {
         cameraDeviceWrapper?.onDeviceClosed()
     }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
index ff8f9d0..38d1748 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/FakeCameraDeviceWrapper.kt
@@ -120,7 +120,7 @@
     }
 
     @RequiresApi(Build.VERSION_CODES.R)
-    override fun setCameraAudioRestriction(mode: AudioRestrictionMode) {
+    override fun onCameraAudioRestrictionUpdated(mode: AudioRestrictionMode) {
         fakeCamera.cameraDevice.cameraAudioRestriction = mode.value
     }
 
diff --git a/camera/camera-camera2/api/1.4.0-beta01.txt b/camera/camera-camera2/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..1f2bf8d
--- /dev/null
+++ b/camera/camera-camera2/api/1.4.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.camera.camera2 {
+
+  @RequiresApi(21) public final class Camera2Config {
+    method public static androidx.camera.core.CameraXConfig defaultConfig();
+  }
+
+}
+
+package androidx.camera.camera2.interop {
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraControl {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> addCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> clearCaptureRequestOptions();
+    method public static androidx.camera.camera2.interop.Camera2CameraControl from(androidx.camera.core.CameraControl);
+    method public androidx.camera.camera2.interop.CaptureRequestOptions getCaptureRequestOptions();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraInfo {
+    method public static androidx.camera.camera2.interop.Camera2CameraInfo from(androidx.camera.core.CameraInfo);
+    method public <T> T? getCameraCharacteristic(android.hardware.camera2.CameraCharacteristics.Key<T!>);
+    method public String getCameraId();
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2Interop {
+  }
+
+  @RequiresApi(21) public static final class Camera2Interop.Extender<T> {
+    ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
+    method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
+    method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
+    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
+    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
+    method @RequiresApi(33) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setStreamUseCase(long);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public class CaptureRequestOptions {
+    method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+  }
+
+  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions!> {
+    ctor public CaptureRequestOptions.Builder();
+    method public androidx.camera.camera2.interop.CaptureRequestOptions build();
+    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCamera2Interop {
+  }
+
+}
+
diff --git a/camera/camera-camera2/api/current.txt b/camera/camera-camera2/api/current.txt
index 87c79d0..1f2bf8d 100644
--- a/camera/camera-camera2/api/current.txt
+++ b/camera/camera-camera2/api/current.txt
@@ -40,7 +40,7 @@
     method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
   }
 
-  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions> {
+  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions!> {
     ctor public CaptureRequestOptions.Builder();
     method public androidx.camera.camera2.interop.CaptureRequestOptions build();
     method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
diff --git a/camera/camera-camera2/api/res-1.4.0-beta01.txt b/camera/camera-camera2/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-camera2/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-camera2/api/restricted_1.4.0-beta01.txt b/camera/camera-camera2/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..1f2bf8d
--- /dev/null
+++ b/camera/camera-camera2/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.camera.camera2 {
+
+  @RequiresApi(21) public final class Camera2Config {
+    method public static androidx.camera.core.CameraXConfig defaultConfig();
+  }
+
+}
+
+package androidx.camera.camera2.interop {
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraControl {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> addCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> clearCaptureRequestOptions();
+    method public static androidx.camera.camera2.interop.Camera2CameraControl from(androidx.camera.core.CameraControl);
+    method public androidx.camera.camera2.interop.CaptureRequestOptions getCaptureRequestOptions();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setCaptureRequestOptions(androidx.camera.camera2.interop.CaptureRequestOptions);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2CameraInfo {
+    method public static androidx.camera.camera2.interop.Camera2CameraInfo from(androidx.camera.core.CameraInfo);
+    method public <T> T? getCameraCharacteristic(android.hardware.camera2.CameraCharacteristics.Key<T!>);
+    method public String getCameraId();
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public final class Camera2Interop {
+  }
+
+  @RequiresApi(21) public static final class Camera2Interop.Extender<T> {
+    ctor public Camera2Interop.Extender(androidx.camera.core.ExtendableBuilder<T!>);
+    method public <ValueT> androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setDeviceStateCallback(android.hardware.camera2.CameraDevice.StateCallback);
+    method @RequiresApi(28) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setPhysicalCameraId(String);
+    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionCaptureCallback(android.hardware.camera2.CameraCaptureSession.CaptureCallback);
+    method public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setSessionStateCallback(android.hardware.camera2.CameraCaptureSession.StateCallback);
+    method @RequiresApi(33) public androidx.camera.camera2.interop.Camera2Interop.Extender<T!> setStreamUseCase(long);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.camera2.interop.ExperimentalCamera2Interop public class CaptureRequestOptions {
+    method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+  }
+
+  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions!> {
+    ctor public CaptureRequestOptions.Builder();
+    method public androidx.camera.camera2.interop.CaptureRequestOptions build();
+    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
+    method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder setCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>, ValueT);
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCamera2Interop {
+  }
+
+}
+
diff --git a/camera/camera-camera2/api/restricted_current.txt b/camera/camera-camera2/api/restricted_current.txt
index 87c79d0..1f2bf8d 100644
--- a/camera/camera-camera2/api/restricted_current.txt
+++ b/camera/camera-camera2/api/restricted_current.txt
@@ -40,7 +40,7 @@
     method public <ValueT> ValueT? getCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
   }
 
-  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions> {
+  @RequiresApi(21) public static final class CaptureRequestOptions.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.camera2.interop.CaptureRequestOptions!> {
     ctor public CaptureRequestOptions.Builder();
     method public androidx.camera.camera2.interop.CaptureRequestOptions build();
     method public <ValueT> androidx.camera.camera2.interop.CaptureRequestOptions.Builder clearCaptureRequestOption(android.hardware.camera2.CaptureRequest.Key<ValueT!>);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java
index 5027fef..43663c4 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java
@@ -16,11 +16,9 @@
 
 package androidx.camera.camera2.internal;
 
-import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
+import static androidx.camera.camera2.internal.CameraIdUtil.isBackwardCompatible;
 
 import android.content.Context;
-import android.hardware.camera2.CameraCharacteristics;
-import android.os.Build;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -146,7 +144,7 @@
             if (cameraId.equals("0") || cameraId.equals("1")) {
                 backwardCompatibleCameraIds.add(cameraId);
                 continue;
-            } else if (isBackwardCompatible(cameraId)) {
+            } else if (isBackwardCompatible(mCameraManager, cameraId)) {
                 backwardCompatibleCameraIds.add(cameraId);
             } else {
                 Logger.d(TAG, "Camera " + cameraId + " is filtered out because its capabilities "
@@ -156,32 +154,4 @@
 
         return backwardCompatibleCameraIds;
     }
-
-    private boolean isBackwardCompatible(@NonNull String cameraId) throws InitializationException {
-        // Always returns true to not break robolectric tests because the cameras setup in
-        // robolectric don't have REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE capability
-        // by default.
-        if ("robolectric".equals(Build.FINGERPRINT)) {
-            return true;
-        }
-
-        int[] availableCapabilities;
-
-        try {
-            availableCapabilities = mCameraManager.getCameraCharacteristicsCompat(cameraId).get(
-                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
-        } catch (CameraAccessExceptionCompat e) {
-            throw new InitializationException(CameraUnavailableExceptionHelper.createFrom(e));
-        }
-
-        if (availableCapabilities != null) {
-            for (int capability : availableCapabilities) {
-                if (capability == REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 5bc7186..e6361d0 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -1067,7 +1067,7 @@
                     removeMeteringRepeating();
                 } else {
                     // Other normal cases, do nothing.
-                    Logger.d(TAG, "mMeteringRepeating is ATTACHED, "
+                    Logger.d(TAG, "No need to remove a previous mMeteringRepeating, "
                             + "SessionConfig Surfaces: " + sizeSessionSurfaces + ", "
                             + "CaptureConfig Surfaces: " + sizeRepeatingSurfaces);
                 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index 6d83e9f..d86a586 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -19,6 +19,7 @@
 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
 import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
 import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
+import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
 import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
 import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME;
 import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
@@ -56,6 +57,7 @@
 import androidx.camera.core.ExposureState;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.Logger;
+import androidx.camera.core.PhysicalCameraInfo;
 import androidx.camera.core.ZoomState;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.CameraInfoInternal;
@@ -125,6 +127,9 @@
     @NonNull
     private final CameraManagerCompat mCameraManager;
 
+    @Nullable
+    private Set<PhysicalCameraInfo> mPhysicalCameraInfos;
+
     /**
      * Constructs an instance. Before {@link #linkWithCameraControl(Camera2CameraControlImpl)} is
      * called, camera control related API (torch/exposure/zoom) will return default values.
@@ -405,6 +410,12 @@
                 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
     }
 
+    @Override
+    public boolean isLogicalMultiCameraSupported() {
+        return isCapabilitySupported(mCameraCharacteristicsCompat,
+                REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA);
+    }
+
     /** {@inheritDoc} */
     @NonNull
     @Override
@@ -627,6 +638,32 @@
         return map;
     }
 
+    @NonNull
+    @Override
+    public Set<PhysicalCameraInfo> getPhysicalCameraInfos() {
+        if (mPhysicalCameraInfos == null) {
+            mPhysicalCameraInfos = new HashSet<>();
+            for (String physicalCameraId : mCameraCharacteristicsCompat.getPhysicalCameraIds()) {
+                CameraCharacteristicsCompat characteristicsCompat;
+                try {
+                    characteristicsCompat =
+                            mCameraManager.getCameraCharacteristicsCompat(physicalCameraId);
+                } catch (CameraAccessExceptionCompat e) {
+                    Logger.e(TAG,
+                            "Failed to get CameraCharacteristics for cameraId " + physicalCameraId,
+                            e);
+                    return Collections.emptySet();
+                }
+
+                PhysicalCameraInfo physicalCameraInfo = Camera2PhysicalCameraInfo.of(
+                        physicalCameraId, characteristicsCompat);
+                mPhysicalCameraInfos.add(physicalCameraInfo);
+            }
+        }
+
+        return mPhysicalCameraInfos;
+    }
+
     /**
      * A {@link LiveData} which can be redirected to another {@link LiveData}. If no redirection
      * is set, initial value will be used.
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2PhysicalCameraInfo.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2PhysicalCameraInfo.java
new file mode 100644
index 0000000..2bf301e
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2PhysicalCameraInfo.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.PhysicalCameraInfo;
+import androidx.core.util.Preconditions;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Camera2 implementation of {@link PhysicalCameraInfo} which wraps physical camera id and
+ * camera characteristics.
+ */
+@RequiresApi(21)
+@AutoValue
+abstract class Camera2PhysicalCameraInfo implements PhysicalCameraInfo {
+
+    @NonNull
+    @Override
+    public abstract String getPhysicalCameraId();
+
+    @NonNull
+    public abstract CameraCharacteristicsCompat getCameraCharacteristicsCompat();
+
+    @RequiresApi(28)
+    @NonNull
+    @Override
+    public Integer getLensPoseReference() {
+        Integer lensPoseRef =
+                getCameraCharacteristicsCompat().get(CameraCharacteristics.LENS_POSE_REFERENCE);
+        Preconditions.checkNotNull(lensPoseRef);
+        return lensPoseRef;
+    }
+
+    /**
+     * Creates {@link Camera2PhysicalCameraInfo} instance.
+     *
+     * @param physicalCameraId physical camera id.
+     * @param cameraCharacteristicsCompat {@link CameraCharacteristicsCompat}.
+     * @return
+     */
+    @NonNull
+    public static Camera2PhysicalCameraInfo of(
+            @NonNull String physicalCameraId,
+            @NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
+        return new AutoValue_Camera2PhysicalCameraInfo(
+                physicalCameraId, cameraCharacteristicsCompat);
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraIdUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraIdUtil.java
new file mode 100644
index 0000000..8052ec8
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraIdUtil.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
+import androidx.camera.camera2.internal.compat.CameraManagerCompat;
+import androidx.camera.core.InitializationException;
+
+/**
+ * Utility class to enumerate and filter camera ids.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public final class CameraIdUtil {
+
+    private CameraIdUtil() {
+    }
+
+    /**
+     * Checks whether the camera has
+     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE} or not.
+     *
+     * @param cameraManagerCompat {@link CameraManagerCompat}
+     * @param cameraId camera id
+     * @return True if it is backward compatible, otherwise false.
+     * @throws InitializationException
+     *
+     */
+    public static boolean isBackwardCompatible(
+            @NonNull CameraManagerCompat cameraManagerCompat,
+            @NonNull String cameraId) throws
+            InitializationException {
+        // Always returns true to not break robolectric tests because the cameras setup in
+        // robolectric don't have REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE capability
+        // by default.
+        if ("robolectric".equals(Build.FINGERPRINT)) {
+            return true;
+        }
+
+        int[] availableCapabilities;
+
+        try {
+            availableCapabilities = cameraManagerCompat.getCameraCharacteristicsCompat(cameraId)
+                    .get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+        } catch (CameraAccessExceptionCompat e) {
+            throw new InitializationException(CameraUnavailableExceptionHelper.createFrom(e));
+        }
+
+        if (availableCapabilities != null) {
+            for (int capability : availableCapabilities) {
+                if (capability == REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index 1e8e2fc..ee7006c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -212,10 +212,12 @@
 
                             mProcessorState = ProcessorState.SESSION_INITIALIZED;
                             try {
-                                DeferrableSurfaces.incrementAll(mOutputSurfaces);
+                                List<DeferrableSurface> surfacesToIncrement =
+                                        new ArrayList<>(mOutputSurfaces);
                                 if (postviewDeferrableSurface != null) {
-                                    postviewDeferrableSurface.incrementUseCount();
+                                    surfacesToIncrement.add(postviewDeferrableSurface);
                                 }
+                                DeferrableSurfaces.incrementAll(surfacesToIncrement);
                             } catch (DeferrableSurface.SurfaceClosedException e) {
                                 return Futures.immediateFailedFuture(e);
                             }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
index e6680bf..c41a7857 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2.internal.concurrent;
 
+import static androidx.camera.camera2.internal.CameraIdUtil.isBackwardCompatible;
+
 import android.hardware.camera2.CameraCharacteristics;
 
 import androidx.annotation.NonNull;
@@ -29,10 +31,12 @@
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
+import androidx.camera.core.InitializationException;
 import androidx.camera.core.Logger;
 import androidx.camera.core.concurrent.CameraCoordinator;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -150,18 +154,31 @@
     }
 
     private void retrieveConcurrentCameraIds() {
+        Set<Set<String>> concurrentCameraIds = new HashSet<>();
         try {
-            mConcurrentCameraIds = mCameraManager.getConcurrentCameraIds();
+            concurrentCameraIds = mCameraManager.getConcurrentCameraIds();
         } catch (CameraAccessExceptionCompat e) {
             Logger.e(TAG, "Failed to get concurrent camera ids");
         }
 
-        for (Set<String> concurrentCameraIdList: mConcurrentCameraIds) {
+        for (Set<String> concurrentCameraIdList: concurrentCameraIds) {
             List<String> cameraIdList = new ArrayList<>(concurrentCameraIdList);
 
             if (cameraIdList.size() >= 2) {
                 String cameraId1 = cameraIdList.get(0);
                 String cameraId2 = cameraIdList.get(1);
+                boolean isBackwardCompatible = false;
+                try {
+                    isBackwardCompatible = isBackwardCompatible(mCameraManager, cameraId1)
+                            && isBackwardCompatible(mCameraManager, cameraId2);
+                } catch (InitializationException e) {
+                    Logger.d(TAG, "Concurrent camera id pair: (" + cameraId1 + ", "
+                            + cameraId2 + ") is not backward compatible");
+                }
+                if (!isBackwardCompatible) {
+                    continue;
+                }
+                mConcurrentCameraIds.add(new HashSet<>(Arrays.asList(cameraId1, cameraId2)));
                 if (!mConcurrentCameraIdMap.containsKey(cameraId1)) {
                     mConcurrentCameraIdMap.put(cameraId1, new ArrayList<>());
                 }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
index 1932f6a..a47d41c 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
@@ -62,6 +62,7 @@
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.ExposureState;
 import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.PhysicalCameraInfo;
 import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
 import androidx.camera.core.TorchState;
 import androidx.camera.core.ZoomState;
@@ -86,6 +87,7 @@
 import org.robolectric.shadows.StreamConfigurationMapBuilder;
 import org.robolectric.util.ReflectionHelpers;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -515,6 +517,34 @@
         assertThat(map.get("3")).isSameInstanceAs(characteristicsPhysical3);
     }
 
+    @Config(minSdk = 28)
+    @RequiresApi(28)
+    @Test
+    public void canReturnPhysicalCameraInfos()
+            throws CameraAccessExceptionCompat {
+        init(/* hasAvailableCapabilities = */ true);
+
+        CameraCharacteristics characteristics0 = mock(CameraCharacteristics.class);
+        CameraCharacteristics characteristicsPhysical2 = mock(CameraCharacteristics.class);
+        CameraCharacteristics characteristicsPhysical3 = mock(CameraCharacteristics.class);
+        when(characteristics0.getPhysicalCameraIds())
+                .thenReturn(new HashSet<>(Arrays.asList("0", "2", "3")));
+        CameraManagerCompat cameraManagerCompat = initCameraManagerWithPhysicalIds(
+                Arrays.asList(
+                        new Pair<>("0", characteristics0),
+                        new Pair<>("2", characteristicsPhysical2),
+                        new Pair<>("3", characteristicsPhysical3)));
+        Camera2CameraInfoImpl impl = new Camera2CameraInfoImpl("0", cameraManagerCompat);
+
+        List<PhysicalCameraInfo> physicalCameraInfos = new ArrayList<>(
+                impl.getPhysicalCameraInfos());
+        assertThat(physicalCameraInfos.size()).isEqualTo(3);
+        assertThat(characteristics0.getPhysicalCameraIds()).containsExactly(
+                physicalCameraInfos.get(0).getPhysicalCameraId(),
+                physicalCameraInfos.get(1).getPhysicalCameraId(),
+                physicalCameraInfos.get(2).getPhysicalCameraId());
+    }
+
     @Config(maxSdk = 27)
     @Test
     public void canReturnCameraCharacteristicsMapWithMainCamera()
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
index fa3f220..f795442 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
@@ -20,6 +20,7 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
 import android.hardware.camera2.CameraManager
+import android.hardware.camera2.CameraMetadata
 import android.os.Build
 import androidx.camera.camera2.internal.Camera2CameraInfoImpl
 import androidx.camera.camera2.internal.compat.CameraManagerCompat
@@ -65,9 +66,17 @@
         val cameraCharacteristics0 = mock(CameraCharacteristics::class.java)
         Mockito.`when`(cameraCharacteristics0.get(CameraCharacteristics.LENS_FACING))
             .thenReturn(CameraCharacteristics.LENS_FACING_BACK)
+        Mockito.`when`(cameraCharacteristics0.get(
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
+            .thenReturn(intArrayOf(
+                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE))
         val cameraCharacteristics1 = mock(CameraCharacteristics::class.java)
         Mockito.`when`(cameraCharacteristics1.get(CameraCharacteristics.LENS_FACING))
             .thenReturn(CameraCharacteristics.LENS_FACING_FRONT)
+        Mockito.`when`(cameraCharacteristics1.get(
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
+            .thenReturn(intArrayOf(
+                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE))
         fakeCameraImpl.addCamera("0", cameraCharacteristics0)
         fakeCameraImpl.addCamera("1", cameraCharacteristics1)
         cameraCoordinator = Camera2CameraCoordinator(CameraManagerCompat.from(fakeCameraImpl))
diff --git a/camera/camera-core/api/1.4.0-beta01.txt b/camera/camera-core/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..abd42142
--- /dev/null
+++ b/camera/camera-core/api/1.4.0-beta01.txt
@@ -0,0 +1,716 @@
+// Signature format: 4.0
+package androidx.camera.core {
+
+  @RequiresApi(21) public class AspectRatio {
+    field public static final int RATIO_16_9 = 1; // 0x1
+    field public static final int RATIO_4_3 = 0; // 0x0
+    field public static final int RATIO_DEFAULT = -1; // 0xffffffff
+  }
+
+  @RequiresApi(21) public interface Camera {
+    method public androidx.camera.core.CameraControl getCameraControl();
+    method public androidx.camera.core.CameraInfo getCameraInfo();
+  }
+
+  @RequiresApi(21) public interface CameraControl {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
+  }
+
+  public static final class CameraControl.OperationCanceledException extends java.lang.Exception {
+  }
+
+  @RequiresApi(21) public abstract class CameraEffect {
+    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.ImageProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.SurfaceProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+    method public androidx.core.util.Consumer<java.lang.Throwable!> getErrorListener();
+    method public java.util.concurrent.Executor getExecutor();
+    method public androidx.camera.core.SurfaceProcessor? getSurfaceProcessor();
+    method public int getTargets();
+    field public static final int IMAGE_CAPTURE = 4; // 0x4
+    field public static final int PREVIEW = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 2; // 0x2
+  }
+
+  @RequiresApi(21) public interface CameraFilter {
+    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+  }
+
+  @RequiresApi(21) public interface CameraInfo {
+    method public androidx.camera.core.CameraSelector getCameraSelector();
+    method public androidx.lifecycle.LiveData<androidx.camera.core.CameraState!> getCameraState();
+    method public androidx.camera.core.ExposureState getExposureState();
+    method @FloatRange(from=0, fromInclusive=false) public default float getIntrinsicZoomRatio();
+    method public default int getLensFacing();
+    method public int getSensorRotationDegrees();
+    method public int getSensorRotationDegrees(int);
+    method public default java.util.Set<android.util.Range<java.lang.Integer!>!> getSupportedFrameRateRanges();
+    method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+    method public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+    method public boolean hasFlashUnit();
+    method public default boolean isFocusMeteringSupported(androidx.camera.core.FocusMeteringAction);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public default boolean isZslSupported();
+    method public static boolean mustPlayShutterSound();
+    method public default java.util.Set<androidx.camera.core.DynamicRange!> querySupportedDynamicRanges(java.util.Set<androidx.camera.core.DynamicRange!>);
+  }
+
+  @RequiresApi(21) public final class CameraInfoUnavailableException extends java.lang.Exception {
+  }
+
+  @RequiresApi(21) public interface CameraProvider {
+    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+  }
+
+  @RequiresApi(21) public final class CameraSelector {
+    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+    field public static final androidx.camera.core.CameraSelector DEFAULT_BACK_CAMERA;
+    field public static final androidx.camera.core.CameraSelector DEFAULT_FRONT_CAMERA;
+    field public static final int LENS_FACING_BACK = 1; // 0x1
+    field @SuppressCompatibility @androidx.camera.core.ExperimentalLensFacing public static final int LENS_FACING_EXTERNAL = 2; // 0x2
+    field public static final int LENS_FACING_FRONT = 0; // 0x0
+    field public static final int LENS_FACING_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public static final class CameraSelector.Builder {
+    ctor public CameraSelector.Builder();
+    method public androidx.camera.core.CameraSelector.Builder addCameraFilter(androidx.camera.core.CameraFilter);
+    method public androidx.camera.core.CameraSelector build();
+    method public androidx.camera.core.CameraSelector.Builder requireLensFacing(int);
+  }
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class CameraState {
+    ctor public CameraState();
+    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type);
+    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type, androidx.camera.core.CameraState.StateError?);
+    method public abstract androidx.camera.core.CameraState.StateError? getError();
+    method public abstract androidx.camera.core.CameraState.Type getType();
+    field public static final int ERROR_CAMERA_DISABLED = 5; // 0x5
+    field public static final int ERROR_CAMERA_FATAL_ERROR = 6; // 0x6
+    field public static final int ERROR_CAMERA_IN_USE = 2; // 0x2
+    field public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7; // 0x7
+    field public static final int ERROR_MAX_CAMERAS_IN_USE = 1; // 0x1
+    field public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3; // 0x3
+    field public static final int ERROR_STREAM_CONFIG = 4; // 0x4
+  }
+
+  public enum CameraState.ErrorType {
+    enum_constant public static final androidx.camera.core.CameraState.ErrorType CRITICAL;
+    enum_constant public static final androidx.camera.core.CameraState.ErrorType RECOVERABLE;
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class CameraState.StateError {
+    ctor public CameraState.StateError();
+    method public static androidx.camera.core.CameraState.StateError create(int);
+    method public static androidx.camera.core.CameraState.StateError create(int, Throwable?);
+    method public abstract Throwable? getCause();
+    method public abstract int getCode();
+    method public androidx.camera.core.CameraState.ErrorType getType();
+  }
+
+  public enum CameraState.Type {
+    enum_constant public static final androidx.camera.core.CameraState.Type CLOSED;
+    enum_constant public static final androidx.camera.core.CameraState.Type CLOSING;
+    enum_constant public static final androidx.camera.core.CameraState.Type OPEN;
+    enum_constant public static final androidx.camera.core.CameraState.Type OPENING;
+    enum_constant public static final androidx.camera.core.CameraState.Type PENDING_OPEN;
+  }
+
+  @RequiresApi(21) public class CameraUnavailableException extends java.lang.Exception {
+    ctor public CameraUnavailableException(int);
+    ctor public CameraUnavailableException(int, String?);
+    ctor public CameraUnavailableException(int, String?, Throwable?);
+    ctor public CameraUnavailableException(int, Throwable?);
+    method public int getReason();
+    field public static final int CAMERA_DISABLED = 1; // 0x1
+    field public static final int CAMERA_DISCONNECTED = 2; // 0x2
+    field public static final int CAMERA_ERROR = 3; // 0x3
+    field public static final int CAMERA_IN_USE = 4; // 0x4
+    field public static final int CAMERA_MAX_IN_USE = 5; // 0x5
+    field public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 6; // 0x6
+    field public static final int CAMERA_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @RequiresApi(21) public final class CameraXConfig {
+    method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
+    method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
+    method public long getCameraOpenRetryMaxTimeoutInMillisWhileResuming();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.RetryPolicy getCameraProviderInitRetryPolicy();
+    method public int getMinimumLoggingLevel();
+    method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
+  }
+
+  public static final class CameraXConfig.Builder {
+    method public androidx.camera.core.CameraXConfig build();
+    method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
+    method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
+    method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.CameraXConfig.Builder setCameraOpenRetryMaxTimeoutInMillisWhileResuming(long);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.CameraXConfig.Builder setCameraProviderInitRetryPolicy(androidx.camera.core.RetryPolicy);
+    method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
+    method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
+  }
+
+  public static interface CameraXConfig.Provider {
+    method public androidx.camera.core.CameraXConfig getCameraXConfig();
+  }
+
+  @RequiresApi(21) public class ConcurrentCamera {
+    ctor public ConcurrentCamera(java.util.List<androidx.camera.core.Camera!>);
+    method public java.util.List<androidx.camera.core.Camera!> getCameras();
+  }
+
+  public static final class ConcurrentCamera.SingleCameraConfig {
+    ctor public ConcurrentCamera.SingleCameraConfig(androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup, androidx.lifecycle.LifecycleOwner);
+    method public androidx.camera.core.CameraSelector getCameraSelector();
+    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
+    method public androidx.camera.core.UseCaseGroup getUseCaseGroup();
+  }
+
+  @RequiresApi(21) public final class DisplayOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+    ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
+  }
+
+  @RequiresApi(21) public final class DynamicRange {
+    ctor public DynamicRange(int, int);
+    method public int getBitDepth();
+    method public int getEncoding();
+    field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
+    field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
+    field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
+    field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
+    field public static final int ENCODING_HDR10 = 4; // 0x4
+    field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
+    field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
+    field public static final int ENCODING_HLG = 3; // 0x3
+    field public static final int ENCODING_SDR = 1; // 0x1
+    field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
+    field public static final androidx.camera.core.DynamicRange SDR;
+    field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalZeroShutterLag {
+  }
+
+  @RequiresApi(21) public interface ExposureState {
+    method public int getExposureCompensationIndex();
+    method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
+    method public android.util.Rational getExposureCompensationStep();
+    method public boolean isExposureCompensationSupported();
+  }
+
+  @RequiresApi(21) public interface ExtendableBuilder<T> {
+    method public T build();
+  }
+
+  @RequiresApi(21) public final class FocusMeteringAction {
+    method public long getAutoCancelDurationInMillis();
+    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAe();
+    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAf();
+    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAwb();
+    method public boolean isAutoCancelEnabled();
+    field public static final int FLAG_AE = 2; // 0x2
+    field public static final int FLAG_AF = 1; // 0x1
+    field public static final int FLAG_AWB = 4; // 0x4
+  }
+
+  public static class FocusMeteringAction.Builder {
+    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint);
+    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint, int);
+    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint);
+    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint, int);
+    method public androidx.camera.core.FocusMeteringAction build();
+    method public androidx.camera.core.FocusMeteringAction.Builder disableAutoCancel();
+    method public androidx.camera.core.FocusMeteringAction.Builder setAutoCancelDuration(@IntRange(from=1) long, java.util.concurrent.TimeUnit);
+  }
+
+  @RequiresApi(21) public final class FocusMeteringResult {
+    method public boolean isFocusSuccessful();
+  }
+
+  @RequiresApi(21) public final class ImageAnalysis extends androidx.camera.core.UseCase {
+    method public void clearAnalyzer();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalUseCaseApi public java.util.concurrent.Executor? getBackgroundExecutor();
+    method public int getBackpressureStrategy();
+    method public int getImageQueueDepth();
+    method public int getOutputImageFormat();
+    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public int getTargetRotation();
+    method public boolean isOutputImageRotationEnabled();
+    method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+    method public void setTargetRotation(int);
+    field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
+    field public static final int COORDINATE_SYSTEM_SENSOR = 2; // 0x2
+    field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
+    field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
+    field public static final int STRATEGY_BLOCK_PRODUCER = 1; // 0x1
+    field public static final int STRATEGY_KEEP_ONLY_LATEST = 0; // 0x0
+  }
+
+  public static interface ImageAnalysis.Analyzer {
+    method public void analyze(androidx.camera.core.ImageProxy);
+    method public default android.util.Size? getDefaultTargetResolution();
+    method public default int getTargetCoordinateSystem();
+    method public default void updateTransform(android.graphics.Matrix?);
+  }
+
+  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis!> {
+    ctor public ImageAnalysis.Builder();
+    method public androidx.camera.core.ImageAnalysis build();
+    method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageAnalysis.Builder setBackpressureStrategy(int);
+    method public androidx.camera.core.ImageAnalysis.Builder setImageQueueDepth(int);
+    method public androidx.camera.core.ImageAnalysis.Builder setOutputImageFormat(int);
+    method @RequiresApi(23) public androidx.camera.core.ImageAnalysis.Builder setOutputImageRotationEnabled(boolean);
+    method public androidx.camera.core.ImageAnalysis.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.ImageAnalysis.Builder setTargetName(String);
+    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetResolution(android.util.Size);
+    method public androidx.camera.core.ImageAnalysis.Builder setTargetRotation(int);
+  }
+
+  @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
+    method public int getCaptureMode();
+    method public int getFlashMode();
+    method public static androidx.camera.core.ImageCaptureCapabilities getImageCaptureCapabilities(androidx.camera.core.CameraInfo);
+    method @IntRange(from=1, to=100) public int getJpegQuality();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector? getPostviewResolutionSelector();
+    method public androidx.camera.core.ImageCaptureLatencyEstimate getRealtimeCaptureLatencyEstimate();
+    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+    method public int getTargetRotation();
+    method public boolean isPostviewEnabled();
+    method public void setCropAspectRatio(android.util.Rational);
+    method public void setFlashMode(int);
+    method public void setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash?);
+    method public void setTargetRotation(int);
+    method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+    method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+    field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
+    field public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1; // 0x1
+    field @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2; // 0x2
+    field public static final int ERROR_CAMERA_CLOSED = 3; // 0x3
+    field public static final int ERROR_CAPTURE_FAILED = 2; // 0x2
+    field public static final int ERROR_FILE_IO = 1; // 0x1
+    field public static final int ERROR_INVALID_CAMERA = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int FLASH_MODE_AUTO = 0; // 0x0
+    field public static final int FLASH_MODE_OFF = 2; // 0x2
+    field public static final int FLASH_MODE_ON = 1; // 0x1
+    field public static final int FLASH_MODE_SCREEN = 3; // 0x3
+  }
+
+  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture!> {
+    ctor public ImageCapture.Builder();
+    method public androidx.camera.core.ImageCapture build();
+    method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
+    method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
+    method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
+    method public androidx.camera.core.ImageCapture.Builder setPostviewEnabled(boolean);
+    method public androidx.camera.core.ImageCapture.Builder setPostviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method public androidx.camera.core.ImageCapture.Builder setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash);
+    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
+    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
+    method public androidx.camera.core.ImageCapture.Builder setTargetRotation(int);
+  }
+
+  public static final class ImageCapture.Metadata {
+    ctor public ImageCapture.Metadata();
+    method public android.location.Location? getLocation();
+    method public boolean isReversedHorizontal();
+    method public boolean isReversedVertical();
+    method public void setLocation(android.location.Location?);
+    method public void setReversedHorizontal(boolean);
+    method public void setReversedVertical(boolean);
+  }
+
+  public abstract static class ImageCapture.OnImageCapturedCallback {
+    ctor public ImageCapture.OnImageCapturedCallback();
+    method public void onCaptureProcessProgressed(int);
+    method public void onCaptureStarted();
+    method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
+    method public void onError(androidx.camera.core.ImageCaptureException);
+    method public void onPostviewBitmapAvailable(android.graphics.Bitmap);
+  }
+
+  public static interface ImageCapture.OnImageSavedCallback {
+    method public default void onCaptureProcessProgressed(int);
+    method public default void onCaptureStarted();
+    method public void onError(androidx.camera.core.ImageCaptureException);
+    method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
+    method public default void onPostviewBitmapAvailable(android.graphics.Bitmap);
+  }
+
+  public static final class ImageCapture.OutputFileOptions {
+  }
+
+  public static final class ImageCapture.OutputFileOptions.Builder {
+    ctor public ImageCapture.OutputFileOptions.Builder(android.content.ContentResolver, android.net.Uri, android.content.ContentValues);
+    ctor public ImageCapture.OutputFileOptions.Builder(java.io.File);
+    ctor public ImageCapture.OutputFileOptions.Builder(java.io.OutputStream);
+    method public androidx.camera.core.ImageCapture.OutputFileOptions build();
+    method public androidx.camera.core.ImageCapture.OutputFileOptions.Builder setMetadata(androidx.camera.core.ImageCapture.Metadata);
+  }
+
+  public static class ImageCapture.OutputFileResults {
+    method public android.net.Uri? getSavedUri();
+  }
+
+  public static interface ImageCapture.ScreenFlash {
+    method @UiThread public void apply(long, androidx.camera.core.ImageCapture.ScreenFlashListener);
+    method @UiThread public void clear();
+  }
+
+  public static interface ImageCapture.ScreenFlashListener {
+    method public void onCompleted();
+  }
+
+  public interface ImageCaptureCapabilities {
+    method public boolean isCaptureProcessProgressSupported();
+    method public boolean isPostviewSupported();
+  }
+
+  @RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
+    ctor public ImageCaptureException(int, String, Throwable?);
+    method public int getImageCaptureError();
+  }
+
+  @RequiresApi(21) public class ImageCaptureLatencyEstimate {
+    ctor public ImageCaptureLatencyEstimate(long, long);
+    method public long getCaptureLatencyMillis();
+    method public long getProcessingLatencyMillis();
+    method public long getTotalCaptureLatencyMillis();
+    field public static final long UNDEFINED_CAPTURE_LATENCY = -1L; // 0xffffffffffffffffL
+    field public static final androidx.camera.core.ImageCaptureLatencyEstimate UNDEFINED_IMAGE_CAPTURE_LATENCY;
+    field public static final long UNDEFINED_PROCESSING_LATENCY = -1L; // 0xffffffffffffffffL
+  }
+
+  @RequiresApi(21) public interface ImageInfo {
+    method public int getRotationDegrees();
+    method public default android.graphics.Matrix getSensorToBufferTransformMatrix();
+    method public long getTimestamp();
+  }
+
+  public interface ImageProcessor {
+    method public androidx.camera.core.ImageProcessor.Response process(androidx.camera.core.ImageProcessor.Request) throws androidx.camera.core.ProcessingException;
+  }
+
+  public static interface ImageProcessor.Request {
+    method public androidx.camera.core.ImageProxy getInputImage();
+    method public int getOutputFormat();
+  }
+
+  public static interface ImageProcessor.Response {
+    method public androidx.camera.core.ImageProxy getOutputImage();
+  }
+
+  @RequiresApi(21) public interface ImageProxy extends java.lang.AutoCloseable {
+    method public void close();
+    method public android.graphics.Rect getCropRect();
+    method public int getFormat();
+    method public int getHeight();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalGetImage public android.media.Image? getImage();
+    method public androidx.camera.core.ImageInfo getImageInfo();
+    method public androidx.camera.core.ImageProxy.PlaneProxy![] getPlanes();
+    method public int getWidth();
+    method public void setCropRect(android.graphics.Rect?);
+    method public default android.graphics.Bitmap toBitmap();
+  }
+
+  public static interface ImageProxy.PlaneProxy {
+    method public java.nio.ByteBuffer getBuffer();
+    method public int getPixelStride();
+    method public int getRowStride();
+  }
+
+  @RequiresApi(21) public class InitializationException extends java.lang.Exception {
+    ctor public InitializationException(String?);
+    ctor public InitializationException(String?, Throwable?);
+    ctor public InitializationException(Throwable?);
+  }
+
+  @RequiresApi(21) public class MeteringPoint {
+    method public float getSize();
+  }
+
+  @RequiresApi(21) public abstract class MeteringPointFactory {
+    method public final androidx.camera.core.MeteringPoint createPoint(float, float);
+    method public final androidx.camera.core.MeteringPoint createPoint(float, float, float);
+    method public static float getDefaultPointSize();
+  }
+
+  @RequiresApi(21) public class MirrorMode {
+    field public static final int MIRROR_MODE_OFF = 0; // 0x0
+    field public static final int MIRROR_MODE_ON = 1; // 0x1
+    field public static final int MIRROR_MODE_ON_FRONT_ONLY = 2; // 0x2
+  }
+
+  @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
+    method public androidx.camera.core.DynamicRange getDynamicRange();
+    method public static androidx.camera.core.PreviewCapabilities getPreviewCapabilities(androidx.camera.core.CameraInfo);
+    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 public boolean isPreviewStabilizationEnabled();
+    method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
+    method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
+    method public void setTargetRotation(int);
+  }
+
+  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview!> {
+    ctor public Preview.Builder();
+    method public androidx.camera.core.Preview build();
+    method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+    method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
+    method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+    method public androidx.camera.core.Preview.Builder setTargetName(String);
+    method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
+    method public androidx.camera.core.Preview.Builder setTargetRotation(int);
+  }
+
+  public static interface Preview.SurfaceProvider {
+    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+  }
+
+  @RequiresApi(21) public interface PreviewCapabilities {
+    method public boolean isStabilizationSupported();
+  }
+
+  public class ProcessingException extends java.lang.Exception {
+    ctor public ProcessingException();
+  }
+
+  @RequiresApi(21) public class ResolutionInfo {
+    ctor public ResolutionInfo(android.util.Size, android.graphics.Rect, int);
+    method public android.graphics.Rect getCropRect();
+    method public android.util.Size getResolution();
+    method public int getRotationDegrees();
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.core.ExperimentalRetryPolicy public interface RetryPolicy {
+    method public static long getDefaultRetryTimeoutInMillis();
+    method public default long getTimeoutInMillis();
+    method public androidx.camera.core.RetryPolicy.RetryConfig onRetryDecisionRequested(androidx.camera.core.RetryPolicy.ExecutionState);
+    field public static final androidx.camera.core.RetryPolicy DEFAULT;
+    field public static final androidx.camera.core.RetryPolicy NEVER;
+    field public static final androidx.camera.core.RetryPolicy RETRY_UNAVAILABLE_CAMERA;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.Builder {
+    ctor public RetryPolicy.Builder(androidx.camera.core.RetryPolicy);
+    method public androidx.camera.core.RetryPolicy build();
+    method public androidx.camera.core.RetryPolicy.Builder setTimeoutInMillis(long);
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static interface RetryPolicy.ExecutionState {
+    method public Throwable? getCause();
+    method public long getExecutedTimeInMillis();
+    method public int getNumOfAttempts();
+    method public int getStatus();
+    field public static final int STATUS_CAMERA_UNAVAILABLE = 2; // 0x2
+    field public static final int STATUS_CONFIGURATION_FAIL = 1; // 0x1
+    field public static final int STATUS_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig {
+    method public static long getDefaultRetryDelayInMillis();
+    method public long getRetryDelayInMillis();
+    method public boolean shouldRetry();
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig DEFAULT_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig MINI_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig NOT_RETRY;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig.Builder {
+    ctor public RetryPolicy.RetryConfig.Builder();
+    method public androidx.camera.core.RetryPolicy.RetryConfig build();
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setShouldRetry(boolean);
+  }
+
+  @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+    ctor public SurfaceOrientedMeteringPointFactory(float, float);
+    ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
+  }
+
+  public interface SurfaceOutput extends java.io.Closeable {
+    method public void close();
+    method public default android.graphics.Matrix getSensorToBufferTransform();
+    method public android.util.Size getSize();
+    method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
+    method public int getTargets();
+    method public void updateTransformMatrix(float[], float[]);
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class SurfaceOutput.Event {
+    method public abstract int getEventCode();
+    method public abstract androidx.camera.core.SurfaceOutput getSurfaceOutput();
+    field public static final int EVENT_REQUEST_CLOSE = 0; // 0x0
+  }
+
+  public interface SurfaceProcessor {
+    method public void onInputSurface(androidx.camera.core.SurfaceRequest) throws androidx.camera.core.ProcessingException;
+    method public void onOutputSurface(androidx.camera.core.SurfaceOutput) throws androidx.camera.core.ProcessingException;
+  }
+
+  @RequiresApi(21) public final class SurfaceRequest {
+    method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
+    method public void clearTransformationInfoListener();
+    method public androidx.camera.core.DynamicRange getDynamicRange();
+    method public android.util.Size getResolution();
+    method public boolean invalidate();
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
+    method public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
+    method public boolean willNotProvideSurface();
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
+    method public abstract android.graphics.Rect getCropRect();
+    method public abstract int getRotationDegrees();
+    method public abstract android.graphics.Matrix getSensorToBufferTransform();
+    method public abstract boolean hasCameraTransform();
+    method public abstract boolean isMirroring();
+  }
+
+  public static interface SurfaceRequest.TransformationInfoListener {
+    method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
+  }
+
+  @RequiresApi(21) public class TorchState {
+    field public static final int OFF = 0; // 0x0
+    field public static final int ON = 1; // 0x1
+  }
+
+  @RequiresApi(21) public abstract class UseCase {
+    method public static int snapToSurfaceRotation(@IntRange(from=0, to=359) int);
+  }
+
+  @RequiresApi(21) public final class UseCaseGroup {
+    method public java.util.List<androidx.camera.core.CameraEffect!> getEffects();
+    method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+    method public androidx.camera.core.ViewPort? getViewPort();
+  }
+
+  public static final class UseCaseGroup.Builder {
+    ctor public UseCaseGroup.Builder();
+    method public androidx.camera.core.UseCaseGroup.Builder addEffect(androidx.camera.core.CameraEffect);
+    method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+    method public androidx.camera.core.UseCaseGroup build();
+    method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+  }
+
+  @RequiresApi(21) public final class ViewPort {
+    method public android.util.Rational getAspectRatio();
+    method public int getLayoutDirection();
+    method public int getRotation();
+    method public int getScaleType();
+    field public static final int FILL_CENTER = 1; // 0x1
+    field public static final int FILL_END = 2; // 0x2
+    field public static final int FILL_START = 0; // 0x0
+    field public static final int FIT = 3; // 0x3
+  }
+
+  public static final class ViewPort.Builder {
+    ctor public ViewPort.Builder(android.util.Rational, int);
+    method public androidx.camera.core.ViewPort build();
+    method public androidx.camera.core.ViewPort.Builder setLayoutDirection(int);
+    method public androidx.camera.core.ViewPort.Builder setScaleType(int);
+  }
+
+  @RequiresApi(21) public interface ZoomState {
+    method public float getLinearZoom();
+    method public float getMaxZoomRatio();
+    method public float getMinZoomRatio();
+    method public float getZoomRatio();
+  }
+
+}
+
+package androidx.camera.core.resolutionselector {
+
+  @RequiresApi(21) public final class AspectRatioStrategy {
+    ctor public AspectRatioStrategy(int, int);
+    method public int getFallbackRule();
+    method public int getPreferredAspectRatio();
+    field public static final int FALLBACK_RULE_AUTO = 1; // 0x1
+    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_16_9_FALLBACK_AUTO_STRATEGY;
+    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_4_3_FALLBACK_AUTO_STRATEGY;
+  }
+
+  @RequiresApi(21) public interface ResolutionFilter {
+    method public java.util.List<android.util.Size!> filter(java.util.List<android.util.Size!>, int);
+  }
+
+  @RequiresApi(21) public final class ResolutionSelector {
+    method public int getAllowedResolutionMode();
+    method public androidx.camera.core.resolutionselector.AspectRatioStrategy getAspectRatioStrategy();
+    method public androidx.camera.core.resolutionselector.ResolutionFilter? getResolutionFilter();
+    method public androidx.camera.core.resolutionselector.ResolutionStrategy? getResolutionStrategy();
+    field public static final int PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION = 0; // 0x0
+    field public static final int PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE = 1; // 0x1
+  }
+
+  public static final class ResolutionSelector.Builder {
+    ctor public ResolutionSelector.Builder();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector build();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAllowedResolutionMode(int);
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAspectRatioStrategy(androidx.camera.core.resolutionselector.AspectRatioStrategy);
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter);
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionStrategy(androidx.camera.core.resolutionselector.ResolutionStrategy);
+  }
+
+  @RequiresApi(21) public final class ResolutionStrategy {
+    ctor public ResolutionStrategy(android.util.Size, int);
+    method public android.util.Size? getBoundSize();
+    method public int getFallbackRule();
+    field public static final int FALLBACK_RULE_CLOSEST_HIGHER = 2; // 0x2
+    field public static final int FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER = 1; // 0x1
+    field public static final int FALLBACK_RULE_CLOSEST_LOWER = 4; // 0x4
+    field public static final int FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER = 3; // 0x3
+    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+    field public static final androidx.camera.core.resolutionselector.ResolutionStrategy HIGHEST_AVAILABLE_STRATEGY;
+  }
+
+}
+
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index 2a567c8..abd42142 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -277,7 +277,7 @@
     method public default void updateTransform(android.graphics.Matrix?);
   }
 
-  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis> {
+  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis!> {
     ctor public ImageAnalysis.Builder();
     method public androidx.camera.core.ImageAnalysis build();
     method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
@@ -324,7 +324,7 @@
     field public static final int FLASH_MODE_SCREEN = 3; // 0x3
   }
 
-  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture> {
+  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture!> {
     ctor public ImageCapture.Builder();
     method public androidx.camera.core.ImageCapture build();
     method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
@@ -485,7 +485,7 @@
     method public void setTargetRotation(int);
   }
 
-  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
+  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview!> {
     ctor public Preview.Builder();
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
diff --git a/camera/camera-core/api/res-1.4.0-beta01.txt b/camera/camera-core/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-core/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-core/api/restricted_1.4.0-beta01.txt b/camera/camera-core/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..abd42142
--- /dev/null
+++ b/camera/camera-core/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,716 @@
+// Signature format: 4.0
+package androidx.camera.core {
+
+  @RequiresApi(21) public class AspectRatio {
+    field public static final int RATIO_16_9 = 1; // 0x1
+    field public static final int RATIO_4_3 = 0; // 0x0
+    field public static final int RATIO_DEFAULT = -1; // 0xffffffff
+  }
+
+  @RequiresApi(21) public interface Camera {
+    method public androidx.camera.core.CameraControl getCameraControl();
+    method public androidx.camera.core.CameraInfo getCameraInfo();
+  }
+
+  @RequiresApi(21) public interface CameraControl {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> cancelFocusAndMetering();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> setExposureCompensationIndex(int);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.camera.core.FocusMeteringResult!> startFocusAndMetering(androidx.camera.core.FocusMeteringAction);
+  }
+
+  public static final class CameraControl.OperationCanceledException extends java.lang.Exception {
+  }
+
+  @RequiresApi(21) public abstract class CameraEffect {
+    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.ImageProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+    ctor protected CameraEffect(int, java.util.concurrent.Executor, androidx.camera.core.SurfaceProcessor, androidx.core.util.Consumer<java.lang.Throwable!>);
+    method public androidx.core.util.Consumer<java.lang.Throwable!> getErrorListener();
+    method public java.util.concurrent.Executor getExecutor();
+    method public androidx.camera.core.SurfaceProcessor? getSurfaceProcessor();
+    method public int getTargets();
+    field public static final int IMAGE_CAPTURE = 4; // 0x4
+    field public static final int PREVIEW = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 2; // 0x2
+  }
+
+  @RequiresApi(21) public interface CameraFilter {
+    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+  }
+
+  @RequiresApi(21) public interface CameraInfo {
+    method public androidx.camera.core.CameraSelector getCameraSelector();
+    method public androidx.lifecycle.LiveData<androidx.camera.core.CameraState!> getCameraState();
+    method public androidx.camera.core.ExposureState getExposureState();
+    method @FloatRange(from=0, fromInclusive=false) public default float getIntrinsicZoomRatio();
+    method public default int getLensFacing();
+    method public int getSensorRotationDegrees();
+    method public int getSensorRotationDegrees(int);
+    method public default java.util.Set<android.util.Range<java.lang.Integer!>!> getSupportedFrameRateRanges();
+    method public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+    method public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+    method public boolean hasFlashUnit();
+    method public default boolean isFocusMeteringSupported(androidx.camera.core.FocusMeteringAction);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public default boolean isZslSupported();
+    method public static boolean mustPlayShutterSound();
+    method public default java.util.Set<androidx.camera.core.DynamicRange!> querySupportedDynamicRanges(java.util.Set<androidx.camera.core.DynamicRange!>);
+  }
+
+  @RequiresApi(21) public final class CameraInfoUnavailableException extends java.lang.Exception {
+  }
+
+  @RequiresApi(21) public interface CameraProvider {
+    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+  }
+
+  @RequiresApi(21) public final class CameraSelector {
+    method public java.util.List<androidx.camera.core.CameraInfo!> filter(java.util.List<androidx.camera.core.CameraInfo!>);
+    field public static final androidx.camera.core.CameraSelector DEFAULT_BACK_CAMERA;
+    field public static final androidx.camera.core.CameraSelector DEFAULT_FRONT_CAMERA;
+    field public static final int LENS_FACING_BACK = 1; // 0x1
+    field @SuppressCompatibility @androidx.camera.core.ExperimentalLensFacing public static final int LENS_FACING_EXTERNAL = 2; // 0x2
+    field public static final int LENS_FACING_FRONT = 0; // 0x0
+    field public static final int LENS_FACING_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public static final class CameraSelector.Builder {
+    ctor public CameraSelector.Builder();
+    method public androidx.camera.core.CameraSelector.Builder addCameraFilter(androidx.camera.core.CameraFilter);
+    method public androidx.camera.core.CameraSelector build();
+    method public androidx.camera.core.CameraSelector.Builder requireLensFacing(int);
+  }
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class CameraState {
+    ctor public CameraState();
+    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type);
+    method public static androidx.camera.core.CameraState create(androidx.camera.core.CameraState.Type, androidx.camera.core.CameraState.StateError?);
+    method public abstract androidx.camera.core.CameraState.StateError? getError();
+    method public abstract androidx.camera.core.CameraState.Type getType();
+    field public static final int ERROR_CAMERA_DISABLED = 5; // 0x5
+    field public static final int ERROR_CAMERA_FATAL_ERROR = 6; // 0x6
+    field public static final int ERROR_CAMERA_IN_USE = 2; // 0x2
+    field public static final int ERROR_DO_NOT_DISTURB_MODE_ENABLED = 7; // 0x7
+    field public static final int ERROR_MAX_CAMERAS_IN_USE = 1; // 0x1
+    field public static final int ERROR_OTHER_RECOVERABLE_ERROR = 3; // 0x3
+    field public static final int ERROR_STREAM_CONFIG = 4; // 0x4
+  }
+
+  public enum CameraState.ErrorType {
+    enum_constant public static final androidx.camera.core.CameraState.ErrorType CRITICAL;
+    enum_constant public static final androidx.camera.core.CameraState.ErrorType RECOVERABLE;
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class CameraState.StateError {
+    ctor public CameraState.StateError();
+    method public static androidx.camera.core.CameraState.StateError create(int);
+    method public static androidx.camera.core.CameraState.StateError create(int, Throwable?);
+    method public abstract Throwable? getCause();
+    method public abstract int getCode();
+    method public androidx.camera.core.CameraState.ErrorType getType();
+  }
+
+  public enum CameraState.Type {
+    enum_constant public static final androidx.camera.core.CameraState.Type CLOSED;
+    enum_constant public static final androidx.camera.core.CameraState.Type CLOSING;
+    enum_constant public static final androidx.camera.core.CameraState.Type OPEN;
+    enum_constant public static final androidx.camera.core.CameraState.Type OPENING;
+    enum_constant public static final androidx.camera.core.CameraState.Type PENDING_OPEN;
+  }
+
+  @RequiresApi(21) public class CameraUnavailableException extends java.lang.Exception {
+    ctor public CameraUnavailableException(int);
+    ctor public CameraUnavailableException(int, String?);
+    ctor public CameraUnavailableException(int, String?, Throwable?);
+    ctor public CameraUnavailableException(int, Throwable?);
+    method public int getReason();
+    field public static final int CAMERA_DISABLED = 1; // 0x1
+    field public static final int CAMERA_DISCONNECTED = 2; // 0x2
+    field public static final int CAMERA_ERROR = 3; // 0x3
+    field public static final int CAMERA_IN_USE = 4; // 0x4
+    field public static final int CAMERA_MAX_IN_USE = 5; // 0x5
+    field public static final int CAMERA_UNAVAILABLE_DO_NOT_DISTURB = 6; // 0x6
+    field public static final int CAMERA_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @RequiresApi(21) public final class CameraXConfig {
+    method public androidx.camera.core.CameraSelector? getAvailableCamerasLimiter(androidx.camera.core.CameraSelector?);
+    method public java.util.concurrent.Executor? getCameraExecutor(java.util.concurrent.Executor?);
+    method public long getCameraOpenRetryMaxTimeoutInMillisWhileResuming();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.RetryPolicy getCameraProviderInitRetryPolicy();
+    method public int getMinimumLoggingLevel();
+    method public android.os.Handler? getSchedulerHandler(android.os.Handler?);
+  }
+
+  public static final class CameraXConfig.Builder {
+    method public androidx.camera.core.CameraXConfig build();
+    method public static androidx.camera.core.CameraXConfig.Builder fromConfig(androidx.camera.core.CameraXConfig);
+    method public androidx.camera.core.CameraXConfig.Builder setAvailableCamerasLimiter(androidx.camera.core.CameraSelector);
+    method public androidx.camera.core.CameraXConfig.Builder setCameraExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.CameraXConfig.Builder setCameraOpenRetryMaxTimeoutInMillisWhileResuming(long);
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public androidx.camera.core.CameraXConfig.Builder setCameraProviderInitRetryPolicy(androidx.camera.core.RetryPolicy);
+    method public androidx.camera.core.CameraXConfig.Builder setMinimumLoggingLevel(@IntRange(from=android.util.Log.DEBUG, to=android.util.Log.ERROR) int);
+    method public androidx.camera.core.CameraXConfig.Builder setSchedulerHandler(android.os.Handler);
+  }
+
+  public static interface CameraXConfig.Provider {
+    method public androidx.camera.core.CameraXConfig getCameraXConfig();
+  }
+
+  @RequiresApi(21) public class ConcurrentCamera {
+    ctor public ConcurrentCamera(java.util.List<androidx.camera.core.Camera!>);
+    method public java.util.List<androidx.camera.core.Camera!> getCameras();
+  }
+
+  public static final class ConcurrentCamera.SingleCameraConfig {
+    ctor public ConcurrentCamera.SingleCameraConfig(androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup, androidx.lifecycle.LifecycleOwner);
+    method public androidx.camera.core.CameraSelector getCameraSelector();
+    method public androidx.lifecycle.LifecycleOwner getLifecycleOwner();
+    method public androidx.camera.core.UseCaseGroup getUseCaseGroup();
+  }
+
+  @RequiresApi(21) public final class DisplayOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+    ctor public DisplayOrientedMeteringPointFactory(android.view.Display, androidx.camera.core.CameraInfo, float, float);
+  }
+
+  @RequiresApi(21) public final class DynamicRange {
+    ctor public DynamicRange(int, int);
+    method public int getBitDepth();
+    method public int getEncoding();
+    field public static final int BIT_DEPTH_10_BIT = 10; // 0xa
+    field public static final int BIT_DEPTH_8_BIT = 8; // 0x8
+    field public static final int BIT_DEPTH_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_10_BIT;
+    field public static final androidx.camera.core.DynamicRange DOLBY_VISION_8_BIT;
+    field public static final int ENCODING_DOLBY_VISION = 6; // 0x6
+    field public static final int ENCODING_HDR10 = 4; // 0x4
+    field public static final int ENCODING_HDR10_PLUS = 5; // 0x5
+    field public static final int ENCODING_HDR_UNSPECIFIED = 2; // 0x2
+    field public static final int ENCODING_HLG = 3; // 0x3
+    field public static final int ENCODING_SDR = 1; // 0x1
+    field public static final int ENCODING_UNSPECIFIED = 0; // 0x0
+    field public static final androidx.camera.core.DynamicRange HDR10_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR10_PLUS_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HDR_UNSPECIFIED_10_BIT;
+    field public static final androidx.camera.core.DynamicRange HLG_10_BIT;
+    field public static final androidx.camera.core.DynamicRange SDR;
+    field public static final androidx.camera.core.DynamicRange UNSPECIFIED;
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalGetImage {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalLensFacing {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalRetryPolicy {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalUseCaseApi {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalZeroShutterLag {
+  }
+
+  @RequiresApi(21) public interface ExposureState {
+    method public int getExposureCompensationIndex();
+    method public android.util.Range<java.lang.Integer!> getExposureCompensationRange();
+    method public android.util.Rational getExposureCompensationStep();
+    method public boolean isExposureCompensationSupported();
+  }
+
+  @RequiresApi(21) public interface ExtendableBuilder<T> {
+    method public T build();
+  }
+
+  @RequiresApi(21) public final class FocusMeteringAction {
+    method public long getAutoCancelDurationInMillis();
+    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAe();
+    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAf();
+    method public java.util.List<androidx.camera.core.MeteringPoint!> getMeteringPointsAwb();
+    method public boolean isAutoCancelEnabled();
+    field public static final int FLAG_AE = 2; // 0x2
+    field public static final int FLAG_AF = 1; // 0x1
+    field public static final int FLAG_AWB = 4; // 0x4
+  }
+
+  public static class FocusMeteringAction.Builder {
+    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint);
+    ctor public FocusMeteringAction.Builder(androidx.camera.core.MeteringPoint, int);
+    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint);
+    method public androidx.camera.core.FocusMeteringAction.Builder addPoint(androidx.camera.core.MeteringPoint, int);
+    method public androidx.camera.core.FocusMeteringAction build();
+    method public androidx.camera.core.FocusMeteringAction.Builder disableAutoCancel();
+    method public androidx.camera.core.FocusMeteringAction.Builder setAutoCancelDuration(@IntRange(from=1) long, java.util.concurrent.TimeUnit);
+  }
+
+  @RequiresApi(21) public final class FocusMeteringResult {
+    method public boolean isFocusSuccessful();
+  }
+
+  @RequiresApi(21) public final class ImageAnalysis extends androidx.camera.core.UseCase {
+    method public void clearAnalyzer();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalUseCaseApi public java.util.concurrent.Executor? getBackgroundExecutor();
+    method public int getBackpressureStrategy();
+    method public int getImageQueueDepth();
+    method public int getOutputImageFormat();
+    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public int getTargetRotation();
+    method public boolean isOutputImageRotationEnabled();
+    method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+    method public void setTargetRotation(int);
+    field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
+    field public static final int COORDINATE_SYSTEM_SENSOR = 2; // 0x2
+    field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
+    field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
+    field public static final int STRATEGY_BLOCK_PRODUCER = 1; // 0x1
+    field public static final int STRATEGY_KEEP_ONLY_LATEST = 0; // 0x0
+  }
+
+  public static interface ImageAnalysis.Analyzer {
+    method public void analyze(androidx.camera.core.ImageProxy);
+    method public default android.util.Size? getDefaultTargetResolution();
+    method public default int getTargetCoordinateSystem();
+    method public default void updateTransform(android.graphics.Matrix?);
+  }
+
+  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis!> {
+    ctor public ImageAnalysis.Builder();
+    method public androidx.camera.core.ImageAnalysis build();
+    method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageAnalysis.Builder setBackpressureStrategy(int);
+    method public androidx.camera.core.ImageAnalysis.Builder setImageQueueDepth(int);
+    method public androidx.camera.core.ImageAnalysis.Builder setOutputImageFormat(int);
+    method @RequiresApi(23) public androidx.camera.core.ImageAnalysis.Builder setOutputImageRotationEnabled(boolean);
+    method public androidx.camera.core.ImageAnalysis.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.ImageAnalysis.Builder setTargetName(String);
+    method @Deprecated public androidx.camera.core.ImageAnalysis.Builder setTargetResolution(android.util.Size);
+    method public androidx.camera.core.ImageAnalysis.Builder setTargetRotation(int);
+  }
+
+  @RequiresApi(21) public final class ImageCapture extends androidx.camera.core.UseCase {
+    method public int getCaptureMode();
+    method public int getFlashMode();
+    method public static androidx.camera.core.ImageCaptureCapabilities getImageCaptureCapabilities(androidx.camera.core.CameraInfo);
+    method @IntRange(from=1, to=100) public int getJpegQuality();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector? getPostviewResolutionSelector();
+    method public androidx.camera.core.ImageCaptureLatencyEstimate getRealtimeCaptureLatencyEstimate();
+    method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector? getResolutionSelector();
+    method public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+    method public int getTargetRotation();
+    method public boolean isPostviewEnabled();
+    method public void setCropAspectRatio(android.util.Rational);
+    method public void setFlashMode(int);
+    method public void setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash?);
+    method public void setTargetRotation(int);
+    method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+    method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+    field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
+    field public static final int CAPTURE_MODE_MINIMIZE_LATENCY = 1; // 0x1
+    field @SuppressCompatibility @androidx.camera.core.ExperimentalZeroShutterLag public static final int CAPTURE_MODE_ZERO_SHUTTER_LAG = 2; // 0x2
+    field public static final int ERROR_CAMERA_CLOSED = 3; // 0x3
+    field public static final int ERROR_CAPTURE_FAILED = 2; // 0x2
+    field public static final int ERROR_FILE_IO = 1; // 0x1
+    field public static final int ERROR_INVALID_CAMERA = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+    field public static final int FLASH_MODE_AUTO = 0; // 0x0
+    field public static final int FLASH_MODE_OFF = 2; // 0x2
+    field public static final int FLASH_MODE_ON = 1; // 0x1
+    field public static final int FLASH_MODE_SCREEN = 3; // 0x3
+  }
+
+  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture!> {
+    ctor public ImageCapture.Builder();
+    method public androidx.camera.core.ImageCapture build();
+    method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
+    method public androidx.camera.core.ImageCapture.Builder setFlashMode(int);
+    method public androidx.camera.core.ImageCapture.Builder setIoExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.core.ImageCapture.Builder setJpegQuality(@IntRange(from=1, to=100) int);
+    method public androidx.camera.core.ImageCapture.Builder setPostviewEnabled(boolean);
+    method public androidx.camera.core.ImageCapture.Builder setPostviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method public androidx.camera.core.ImageCapture.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method public androidx.camera.core.ImageCapture.Builder setScreenFlash(androidx.camera.core.ImageCapture.ScreenFlash);
+    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.ImageCapture.Builder setTargetName(String);
+    method @Deprecated public androidx.camera.core.ImageCapture.Builder setTargetResolution(android.util.Size);
+    method public androidx.camera.core.ImageCapture.Builder setTargetRotation(int);
+  }
+
+  public static final class ImageCapture.Metadata {
+    ctor public ImageCapture.Metadata();
+    method public android.location.Location? getLocation();
+    method public boolean isReversedHorizontal();
+    method public boolean isReversedVertical();
+    method public void setLocation(android.location.Location?);
+    method public void setReversedHorizontal(boolean);
+    method public void setReversedVertical(boolean);
+  }
+
+  public abstract static class ImageCapture.OnImageCapturedCallback {
+    ctor public ImageCapture.OnImageCapturedCallback();
+    method public void onCaptureProcessProgressed(int);
+    method public void onCaptureStarted();
+    method public void onCaptureSuccess(androidx.camera.core.ImageProxy);
+    method public void onError(androidx.camera.core.ImageCaptureException);
+    method public void onPostviewBitmapAvailable(android.graphics.Bitmap);
+  }
+
+  public static interface ImageCapture.OnImageSavedCallback {
+    method public default void onCaptureProcessProgressed(int);
+    method public default void onCaptureStarted();
+    method public void onError(androidx.camera.core.ImageCaptureException);
+    method public void onImageSaved(androidx.camera.core.ImageCapture.OutputFileResults);
+    method public default void onPostviewBitmapAvailable(android.graphics.Bitmap);
+  }
+
+  public static final class ImageCapture.OutputFileOptions {
+  }
+
+  public static final class ImageCapture.OutputFileOptions.Builder {
+    ctor public ImageCapture.OutputFileOptions.Builder(android.content.ContentResolver, android.net.Uri, android.content.ContentValues);
+    ctor public ImageCapture.OutputFileOptions.Builder(java.io.File);
+    ctor public ImageCapture.OutputFileOptions.Builder(java.io.OutputStream);
+    method public androidx.camera.core.ImageCapture.OutputFileOptions build();
+    method public androidx.camera.core.ImageCapture.OutputFileOptions.Builder setMetadata(androidx.camera.core.ImageCapture.Metadata);
+  }
+
+  public static class ImageCapture.OutputFileResults {
+    method public android.net.Uri? getSavedUri();
+  }
+
+  public static interface ImageCapture.ScreenFlash {
+    method @UiThread public void apply(long, androidx.camera.core.ImageCapture.ScreenFlashListener);
+    method @UiThread public void clear();
+  }
+
+  public static interface ImageCapture.ScreenFlashListener {
+    method public void onCompleted();
+  }
+
+  public interface ImageCaptureCapabilities {
+    method public boolean isCaptureProcessProgressSupported();
+    method public boolean isPostviewSupported();
+  }
+
+  @RequiresApi(21) public class ImageCaptureException extends java.lang.Exception {
+    ctor public ImageCaptureException(int, String, Throwable?);
+    method public int getImageCaptureError();
+  }
+
+  @RequiresApi(21) public class ImageCaptureLatencyEstimate {
+    ctor public ImageCaptureLatencyEstimate(long, long);
+    method public long getCaptureLatencyMillis();
+    method public long getProcessingLatencyMillis();
+    method public long getTotalCaptureLatencyMillis();
+    field public static final long UNDEFINED_CAPTURE_LATENCY = -1L; // 0xffffffffffffffffL
+    field public static final androidx.camera.core.ImageCaptureLatencyEstimate UNDEFINED_IMAGE_CAPTURE_LATENCY;
+    field public static final long UNDEFINED_PROCESSING_LATENCY = -1L; // 0xffffffffffffffffL
+  }
+
+  @RequiresApi(21) public interface ImageInfo {
+    method public int getRotationDegrees();
+    method public default android.graphics.Matrix getSensorToBufferTransformMatrix();
+    method public long getTimestamp();
+  }
+
+  public interface ImageProcessor {
+    method public androidx.camera.core.ImageProcessor.Response process(androidx.camera.core.ImageProcessor.Request) throws androidx.camera.core.ProcessingException;
+  }
+
+  public static interface ImageProcessor.Request {
+    method public androidx.camera.core.ImageProxy getInputImage();
+    method public int getOutputFormat();
+  }
+
+  public static interface ImageProcessor.Response {
+    method public androidx.camera.core.ImageProxy getOutputImage();
+  }
+
+  @RequiresApi(21) public interface ImageProxy extends java.lang.AutoCloseable {
+    method public void close();
+    method public android.graphics.Rect getCropRect();
+    method public int getFormat();
+    method public int getHeight();
+    method @SuppressCompatibility @androidx.camera.core.ExperimentalGetImage public android.media.Image? getImage();
+    method public androidx.camera.core.ImageInfo getImageInfo();
+    method public androidx.camera.core.ImageProxy.PlaneProxy![] getPlanes();
+    method public int getWidth();
+    method public void setCropRect(android.graphics.Rect?);
+    method public default android.graphics.Bitmap toBitmap();
+  }
+
+  public static interface ImageProxy.PlaneProxy {
+    method public java.nio.ByteBuffer getBuffer();
+    method public int getPixelStride();
+    method public int getRowStride();
+  }
+
+  @RequiresApi(21) public class InitializationException extends java.lang.Exception {
+    ctor public InitializationException(String?);
+    ctor public InitializationException(String?, Throwable?);
+    ctor public InitializationException(Throwable?);
+  }
+
+  @RequiresApi(21) public class MeteringPoint {
+    method public float getSize();
+  }
+
+  @RequiresApi(21) public abstract class MeteringPointFactory {
+    method public final androidx.camera.core.MeteringPoint createPoint(float, float);
+    method public final androidx.camera.core.MeteringPoint createPoint(float, float, float);
+    method public static float getDefaultPointSize();
+  }
+
+  @RequiresApi(21) public class MirrorMode {
+    field public static final int MIRROR_MODE_OFF = 0; // 0x0
+    field public static final int MIRROR_MODE_ON = 1; // 0x1
+    field public static final int MIRROR_MODE_ON_FRONT_ONLY = 2; // 0x2
+  }
+
+  @RequiresApi(21) public final class Preview extends androidx.camera.core.UseCase {
+    method public androidx.camera.core.DynamicRange getDynamicRange();
+    method public static androidx.camera.core.PreviewCapabilities getPreviewCapabilities(androidx.camera.core.CameraInfo);
+    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 public boolean isPreviewStabilizationEnabled();
+    method @UiThread public void setSurfaceProvider(androidx.camera.core.Preview.SurfaceProvider?);
+    method @UiThread public void setSurfaceProvider(java.util.concurrent.Executor, androidx.camera.core.Preview.SurfaceProvider?);
+    method public void setTargetRotation(int);
+  }
+
+  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview!> {
+    ctor public Preview.Builder();
+    method public androidx.camera.core.Preview build();
+    method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
+    method public androidx.camera.core.Preview.Builder setPreviewStabilizationEnabled(boolean);
+    method public androidx.camera.core.Preview.Builder setResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector);
+    method @Deprecated public androidx.camera.core.Preview.Builder setTargetAspectRatio(int);
+    method public androidx.camera.core.Preview.Builder setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+    method public androidx.camera.core.Preview.Builder setTargetName(String);
+    method @Deprecated public androidx.camera.core.Preview.Builder setTargetResolution(android.util.Size);
+    method public androidx.camera.core.Preview.Builder setTargetRotation(int);
+  }
+
+  public static interface Preview.SurfaceProvider {
+    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+  }
+
+  @RequiresApi(21) public interface PreviewCapabilities {
+    method public boolean isStabilizationSupported();
+  }
+
+  public class ProcessingException extends java.lang.Exception {
+    ctor public ProcessingException();
+  }
+
+  @RequiresApi(21) public class ResolutionInfo {
+    ctor public ResolutionInfo(android.util.Size, android.graphics.Rect, int);
+    method public android.graphics.Rect getCropRect();
+    method public android.util.Size getResolution();
+    method public int getRotationDegrees();
+  }
+
+  @SuppressCompatibility @RequiresApi(21) @androidx.camera.core.ExperimentalRetryPolicy public interface RetryPolicy {
+    method public static long getDefaultRetryTimeoutInMillis();
+    method public default long getTimeoutInMillis();
+    method public androidx.camera.core.RetryPolicy.RetryConfig onRetryDecisionRequested(androidx.camera.core.RetryPolicy.ExecutionState);
+    field public static final androidx.camera.core.RetryPolicy DEFAULT;
+    field public static final androidx.camera.core.RetryPolicy NEVER;
+    field public static final androidx.camera.core.RetryPolicy RETRY_UNAVAILABLE_CAMERA;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.Builder {
+    ctor public RetryPolicy.Builder(androidx.camera.core.RetryPolicy);
+    method public androidx.camera.core.RetryPolicy build();
+    method public androidx.camera.core.RetryPolicy.Builder setTimeoutInMillis(long);
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static interface RetryPolicy.ExecutionState {
+    method public Throwable? getCause();
+    method public long getExecutedTimeInMillis();
+    method public int getNumOfAttempts();
+    method public int getStatus();
+    field public static final int STATUS_CAMERA_UNAVAILABLE = 2; // 0x2
+    field public static final int STATUS_CONFIGURATION_FAIL = 1; // 0x1
+    field public static final int STATUS_UNKNOWN_ERROR = 0; // 0x0
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig {
+    method public static long getDefaultRetryDelayInMillis();
+    method public long getRetryDelayInMillis();
+    method public boolean shouldRetry();
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig DEFAULT_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig MINI_DELAY_RETRY;
+    field public static final androidx.camera.core.RetryPolicy.RetryConfig NOT_RETRY;
+  }
+
+  @SuppressCompatibility @androidx.camera.core.ExperimentalRetryPolicy public static final class RetryPolicy.RetryConfig.Builder {
+    ctor public RetryPolicy.RetryConfig.Builder();
+    method public androidx.camera.core.RetryPolicy.RetryConfig build();
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setRetryDelayInMillis(@IntRange(from=100, to=2000) long);
+    method public androidx.camera.core.RetryPolicy.RetryConfig.Builder setShouldRetry(boolean);
+  }
+
+  @RequiresApi(21) public class SurfaceOrientedMeteringPointFactory extends androidx.camera.core.MeteringPointFactory {
+    ctor public SurfaceOrientedMeteringPointFactory(float, float);
+    ctor public SurfaceOrientedMeteringPointFactory(float, float, androidx.camera.core.UseCase);
+  }
+
+  public interface SurfaceOutput extends java.io.Closeable {
+    method public void close();
+    method public default android.graphics.Matrix getSensorToBufferTransform();
+    method public android.util.Size getSize();
+    method public android.view.Surface getSurface(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceOutput.Event!>);
+    method public int getTargets();
+    method public void updateTransformMatrix(float[], float[]);
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class SurfaceOutput.Event {
+    method public abstract int getEventCode();
+    method public abstract androidx.camera.core.SurfaceOutput getSurfaceOutput();
+    field public static final int EVENT_REQUEST_CLOSE = 0; // 0x0
+  }
+
+  public interface SurfaceProcessor {
+    method public void onInputSurface(androidx.camera.core.SurfaceRequest) throws androidx.camera.core.ProcessingException;
+    method public void onOutputSurface(androidx.camera.core.SurfaceOutput) throws androidx.camera.core.ProcessingException;
+  }
+
+  @RequiresApi(21) public final class SurfaceRequest {
+    method public void addRequestCancellationListener(java.util.concurrent.Executor, Runnable);
+    method public void clearTransformationInfoListener();
+    method public androidx.camera.core.DynamicRange getDynamicRange();
+    method public android.util.Size getResolution();
+    method public boolean invalidate();
+    method public void provideSurface(android.view.Surface, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.core.SurfaceRequest.Result!>);
+    method public void setTransformationInfoListener(java.util.concurrent.Executor, androidx.camera.core.SurfaceRequest.TransformationInfoListener);
+    method public boolean willNotProvideSurface();
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.Result {
+    method public abstract int getResultCode();
+    method public abstract android.view.Surface getSurface();
+    field public static final int RESULT_INVALID_SURFACE = 2; // 0x2
+    field public static final int RESULT_REQUEST_CANCELLED = 1; // 0x1
+    field public static final int RESULT_SURFACE_ALREADY_PROVIDED = 3; // 0x3
+    field public static final int RESULT_SURFACE_USED_SUCCESSFULLY = 0; // 0x0
+    field public static final int RESULT_WILL_NOT_PROVIDE_SURFACE = 4; // 0x4
+  }
+
+  @com.google.auto.value.AutoValue public abstract static class SurfaceRequest.TransformationInfo {
+    method public abstract android.graphics.Rect getCropRect();
+    method public abstract int getRotationDegrees();
+    method public abstract android.graphics.Matrix getSensorToBufferTransform();
+    method public abstract boolean hasCameraTransform();
+    method public abstract boolean isMirroring();
+  }
+
+  public static interface SurfaceRequest.TransformationInfoListener {
+    method public void onTransformationInfoUpdate(androidx.camera.core.SurfaceRequest.TransformationInfo);
+  }
+
+  @RequiresApi(21) public class TorchState {
+    field public static final int OFF = 0; // 0x0
+    field public static final int ON = 1; // 0x1
+  }
+
+  @RequiresApi(21) public abstract class UseCase {
+    method public static int snapToSurfaceRotation(@IntRange(from=0, to=359) int);
+  }
+
+  @RequiresApi(21) public final class UseCaseGroup {
+    method public java.util.List<androidx.camera.core.CameraEffect!> getEffects();
+    method public java.util.List<androidx.camera.core.UseCase!> getUseCases();
+    method public androidx.camera.core.ViewPort? getViewPort();
+  }
+
+  public static final class UseCaseGroup.Builder {
+    ctor public UseCaseGroup.Builder();
+    method public androidx.camera.core.UseCaseGroup.Builder addEffect(androidx.camera.core.CameraEffect);
+    method public androidx.camera.core.UseCaseGroup.Builder addUseCase(androidx.camera.core.UseCase);
+    method public androidx.camera.core.UseCaseGroup build();
+    method public androidx.camera.core.UseCaseGroup.Builder setViewPort(androidx.camera.core.ViewPort);
+  }
+
+  @RequiresApi(21) public final class ViewPort {
+    method public android.util.Rational getAspectRatio();
+    method public int getLayoutDirection();
+    method public int getRotation();
+    method public int getScaleType();
+    field public static final int FILL_CENTER = 1; // 0x1
+    field public static final int FILL_END = 2; // 0x2
+    field public static final int FILL_START = 0; // 0x0
+    field public static final int FIT = 3; // 0x3
+  }
+
+  public static final class ViewPort.Builder {
+    ctor public ViewPort.Builder(android.util.Rational, int);
+    method public androidx.camera.core.ViewPort build();
+    method public androidx.camera.core.ViewPort.Builder setLayoutDirection(int);
+    method public androidx.camera.core.ViewPort.Builder setScaleType(int);
+  }
+
+  @RequiresApi(21) public interface ZoomState {
+    method public float getLinearZoom();
+    method public float getMaxZoomRatio();
+    method public float getMinZoomRatio();
+    method public float getZoomRatio();
+  }
+
+}
+
+package androidx.camera.core.resolutionselector {
+
+  @RequiresApi(21) public final class AspectRatioStrategy {
+    ctor public AspectRatioStrategy(int, int);
+    method public int getFallbackRule();
+    method public int getPreferredAspectRatio();
+    field public static final int FALLBACK_RULE_AUTO = 1; // 0x1
+    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_16_9_FALLBACK_AUTO_STRATEGY;
+    field public static final androidx.camera.core.resolutionselector.AspectRatioStrategy RATIO_4_3_FALLBACK_AUTO_STRATEGY;
+  }
+
+  @RequiresApi(21) public interface ResolutionFilter {
+    method public java.util.List<android.util.Size!> filter(java.util.List<android.util.Size!>, int);
+  }
+
+  @RequiresApi(21) public final class ResolutionSelector {
+    method public int getAllowedResolutionMode();
+    method public androidx.camera.core.resolutionselector.AspectRatioStrategy getAspectRatioStrategy();
+    method public androidx.camera.core.resolutionselector.ResolutionFilter? getResolutionFilter();
+    method public androidx.camera.core.resolutionselector.ResolutionStrategy? getResolutionStrategy();
+    field public static final int PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION = 0; // 0x0
+    field public static final int PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE = 1; // 0x1
+  }
+
+  public static final class ResolutionSelector.Builder {
+    ctor public ResolutionSelector.Builder();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector build();
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAllowedResolutionMode(int);
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setAspectRatioStrategy(androidx.camera.core.resolutionselector.AspectRatioStrategy);
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter);
+    method public androidx.camera.core.resolutionselector.ResolutionSelector.Builder setResolutionStrategy(androidx.camera.core.resolutionselector.ResolutionStrategy);
+  }
+
+  @RequiresApi(21) public final class ResolutionStrategy {
+    ctor public ResolutionStrategy(android.util.Size, int);
+    method public android.util.Size? getBoundSize();
+    method public int getFallbackRule();
+    field public static final int FALLBACK_RULE_CLOSEST_HIGHER = 2; // 0x2
+    field public static final int FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER = 1; // 0x1
+    field public static final int FALLBACK_RULE_CLOSEST_LOWER = 4; // 0x4
+    field public static final int FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER = 3; // 0x3
+    field public static final int FALLBACK_RULE_NONE = 0; // 0x0
+    field public static final androidx.camera.core.resolutionselector.ResolutionStrategy HIGHEST_AVAILABLE_STRATEGY;
+  }
+
+}
+
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index 2a567c8..abd42142 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -277,7 +277,7 @@
     method public default void updateTransform(android.graphics.Matrix?);
   }
 
-  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis> {
+  public static final class ImageAnalysis.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageAnalysis!> {
     ctor public ImageAnalysis.Builder();
     method public androidx.camera.core.ImageAnalysis build();
     method public androidx.camera.core.ImageAnalysis.Builder setBackgroundExecutor(java.util.concurrent.Executor);
@@ -324,7 +324,7 @@
     field public static final int FLASH_MODE_SCREEN = 3; // 0x3
   }
 
-  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture> {
+  public static final class ImageCapture.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.ImageCapture!> {
     ctor public ImageCapture.Builder();
     method public androidx.camera.core.ImageCapture build();
     method public androidx.camera.core.ImageCapture.Builder setCaptureMode(int);
@@ -485,7 +485,7 @@
     method public void setTargetRotation(int);
   }
 
-  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview> {
+  public static final class Preview.Builder implements androidx.camera.core.ExtendableBuilder<androidx.camera.core.Preview!> {
     ctor public Preview.Builder();
     method public androidx.camera.core.Preview build();
     method public androidx.camera.core.Preview.Builder setDynamicRange(androidx.camera.core.DynamicRange);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
index 7d71a4f..846b3a8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
@@ -330,6 +330,18 @@
     }
 
     /**
+     * Returns if logical multi camera is supported on the device.
+     *
+     * @return true if supported, otherwise false.
+     * @see android.hardware.camera2.CameraMetadata
+     * #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    default boolean isLogicalMultiCameraSupported() {
+        return false;
+    }
+
+    /**
      * Returns if {@link ImageFormat#PRIVATE} reprocessing is supported on the device.
      *
      * @return true if supported, otherwise false.
@@ -405,6 +417,17 @@
                 Collections.singleton(DynamicRange.SDR));
     }
 
+    /**
+     * Returns a set of {@link PhysicalCameraInfo}.
+     *
+     * @return Set of {@link PhysicalCameraInfo}.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    default Set<PhysicalCameraInfo> getPhysicalCameraInfos() {
+        return Collections.emptySet();
+    }
+
     @StringDef(open = true, value = {IMPLEMENTATION_TYPE_UNKNOWN,
             IMPLEMENTATION_TYPE_CAMERA2_LEGACY, IMPLEMENTATION_TYPE_CAMERA2,
             IMPLEMENTATION_TYPE_FAKE})
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java
index 240375f..ca80c82 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraSelector.java
@@ -66,10 +66,16 @@
     public static final CameraSelector DEFAULT_BACK_CAMERA =
             new CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build();
 
-    private LinkedHashSet<CameraFilter> mCameraFilterSet;
+    @NonNull
+    private final LinkedHashSet<CameraFilter> mCameraFilterSet;
 
-    CameraSelector(LinkedHashSet<CameraFilter> cameraFilterSet) {
+    @Nullable
+    private final String mPhysicalCameraId;
+
+    CameraSelector(@NonNull LinkedHashSet<CameraFilter> cameraFilterSet,
+            @Nullable String physicalCameraId) {
         mCameraFilterSet = cameraFilterSet;
+        mPhysicalCameraId = physicalCameraId;
     }
 
     /**
@@ -204,10 +210,25 @@
         return currentLensFacing;
     }
 
+    /**
+     * Returns the physical camera id.
+     *
+     * @return physical camera id.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @Nullable
+    public String getPhysicalCameraId() {
+        return mPhysicalCameraId;
+    }
+
     /** Builder for a {@link CameraSelector}. */
     public static final class Builder {
+        @NonNull
         private final LinkedHashSet<CameraFilter> mCameraFilterSet;
 
+        @Nullable
+        private String mPhysicalCameraId;
+
         public Builder() {
             mCameraFilterSet = new LinkedHashSet<>();
         }
@@ -270,10 +291,23 @@
             return builder;
         }
 
+        /**
+         * Sets the physical camera id.
+         *
+         * @param physicalCameraId physical camera id.
+         * @return this builder.
+         */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public Builder setPhysicalCameraId(@NonNull String physicalCameraId) {
+            mPhysicalCameraId = physicalCameraId;
+            return this;
+        }
+
         /** Builds the {@link CameraSelector}. */
         @NonNull
         public CameraSelector build() {
-            return new CameraSelector(mCameraFilterSet);
+            return new CameraSelector(mCameraFilterSet, mPhysicalCameraId);
         }
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index ed8b596..346b571 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -392,7 +392,7 @@
 
         sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange());
 
-        sessionConfigBuilder.addSurface(mDeferrableSurface, streamSpec.getDynamicRange());
+        sessionConfigBuilder.addSurface(mDeferrableSurface, streamSpec.getDynamicRange(), null);
 
         sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
             clearPipeline();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/PhysicalCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/PhysicalCameraInfo.java
new file mode 100644
index 0000000..1ab2035e
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/PhysicalCameraInfo.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+/**
+ * An interface for retrieving physical camera information.
+ *
+ * <p>Applications can retrieve physical camera information via
+ * {@link CameraInfo#getPhysicalCameraInfos()}. As a comparison, {@link CameraInfo} represents
+ * logical camera information. A logical camera is a grouping of two or more of those physical
+ * cameras.
+ *
+ * <p>See <a href="https://developer.android.com/media/camera/camera2/multi-camera">Multi-camera API</a>
+ * for more information.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21)
+public interface PhysicalCameraInfo {
+
+    /**
+     * Returns physical camera id.
+     *
+     * @return physical camera id.
+     */
+    @NonNull
+    String getPhysicalCameraId();
+
+    /**
+     * Returns {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_REFERENCE}.
+     *
+     * @return lens pose reference.
+     */
+    @RequiresApi(28)
+    @NonNull
+    Integer getLensPoseReference();
+}
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 02d910b..77899224 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
@@ -332,7 +332,8 @@
         // output target for these two cases.
         if (mSurfaceProvider != null) {
             sessionConfigBuilder.addSurface(mSessionDeferrableSurface,
-                    streamSpec.getDynamicRange());
+                    streamSpec.getDynamicRange(),
+                    getPhysicalCameraId());
         }
 
         sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
index bd07576..8bd0481 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
@@ -152,6 +152,9 @@
     @Nullable
     private CameraEffect mEffect;
 
+    @Nullable
+    private String mPhysicalCameraId;
+
     ////////////////////////////////////////////////////////////////////////////////////////////
     // [UseCase attached dynamic] - Can change but is only available when the UseCase is attached.
     ////////////////////////////////////////////////////////////////////////////////////////////
@@ -362,6 +365,17 @@
         }
     }
 
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public void setPhysicalCameraId(@NonNull String physicalCameraId) {
+        mPhysicalCameraId = physicalCameraId;
+    }
+
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @Nullable
+    public String getPhysicalCameraId() {
+        return mPhysicalCameraId;
+    }
+
     /**
      * Updates the target rotation of the use case config.
      *
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
index 1046b45..6c3cf5d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
@@ -323,10 +323,16 @@
         // prematurely before it can be used by camera2.
         inputEdge.getSurface().getTerminationFuture().addListener(() -> {
             imageReader.safeClose();
-            if (imageReaderForPostview != null) {
-                imageReaderForPostview.safeClose();
-            }
         }, mainThreadExecutor());
+
+        if (inputEdge.getPostviewSurface() != null) {
+            inputEdge.getPostviewSurface().close();
+            inputEdge.getPostviewSurface().getTerminationFuture().addListener(() -> {
+                if (imageReaderForPostview != null) {
+                    imageReaderForPostview.safeClose();
+                }
+            }, mainThreadExecutor());
+        }
     }
 
     @VisibleForTesting
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
index 7d101c05..cbdfa54 100644
--- 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
@@ -28,6 +28,7 @@
 import androidx.camera.core.ExperimentalZeroShutterLag;
 import androidx.camera.core.ExposureState;
 import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.PhysicalCameraInfo;
 import androidx.camera.core.ZoomState;
 import androidx.lifecycle.LiveData;
 
@@ -129,6 +130,11 @@
         return mCameraInfoInternal.isPrivateReprocessingSupported();
     }
 
+    @Override
+    public boolean isLogicalMultiCameraSupported() {
+        return mCameraInfoInternal.isLogicalMultiCameraSupported();
+    }
+
     @NonNull
     @Override
     public String getCameraId() {
@@ -228,4 +234,10 @@
     public Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId) {
         return mCameraInfoInternal.getPhysicalCameraCharacteristics(physicalCameraId);
     }
+
+    @NonNull
+    @Override
+    public Set<PhysicalCameraInfo> getPhysicalCameraInfos() {
+        return mCameraInfoInternal.getPhysicalCameraInfos();
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
index 63bdf6d..414ddb0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
@@ -647,7 +647,7 @@
          */
         @NonNull
         public Builder addSurface(@NonNull DeferrableSurface surface) {
-            return addSurface(surface, DynamicRange.SDR);
+            return addSurface(surface, DynamicRange.SDR, null);
         }
 
         /**
@@ -656,8 +656,10 @@
          */
         @NonNull
         public Builder addSurface(@NonNull DeferrableSurface surface,
-                @NonNull DynamicRange dynamicRange) {
+                @NonNull DynamicRange dynamicRange,
+                @Nullable String physicalCameraId) {
             OutputConfig outputConfig = OutputConfig.builder(surface)
+                    .setPhysicalCameraId(physicalCameraId)
                     .setDynamicRange(dynamicRange)
                     .build();
             mOutputConfigs.add(outputConfig);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index 687a41b..9844499 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -278,7 +278,7 @@
 
         propagateChildrenCamera2Interop(streamSpec.getResolution(), builder);
 
-        builder.addSurface(mCameraEdge.getDeferrableSurface(), streamSpec.getDynamicRange());
+        builder.addSurface(mCameraEdge.getDeferrableSurface(), streamSpec.getDynamicRange(), null);
         builder.addRepeatingCameraCaptureCallback(
                 mVirtualCameraAdapter.getParentMetadataCallback());
         if (streamSpec.getImplementationOptions() != null) {
diff --git a/camera/camera-effects-still-portrait/api/1.4.0-beta01.txt b/camera/camera-effects-still-portrait/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects-still-portrait/api/1.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects-still-portrait/api/res-1.4.0-beta01.txt b/camera/camera-effects-still-portrait/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-effects-still-portrait/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-effects-still-portrait/api/restricted_1.4.0-beta01.txt b/camera/camera-effects-still-portrait/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/camera/camera-effects-still-portrait/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/camera/camera-effects/api/1.4.0-beta01.txt b/camera/camera-effects/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..e47a913
--- /dev/null
+++ b/camera/camera-effects/api/1.4.0-beta01.txt
@@ -0,0 +1,30 @@
+// Signature format: 4.0
+package androidx.camera.effects {
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class Frame {
+    ctor public Frame();
+    method public abstract android.graphics.Rect getCropRect();
+    method public android.graphics.Canvas getOverlayCanvas();
+    method @IntRange(from=0, to=359) public abstract int getRotationDegrees();
+    method public abstract android.graphics.Matrix getSensorToBufferTransform();
+    method public abstract android.util.Size getSize();
+    method public abstract long getTimestampNanos();
+    method public abstract boolean isMirroring();
+  }
+
+  @RequiresApi(21) public class OverlayEffect extends androidx.camera.core.CameraEffect implements java.lang.AutoCloseable {
+    ctor public OverlayEffect(int, int, android.os.Handler, androidx.core.util.Consumer<java.lang.Throwable!>);
+    method public void clearOnDrawListener();
+    method public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> drawFrameAsync(long);
+    method public android.os.Handler getHandler();
+    method public int getQueueDepth();
+    method public void setOnDrawListener(androidx.arch.core.util.Function<androidx.camera.effects.Frame!,java.lang.Boolean!>);
+    field public static final int RESULT_CANCELLED_BY_CALLER = 4; // 0x4
+    field public static final int RESULT_FRAME_NOT_FOUND = 2; // 0x2
+    field public static final int RESULT_INVALID_SURFACE = 3; // 0x3
+    field public static final int RESULT_SUCCESS = 1; // 0x1
+  }
+
+}
+
diff --git a/camera/camera-effects/api/res-1.4.0-beta01.txt b/camera/camera-effects/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-effects/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-effects/api/restricted_1.4.0-beta01.txt b/camera/camera-effects/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..e47a913
--- /dev/null
+++ b/camera/camera-effects/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,30 @@
+// Signature format: 4.0
+package androidx.camera.effects {
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class Frame {
+    ctor public Frame();
+    method public abstract android.graphics.Rect getCropRect();
+    method public android.graphics.Canvas getOverlayCanvas();
+    method @IntRange(from=0, to=359) public abstract int getRotationDegrees();
+    method public abstract android.graphics.Matrix getSensorToBufferTransform();
+    method public abstract android.util.Size getSize();
+    method public abstract long getTimestampNanos();
+    method public abstract boolean isMirroring();
+  }
+
+  @RequiresApi(21) public class OverlayEffect extends androidx.camera.core.CameraEffect implements java.lang.AutoCloseable {
+    ctor public OverlayEffect(int, int, android.os.Handler, androidx.core.util.Consumer<java.lang.Throwable!>);
+    method public void clearOnDrawListener();
+    method public void close();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Integer!> drawFrameAsync(long);
+    method public android.os.Handler getHandler();
+    method public int getQueueDepth();
+    method public void setOnDrawListener(androidx.arch.core.util.Function<androidx.camera.effects.Frame!,java.lang.Boolean!>);
+    field public static final int RESULT_CANCELLED_BY_CALLER = 4; // 0x4
+    field public static final int RESULT_FRAME_NOT_FOUND = 2; // 0x2
+    field public static final int RESULT_INVALID_SURFACE = 3; // 0x3
+    field public static final int RESULT_SUCCESS = 1; // 0x1
+  }
+
+}
+
diff --git a/camera/camera-extensions/api/1.4.0-beta01.txt b/camera/camera-extensions/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..f365e3d
--- /dev/null
+++ b/camera/camera-extensions/api/1.4.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.camera.extensions {
+
+  public interface CameraExtensionsControl {
+    method public default void setExtensionStrength(@IntRange(from=0, to=100) int);
+  }
+
+  public interface CameraExtensionsInfo {
+    method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getCurrentExtensionMode();
+    method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getExtensionStrength();
+    method public default boolean isCurrentExtensionModeAvailable();
+    method public default boolean isExtensionStrengthAvailable();
+  }
+
+  @RequiresApi(21) public final class ExtensionMode {
+    field public static final int AUTO = 5; // 0x5
+    field public static final int BOKEH = 1; // 0x1
+    field public static final int FACE_RETOUCH = 4; // 0x4
+    field public static final int HDR = 2; // 0x2
+    field public static final int NIGHT = 3; // 0x3
+    field public static final int NONE = 0; // 0x0
+  }
+
+  @RequiresApi(21) public final class ExtensionsManager {
+    method public androidx.camera.extensions.CameraExtensionsControl? getCameraExtensionsControl(androidx.camera.core.CameraControl);
+    method public androidx.camera.extensions.CameraExtensionsInfo getCameraExtensionsInfo(androidx.camera.core.CameraInfo);
+    method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
+    method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
+    method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
+    method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
+  }
+
+}
+
diff --git a/camera/camera-extensions/api/res-1.4.0-beta01.txt b/camera/camera-extensions/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-extensions/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-extensions/api/restricted_1.4.0-beta01.txt b/camera/camera-extensions/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..f365e3d
--- /dev/null
+++ b/camera/camera-extensions/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,35 @@
+// Signature format: 4.0
+package androidx.camera.extensions {
+
+  public interface CameraExtensionsControl {
+    method public default void setExtensionStrength(@IntRange(from=0, to=100) int);
+  }
+
+  public interface CameraExtensionsInfo {
+    method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getCurrentExtensionMode();
+    method public default androidx.lifecycle.LiveData<java.lang.Integer!>? getExtensionStrength();
+    method public default boolean isCurrentExtensionModeAvailable();
+    method public default boolean isExtensionStrengthAvailable();
+  }
+
+  @RequiresApi(21) public final class ExtensionMode {
+    field public static final int AUTO = 5; // 0x5
+    field public static final int BOKEH = 1; // 0x1
+    field public static final int FACE_RETOUCH = 4; // 0x4
+    field public static final int HDR = 2; // 0x2
+    field public static final int NIGHT = 3; // 0x3
+    field public static final int NONE = 0; // 0x0
+  }
+
+  @RequiresApi(21) public final class ExtensionsManager {
+    method public androidx.camera.extensions.CameraExtensionsControl? getCameraExtensionsControl(androidx.camera.core.CameraControl);
+    method public androidx.camera.extensions.CameraExtensionsInfo getCameraExtensionsInfo(androidx.camera.core.CameraInfo);
+    method public android.util.Range<java.lang.Long!>? getEstimatedCaptureLatencyRange(androidx.camera.core.CameraSelector, int);
+    method public androidx.camera.core.CameraSelector getExtensionEnabledCameraSelector(androidx.camera.core.CameraSelector, int);
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.extensions.ExtensionsManager!> getInstanceAsync(android.content.Context, androidx.camera.core.CameraProvider);
+    method public boolean isExtensionAvailable(androidx.camera.core.CameraSelector, int);
+    method public boolean isImageAnalysisSupported(androidx.camera.core.CameraSelector, int);
+  }
+
+}
+
diff --git a/camera/camera-extensions/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 edcf69a..9c5d04ae 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
@@ -290,6 +290,7 @@
         }
 
         if (mPreviewProcessor != null) {
+            mPreviewProcessor.resume();
             setImageProcessor(mPreviewOutputConfig.getId(),
                     new ImageProcessor() {
                         @Override
@@ -357,6 +358,9 @@
     @Override
     public void onCaptureSessionEnd() {
         mOnEnableDisableSessionDurationCheck.onDisableSessionInvoked();
+        if (mPreviewProcessor != null) {
+            mPreviewProcessor.pause();
+        }
         List<CaptureStageImpl> captureStages = new ArrayList<>();
         CaptureStageImpl captureStage1 = mPreviewExtenderImpl.onDisableSession();
         Logger.d(TAG, "preview onDisableSession: " + captureStage1);
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessor.java
index bf0f327..337a01e 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessor.java
@@ -52,12 +52,15 @@
 class PreviewProcessor {
     private static final String TAG = "PreviewProcessor";
     @NonNull
-    final PreviewImageProcessorImpl mPreviewImageProcessor;
+    private final PreviewImageProcessorImpl mPreviewImageProcessor;
     @NonNull
-    final CaptureResultImageMatcher mCaptureResultImageMatcher = new CaptureResultImageMatcher();
-    final Object mLock = new Object();
+    private final CaptureResultImageMatcher mCaptureResultImageMatcher =
+            new CaptureResultImageMatcher();
+    private final Object mLock = new Object();
     @GuardedBy("mLock")
-    boolean mIsClosed = false;
+    private boolean mIsClosed = false;
+    @GuardedBy("mLock")
+    private boolean mIsPaused = false;
 
     PreviewProcessor(@NonNull PreviewImageProcessorImpl previewImageProcessor,
             @NonNull Surface previewOutputSurface, @NonNull Size surfaceSize) {
@@ -72,13 +75,25 @@
                 @NonNull List<Pair<CaptureResult.Key, Object>> result);
     }
 
+    void pause() {
+        synchronized (mLock) {
+            mIsPaused = true;
+        }
+    }
+
+    void resume() {
+        synchronized (mLock) {
+            mIsPaused = false;
+        }
+    }
+
     void start(@NonNull OnCaptureResultCallback onResultCallback) {
         mCaptureResultImageMatcher.setImageReferenceListener(
                 (imageReference, totalCaptureResult, captureStageId) -> {
                     synchronized (mLock) {
-                        if (mIsClosed) {
+                        if (mIsClosed || mIsPaused) {
                             imageReference.decrement();
-                            Logger.d(TAG, "Ignore image in closed state");
+                            Logger.d(TAG, "Ignore image in closed or paused state");
                             return;
                         }
                         if (ClientVersion.isMinimumCompatibleVersion(Version.VERSION_1_3)
diff --git a/camera/camera-lifecycle/api/1.4.0-beta01.txt b/camera/camera-lifecycle/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..5dd135e
--- /dev/null
+++ b/camera/camera-lifecycle/api/1.4.0-beta01.txt
@@ -0,0 +1,24 @@
+// Signature format: 4.0
+package androidx.camera.lifecycle {
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
+  }
+
+  @RequiresApi(21) public final class ProcessCameraProvider implements androidx.camera.core.CameraProvider {
+    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
+    method @MainThread public androidx.camera.core.ConcurrentCamera bindToLifecycle(java.util.List<androidx.camera.core.ConcurrentCamera.SingleCameraConfig!>);
+    method @SuppressCompatibility @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
+    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+    method public java.util.List<java.util.List<androidx.camera.core.CameraInfo!>!> getAvailableConcurrentCameraInfos();
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
+    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+    method public boolean isBound(androidx.camera.core.UseCase);
+    method @MainThread public boolean isConcurrentCameraModeOn();
+    method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> shutdownAsync();
+    method @MainThread public void unbind(androidx.camera.core.UseCase!...);
+    method @MainThread public void unbindAll();
+  }
+
+}
+
diff --git a/camera/camera-lifecycle/api/res-1.4.0-beta01.txt b/camera/camera-lifecycle/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-lifecycle/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-lifecycle/api/restricted_1.4.0-beta01.txt b/camera/camera-lifecycle/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..5dd135e
--- /dev/null
+++ b/camera/camera-lifecycle/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,24 @@
+// Signature format: 4.0
+package androidx.camera.lifecycle {
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalCameraProviderConfiguration {
+  }
+
+  @RequiresApi(21) public final class ProcessCameraProvider implements androidx.camera.core.CameraProvider {
+    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCase!...);
+    method @MainThread public androidx.camera.core.Camera bindToLifecycle(androidx.lifecycle.LifecycleOwner, androidx.camera.core.CameraSelector, androidx.camera.core.UseCaseGroup);
+    method @MainThread public androidx.camera.core.ConcurrentCamera bindToLifecycle(java.util.List<androidx.camera.core.ConcurrentCamera.SingleCameraConfig!>);
+    method @SuppressCompatibility @androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration public static void configureInstance(androidx.camera.core.CameraXConfig);
+    method public java.util.List<androidx.camera.core.CameraInfo!> getAvailableCameraInfos();
+    method public java.util.List<java.util.List<androidx.camera.core.CameraInfo!>!> getAvailableConcurrentCameraInfos();
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.camera.lifecycle.ProcessCameraProvider!> getInstance(android.content.Context);
+    method public boolean hasCamera(androidx.camera.core.CameraSelector) throws androidx.camera.core.CameraInfoUnavailableException;
+    method public boolean isBound(androidx.camera.core.UseCase);
+    method @MainThread public boolean isConcurrentCameraModeOn();
+    method @VisibleForTesting public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> shutdownAsync();
+    method @MainThread public void unbind(androidx.camera.core.UseCase!...);
+    method @MainThread public void unbindAll();
+  }
+
+}
+
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index b39c18a..2c0a5a4 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -806,6 +806,43 @@
     }
 
     @Test
+    fun bindConcurrentPhysicalCamera_isBound() {
+        ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig())
+
+        runBlocking(MainScope().coroutineContext) {
+            provider = ProcessCameraProvider.getInstance(context).await()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+
+            val singleCameraConfig0 = SingleCameraConfig(
+                CameraSelector.Builder()
+                    .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
+                    .build(),
+                UseCaseGroup.Builder()
+                    .addUseCase(useCase0)
+                    .build(),
+                lifecycleOwner0)
+            val singleCameraConfig1 = SingleCameraConfig(
+                CameraSelector.Builder()
+                    .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
+                    .build(),
+                UseCaseGroup.Builder()
+                    .addUseCase(useCase1)
+                    .build(),
+                lifecycleOwner0)
+
+            val concurrentCamera = provider.bindToLifecycle(
+                listOf(singleCameraConfig0, singleCameraConfig1))
+
+            assertThat(concurrentCamera).isNotNull()
+            assertThat(concurrentCamera.cameras.size).isEqualTo(1)
+            assertThat(provider.isBound(useCase0)).isTrue()
+            assertThat(provider.isBound(useCase1)).isTrue()
+            assertThat(provider.isConcurrentCameraModeOn).isFalse()
+        }
+    }
+
+    @Test
     fun bindConcurrentCameraTwice_isBound() {
         ProcessCameraProvider.configureInstance(createConcurrentCameraAppConfig())
 
@@ -882,15 +919,8 @@
                     .build(),
                 lifecycleOwner0)
 
-            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
-                assertThrows<IllegalArgumentException> {
-                    provider.bindToLifecycle(listOf(singleCameraConfig0))
-                }
-                assertThat(provider.isConcurrentCameraModeOn).isFalse()
-            } else {
-                assertThrows<UnsupportedOperationException> {
-                    provider.bindToLifecycle(listOf(singleCameraConfig0))
-                }
+            assertThrows<IllegalArgumentException> {
+                provider.bindToLifecycle(listOf(singleCameraConfig0))
             }
         }
     }
@@ -923,17 +953,9 @@
                     .build(),
                 lifecycleOwner1)
 
-            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
-                assertThrows<IllegalArgumentException> {
-                    provider.bindToLifecycle(
-                        listOf(singleCameraConfig0, singleCameraConfig1, singleCameraConfig2))
-                }
-                assertThat(provider.isConcurrentCameraModeOn).isFalse()
-            } else {
-                assertThrows<java.lang.UnsupportedOperationException> {
-                    provider.bindToLifecycle(
-                        listOf(singleCameraConfig0, singleCameraConfig1, singleCameraConfig2))
-                }
+            assertThrows<java.lang.IllegalArgumentException> {
+                provider.bindToLifecycle(
+                    listOf(singleCameraConfig0, singleCameraConfig1, singleCameraConfig2))
             }
         }
     }
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index 536684e..11038bb 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -82,6 +82,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
@@ -453,16 +454,6 @@
     @MainThread
     @NonNull
     public ConcurrentCamera bindToLifecycle(@NonNull List<SingleCameraConfig> singleCameraConfigs) {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
-            throw new UnsupportedOperationException("Concurrent camera is not supported on the "
-                    + "device");
-        }
-
-        if (getCameraOperatingMode() == CAMERA_OPERATING_MODE_SINGLE) {
-            throw new UnsupportedOperationException("Camera is already running, call "
-                    + "unbindAll() before binding more cameras");
-        }
-
         if (singleCameraConfigs.size() < 2) {
             throw new IllegalArgumentException("Concurrent camera needs two camera configs");
         }
@@ -472,37 +463,81 @@
                     + "cameras at maximum.");
         }
 
-        List<CameraInfo> cameraInfosToBind = new ArrayList<>();
-        CameraInfo firstCameraInfo;
-        CameraInfo secondCameraInfo;
-        try {
-            firstCameraInfo = getCameraInfo(
-                    singleCameraConfigs.get(0).getCameraSelector());
-            secondCameraInfo = getCameraInfo(
-                    singleCameraConfigs.get(1).getCameraSelector());
-        } catch (IllegalArgumentException e) {
-            throw new IllegalArgumentException("Invalid camera selectors in camera configs");
-        }
-        cameraInfosToBind.add(firstCameraInfo);
-        cameraInfosToBind.add(secondCameraInfo);
-        if (!getActiveConcurrentCameraInfos().isEmpty()
-                && !cameraInfosToBind.equals(getActiveConcurrentCameraInfos())) {
-            throw new UnsupportedOperationException("Cameras are already running, call "
-                    + "unbindAll() before binding more cameras");
-        }
-
-        setCameraOperatingMode(CAMERA_OPERATING_MODE_CONCURRENT);
         List<Camera> cameras = new ArrayList<>();
-        for (SingleCameraConfig config : singleCameraConfigs) {
-            Camera camera = bindToLifecycle(
-                    config.getLifecycleOwner(),
-                    config.getCameraSelector(),
-                    config.getUseCaseGroup().getViewPort(),
-                    config.getUseCaseGroup().getEffects(),
-                    config.getUseCaseGroup().getUseCases().toArray(new UseCase[0]));
+        if (Objects.equals(singleCameraConfigs.get(0).getCameraSelector().getLensFacing(),
+                singleCameraConfigs.get(1).getCameraSelector().getLensFacing())) {
+            if (getCameraOperatingMode() == CAMERA_OPERATING_MODE_CONCURRENT) {
+                throw new UnsupportedOperationException("Camera is already running, call "
+                        + "unbindAll() before binding more cameras");
+            }
+            if (!Objects.equals(singleCameraConfigs.get(0).getLifecycleOwner(),
+                    singleCameraConfigs.get(1).getLifecycleOwner())
+                    || !Objects.equals(singleCameraConfigs.get(0).getUseCaseGroup().getViewPort(),
+                    singleCameraConfigs.get(1).getUseCaseGroup().getViewPort())
+                    || !Objects.equals(singleCameraConfigs.get(0).getUseCaseGroup().getEffects(),
+                    singleCameraConfigs.get(1).getUseCaseGroup().getEffects())) {
+                throw new IllegalArgumentException("Two camera configs need to have the same "
+                        + "lifecycle owner, view port and effects");
+            }
+            LifecycleOwner lifecycleOwner = singleCameraConfigs.get(0).getLifecycleOwner();
+            CameraSelector cameraSelector = singleCameraConfigs.get(0).getCameraSelector();
+            ViewPort viewPort = singleCameraConfigs.get(0).getUseCaseGroup().getViewPort();
+            List<CameraEffect> effects = singleCameraConfigs.get(0).getUseCaseGroup().getEffects();
+            List<UseCase> useCases = new ArrayList<>();
+            for (SingleCameraConfig config : singleCameraConfigs) {
+                // Connect physical camera id with use case
+                for (UseCase useCase : config.getUseCaseGroup().getUseCases()) {
+                    useCase.setPhysicalCameraId(config.getCameraSelector().getPhysicalCameraId());
+                }
+                useCases.addAll(config.getUseCaseGroup().getUseCases());
+            }
+
+            setCameraOperatingMode(CAMERA_OPERATING_MODE_SINGLE);
+            Camera camera = bindToLifecycle(lifecycleOwner, cameraSelector, viewPort,
+                    effects, useCases.toArray(new UseCase[0]));
             cameras.add(camera);
+        } else {
+            if (!mContext.getPackageManager().hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+                throw new UnsupportedOperationException("Concurrent camera is not supported on the "
+                        + "device");
+            }
+
+            if (getCameraOperatingMode() == CAMERA_OPERATING_MODE_SINGLE) {
+                throw new UnsupportedOperationException("Camera is already running, call "
+                        + "unbindAll() before binding more cameras");
+            }
+
+            List<CameraInfo> cameraInfosToBind = new ArrayList<>();
+            CameraInfo firstCameraInfo;
+            CameraInfo secondCameraInfo;
+            try {
+                firstCameraInfo = getCameraInfo(
+                        singleCameraConfigs.get(0).getCameraSelector());
+                secondCameraInfo = getCameraInfo(
+                        singleCameraConfigs.get(1).getCameraSelector());
+            } catch (IllegalArgumentException e) {
+                throw new IllegalArgumentException("Invalid camera selectors in camera configs");
+            }
+            cameraInfosToBind.add(firstCameraInfo);
+            cameraInfosToBind.add(secondCameraInfo);
+            if (!getActiveConcurrentCameraInfos().isEmpty()
+                    && !cameraInfosToBind.equals(getActiveConcurrentCameraInfos())) {
+                throw new UnsupportedOperationException("Cameras are already running, call "
+                        + "unbindAll() before binding more cameras");
+            }
+
+            setCameraOperatingMode(CAMERA_OPERATING_MODE_CONCURRENT);
+            for (SingleCameraConfig config : singleCameraConfigs) {
+                Camera camera = bindToLifecycle(
+                        config.getLifecycleOwner(),
+                        config.getCameraSelector(),
+                        config.getUseCaseGroup().getViewPort(),
+                        config.getUseCaseGroup().getEffects(),
+                        config.getUseCaseGroup().getUseCases().toArray(new UseCase[0]));
+                cameras.add(camera);
+            }
+            setActiveConcurrentCameraInfos(cameraInfosToBind);
         }
-        setActiveConcurrentCameraInfos(cameraInfosToBind);
         return new ConcurrentCamera(cameras);
     }
 
diff --git a/camera/camera-mlkit-vision/api/1.4.0-beta01.txt b/camera/camera-mlkit-vision/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..0599c25
--- /dev/null
+++ b/camera/camera-mlkit-vision/api/1.4.0-beta01.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.camera.mlkit.vision {
+
+  @RequiresApi(21) public class MlKitAnalyzer implements androidx.camera.core.ImageAnalysis.Analyzer {
+    ctor public MlKitAnalyzer(java.util.List<com.google.mlkit.vision.interfaces.Detector<?>!>, int, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.mlkit.vision.MlKitAnalyzer.Result!>);
+    method public final void analyze(androidx.camera.core.ImageProxy);
+    method public final android.util.Size getDefaultTargetResolution();
+    method public final int getTargetCoordinateSystem();
+    method public final void updateTransform(android.graphics.Matrix?);
+  }
+
+  public static final class MlKitAnalyzer.Result {
+    ctor public MlKitAnalyzer.Result(java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Object!>, long, java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Throwable!>);
+    method public Throwable? getThrowable(com.google.mlkit.vision.interfaces.Detector<?>);
+    method public long getTimestamp();
+    method public <T> T? getValue(com.google.mlkit.vision.interfaces.Detector<T!>);
+  }
+
+}
+
diff --git a/camera/camera-mlkit-vision/api/res-1.4.0-beta01.txt b/camera/camera-mlkit-vision/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-mlkit-vision/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-mlkit-vision/api/restricted_1.4.0-beta01.txt b/camera/camera-mlkit-vision/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..0599c25
--- /dev/null
+++ b/camera/camera-mlkit-vision/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,20 @@
+// Signature format: 4.0
+package androidx.camera.mlkit.vision {
+
+  @RequiresApi(21) public class MlKitAnalyzer implements androidx.camera.core.ImageAnalysis.Analyzer {
+    ctor public MlKitAnalyzer(java.util.List<com.google.mlkit.vision.interfaces.Detector<?>!>, int, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.mlkit.vision.MlKitAnalyzer.Result!>);
+    method public final void analyze(androidx.camera.core.ImageProxy);
+    method public final android.util.Size getDefaultTargetResolution();
+    method public final int getTargetCoordinateSystem();
+    method public final void updateTransform(android.graphics.Matrix?);
+  }
+
+  public static final class MlKitAnalyzer.Result {
+    ctor public MlKitAnalyzer.Result(java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Object!>, long, java.util.Map<com.google.mlkit.vision.interfaces.Detector<?>!,java.lang.Throwable!>);
+    method public Throwable? getThrowable(com.google.mlkit.vision.interfaces.Detector<?>);
+    method public long getTimestamp();
+    method public <T> T? getValue(com.google.mlkit.vision.interfaces.Detector<T!>);
+  }
+
+}
+
diff --git a/camera/camera-video/api/1.4.0-beta01.txt b/camera/camera-video/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..405c299
--- /dev/null
+++ b/camera/camera-video/api/1.4.0-beta01.txt
@@ -0,0 +1,220 @@
+// Signature format: 4.0
+package androidx.camera.video {
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+    method public double getAudioAmplitude();
+    method public abstract int getAudioState();
+    method public abstract Throwable? getErrorCause();
+    method public boolean hasAudio();
+    method public boolean hasError();
+    field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
+    field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
+    field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
+    field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
+    field public static final int AUDIO_STATE_MUTED = 5; // 0x5
+    field public static final int AUDIO_STATE_SOURCE_ERROR = 4; // 0x4
+    field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+  }
+
+  @RequiresApi(21) public class FallbackStrategy {
+    method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
+    method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
+    method public static androidx.camera.video.FallbackStrategy lowerQualityOrHigherThan(androidx.camera.video.Quality);
+    method public static androidx.camera.video.FallbackStrategy lowerQualityThan(androidx.camera.video.Quality);
+  }
+
+  @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
+    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+  }
+
+  @RequiresApi(21) public static final class FileDescriptorOutputOptions.Builder {
+    ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
+    method public androidx.camera.video.FileDescriptorOutputOptions build();
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
+  }
+
+  @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
+    method public java.io.File getFile();
+  }
+
+  @RequiresApi(21) public static final class FileOutputOptions.Builder {
+    ctor public FileOutputOptions.Builder(java.io.File);
+    method public androidx.camera.video.FileOutputOptions build();
+    method public androidx.camera.video.FileOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+    method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
+  }
+
+  @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
+    method public android.net.Uri getCollectionUri();
+    method public android.content.ContentResolver getContentResolver();
+    method public android.content.ContentValues getContentValues();
+    field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
+  }
+
+  public static final class MediaStoreOutputOptions.Builder {
+    ctor public MediaStoreOutputOptions.Builder(android.content.ContentResolver, android.net.Uri);
+    method public androidx.camera.video.MediaStoreOutputOptions build();
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
+  }
+
+  @RequiresApi(21) public abstract class OutputOptions {
+    method @IntRange(from=0) public long getDurationLimitMillis();
+    method @IntRange(from=0) public long getFileSizeLimit();
+    method public android.location.Location? getLocation();
+    field public static final int DURATION_UNLIMITED = 0; // 0x0
+    field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
+  }
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class OutputResults {
+    ctor public OutputResults();
+    method public abstract android.net.Uri getOutputUri();
+  }
+
+  @RequiresApi(21) public final class PendingRecording {
+    method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
+    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
+  }
+
+  @RequiresApi(21) public class Quality {
+    field public static final androidx.camera.video.Quality FHD;
+    field public static final androidx.camera.video.Quality HD;
+    field public static final androidx.camera.video.Quality HIGHEST;
+    field public static final androidx.camera.video.Quality LOWEST;
+    field public static final androidx.camera.video.Quality SD;
+    field public static final androidx.camera.video.Quality UHD;
+  }
+
+  @RequiresApi(21) public final class QualitySelector {
+    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality);
+    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality, androidx.camera.video.FallbackStrategy);
+    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
+    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
+    method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+    method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
+    method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+  }
+
+  @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
+    method public int getAspectRatio();
+    method public java.util.concurrent.Executor? getExecutor();
+    method public androidx.camera.video.QualitySelector getQualitySelector();
+    method public int getTargetVideoEncodingBitRate();
+    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
+    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo, int);
+    method public int getVideoCapabilitiesSource();
+    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+    method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
+    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
+    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.MediaStoreOutputOptions);
+    field public static final androidx.camera.video.QualitySelector DEFAULT_QUALITY_SELECTOR;
+    field public static final int VIDEO_CAPABILITIES_SOURCE_CAMCORDER_PROFILE = 0; // 0x0
+    field public static final int VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES = 1; // 0x1
+  }
+
+  @RequiresApi(21) public static final class Recorder.Builder {
+    ctor public Recorder.Builder();
+    method public androidx.camera.video.Recorder build();
+    method public androidx.camera.video.Recorder.Builder setAspectRatio(int);
+    method public androidx.camera.video.Recorder.Builder setExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.video.Recorder.Builder setQualitySelector(androidx.camera.video.QualitySelector);
+    method public androidx.camera.video.Recorder.Builder setTargetVideoEncodingBitRate(@IntRange(from=1) int);
+    method public androidx.camera.video.Recorder.Builder setVideoCapabilitiesSource(int);
+  }
+
+  @RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
+    method public void close();
+    method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
+    method public void mute(boolean);
+    method public void pause();
+    method public void resume();
+    method public void stop();
+  }
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class RecordingStats {
+    method public abstract androidx.camera.video.AudioStats getAudioStats();
+    method public abstract long getNumBytesRecorded();
+    method public abstract long getRecordedDurationNanos();
+  }
+
+  @RequiresApi(21) public interface VideoCapabilities {
+    method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
+    method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
+    method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
+    method public default boolean isStabilizationSupported();
+  }
+
+  @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
+    method public androidx.camera.core.DynamicRange getDynamicRange();
+    method public int getMirrorMode();
+    method public T getOutput();
+    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
+    method public int getTargetRotation();
+    method public boolean isVideoStabilizationEnabled();
+    method public void setTargetRotation(int);
+    method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
+  }
+
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture!> {
+    ctor public VideoCapture.Builder(T);
+    method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setVideoStabilizationEnabled(boolean);
+  }
+
+  @RequiresApi(21) public interface VideoOutput {
+    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+  }
+
+  @RequiresApi(21) public abstract class VideoRecordEvent {
+    method public androidx.camera.video.OutputOptions getOutputOptions();
+    method public androidx.camera.video.RecordingStats getRecordingStats();
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Finalize extends androidx.camera.video.VideoRecordEvent {
+    method public Throwable? getCause();
+    method public int getError();
+    method public androidx.camera.video.OutputResults getOutputResults();
+    method public boolean hasError();
+    field public static final int ERROR_DURATION_LIMIT_REACHED = 9; // 0x9
+    field public static final int ERROR_ENCODING_FAILED = 6; // 0x6
+    field public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2; // 0x2
+    field public static final int ERROR_INSUFFICIENT_STORAGE = 3; // 0x3
+    field public static final int ERROR_INVALID_OUTPUT_OPTIONS = 5; // 0x5
+    field public static final int ERROR_NONE = 0; // 0x0
+    field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
+    field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
+    field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Pause extends androidx.camera.video.VideoRecordEvent {
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Resume extends androidx.camera.video.VideoRecordEvent {
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Start extends androidx.camera.video.VideoRecordEvent {
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Status extends androidx.camera.video.VideoRecordEvent {
+  }
+
+}
+
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index df297e5..405c299 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -167,7 +167,7 @@
     method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
   }
 
-  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture!> {
     ctor public VideoCapture.Builder(T);
     method public androidx.camera.video.VideoCapture<T!> build();
     method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
diff --git a/camera/camera-video/api/res-1.4.0-beta01.txt b/camera/camera-video/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-video/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-video/api/restricted_1.4.0-beta01.txt b/camera/camera-video/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..405c299
--- /dev/null
+++ b/camera/camera-video/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,220 @@
+// Signature format: 4.0
+package androidx.camera.video {
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+    method public double getAudioAmplitude();
+    method public abstract int getAudioState();
+    method public abstract Throwable? getErrorCause();
+    method public boolean hasAudio();
+    method public boolean hasError();
+    field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
+    field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
+    field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
+    field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
+    field public static final int AUDIO_STATE_MUTED = 5; // 0x5
+    field public static final int AUDIO_STATE_SOURCE_ERROR = 4; // 0x4
+    field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+  }
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
+  }
+
+  @RequiresApi(21) public class FallbackStrategy {
+    method public static androidx.camera.video.FallbackStrategy higherQualityOrLowerThan(androidx.camera.video.Quality);
+    method public static androidx.camera.video.FallbackStrategy higherQualityThan(androidx.camera.video.Quality);
+    method public static androidx.camera.video.FallbackStrategy lowerQualityOrHigherThan(androidx.camera.video.Quality);
+    method public static androidx.camera.video.FallbackStrategy lowerQualityThan(androidx.camera.video.Quality);
+  }
+
+  @RequiresApi(21) public final class FileDescriptorOutputOptions extends androidx.camera.video.OutputOptions {
+    method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+  }
+
+  @RequiresApi(21) public static final class FileDescriptorOutputOptions.Builder {
+    ctor public FileDescriptorOutputOptions.Builder(android.os.ParcelFileDescriptor);
+    method public androidx.camera.video.FileDescriptorOutputOptions build();
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+    method public androidx.camera.video.FileDescriptorOutputOptions.Builder setLocation(android.location.Location?);
+  }
+
+  @RequiresApi(21) public final class FileOutputOptions extends androidx.camera.video.OutputOptions {
+    method public java.io.File getFile();
+  }
+
+  @RequiresApi(21) public static final class FileOutputOptions.Builder {
+    ctor public FileOutputOptions.Builder(java.io.File);
+    method public androidx.camera.video.FileOutputOptions build();
+    method public androidx.camera.video.FileOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+    method public androidx.camera.video.FileOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+    method public androidx.camera.video.FileOutputOptions.Builder setLocation(android.location.Location?);
+  }
+
+  @RequiresApi(21) public final class MediaStoreOutputOptions extends androidx.camera.video.OutputOptions {
+    method public android.net.Uri getCollectionUri();
+    method public android.content.ContentResolver getContentResolver();
+    method public android.content.ContentValues getContentValues();
+    field public static final android.content.ContentValues EMPTY_CONTENT_VALUES;
+  }
+
+  public static final class MediaStoreOutputOptions.Builder {
+    ctor public MediaStoreOutputOptions.Builder(android.content.ContentResolver, android.net.Uri);
+    method public androidx.camera.video.MediaStoreOutputOptions build();
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setContentValues(android.content.ContentValues);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setDurationLimitMillis(@IntRange(from=0) long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setFileSizeLimit(@IntRange(from=0) long);
+    method public androidx.camera.video.MediaStoreOutputOptions.Builder setLocation(android.location.Location?);
+  }
+
+  @RequiresApi(21) public abstract class OutputOptions {
+    method @IntRange(from=0) public long getDurationLimitMillis();
+    method @IntRange(from=0) public long getFileSizeLimit();
+    method public android.location.Location? getLocation();
+    field public static final int DURATION_UNLIMITED = 0; // 0x0
+    field public static final int FILE_SIZE_UNLIMITED = 0; // 0x0
+  }
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class OutputResults {
+    ctor public OutputResults();
+    method public abstract android.net.Uri getOutputUri();
+  }
+
+  @RequiresApi(21) public final class PendingRecording {
+    method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public androidx.camera.video.PendingRecording asPersistentRecording();
+    method @CheckResult public androidx.camera.video.Recording start(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public androidx.camera.video.PendingRecording withAudioEnabled();
+  }
+
+  @RequiresApi(21) public class Quality {
+    field public static final androidx.camera.video.Quality FHD;
+    field public static final androidx.camera.video.Quality HD;
+    field public static final androidx.camera.video.Quality HIGHEST;
+    field public static final androidx.camera.video.Quality LOWEST;
+    field public static final androidx.camera.video.Quality SD;
+    field public static final androidx.camera.video.Quality UHD;
+  }
+
+  @RequiresApi(21) public final class QualitySelector {
+    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality);
+    method public static androidx.camera.video.QualitySelector from(androidx.camera.video.Quality, androidx.camera.video.FallbackStrategy);
+    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>);
+    method public static androidx.camera.video.QualitySelector fromOrderedList(java.util.List<androidx.camera.video.Quality!>, androidx.camera.video.FallbackStrategy);
+    method public static android.util.Size? getResolution(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+    method @Deprecated public static java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.CameraInfo);
+    method @Deprecated public static boolean isQualitySupported(androidx.camera.core.CameraInfo, androidx.camera.video.Quality);
+  }
+
+  @RequiresApi(21) public final class Recorder implements androidx.camera.video.VideoOutput {
+    method public int getAspectRatio();
+    method public java.util.concurrent.Executor? getExecutor();
+    method public androidx.camera.video.QualitySelector getQualitySelector();
+    method public int getTargetVideoEncodingBitRate();
+    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo);
+    method public static androidx.camera.video.VideoCapabilities getVideoCapabilities(androidx.camera.core.CameraInfo, int);
+    method public int getVideoCapabilitiesSource();
+    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+    method @RequiresApi(26) public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileDescriptorOutputOptions);
+    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.FileOutputOptions);
+    method public androidx.camera.video.PendingRecording prepareRecording(android.content.Context, androidx.camera.video.MediaStoreOutputOptions);
+    field public static final androidx.camera.video.QualitySelector DEFAULT_QUALITY_SELECTOR;
+    field public static final int VIDEO_CAPABILITIES_SOURCE_CAMCORDER_PROFILE = 0; // 0x0
+    field public static final int VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES = 1; // 0x1
+  }
+
+  @RequiresApi(21) public static final class Recorder.Builder {
+    ctor public Recorder.Builder();
+    method public androidx.camera.video.Recorder build();
+    method public androidx.camera.video.Recorder.Builder setAspectRatio(int);
+    method public androidx.camera.video.Recorder.Builder setExecutor(java.util.concurrent.Executor);
+    method public androidx.camera.video.Recorder.Builder setQualitySelector(androidx.camera.video.QualitySelector);
+    method public androidx.camera.video.Recorder.Builder setTargetVideoEncodingBitRate(@IntRange(from=1) int);
+    method public androidx.camera.video.Recorder.Builder setVideoCapabilitiesSource(int);
+  }
+
+  @RequiresApi(21) public final class Recording implements java.lang.AutoCloseable {
+    method public void close();
+    method @SuppressCompatibility @androidx.camera.video.ExperimentalPersistentRecording public boolean isPersistent();
+    method public void mute(boolean);
+    method public void pause();
+    method public void resume();
+    method public void stop();
+  }
+
+  @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class RecordingStats {
+    method public abstract androidx.camera.video.AudioStats getAudioStats();
+    method public abstract long getNumBytesRecorded();
+    method public abstract long getRecordedDurationNanos();
+  }
+
+  @RequiresApi(21) public interface VideoCapabilities {
+    method public java.util.Set<androidx.camera.core.DynamicRange!> getSupportedDynamicRanges();
+    method public java.util.List<androidx.camera.video.Quality!> getSupportedQualities(androidx.camera.core.DynamicRange);
+    method public boolean isQualitySupported(androidx.camera.video.Quality, androidx.camera.core.DynamicRange);
+    method public default boolean isStabilizationSupported();
+  }
+
+  @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
+    method public androidx.camera.core.DynamicRange getDynamicRange();
+    method public int getMirrorMode();
+    method public T getOutput();
+    method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
+    method public int getTargetRotation();
+    method public boolean isVideoStabilizationEnabled();
+    method public void setTargetRotation(int);
+    method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
+  }
+
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture!> {
+    ctor public VideoCapture.Builder(T);
+    method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setMirrorMode(int);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetFrameRate(android.util.Range<java.lang.Integer!>);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+    method public androidx.camera.video.VideoCapture.Builder<T!> setVideoStabilizationEnabled(boolean);
+  }
+
+  @RequiresApi(21) public interface VideoOutput {
+    method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
+  }
+
+  @RequiresApi(21) public abstract class VideoRecordEvent {
+    method public androidx.camera.video.OutputOptions getOutputOptions();
+    method public androidx.camera.video.RecordingStats getRecordingStats();
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Finalize extends androidx.camera.video.VideoRecordEvent {
+    method public Throwable? getCause();
+    method public int getError();
+    method public androidx.camera.video.OutputResults getOutputResults();
+    method public boolean hasError();
+    field public static final int ERROR_DURATION_LIMIT_REACHED = 9; // 0x9
+    field public static final int ERROR_ENCODING_FAILED = 6; // 0x6
+    field public static final int ERROR_FILE_SIZE_LIMIT_REACHED = 2; // 0x2
+    field public static final int ERROR_INSUFFICIENT_STORAGE = 3; // 0x3
+    field public static final int ERROR_INVALID_OUTPUT_OPTIONS = 5; // 0x5
+    field public static final int ERROR_NONE = 0; // 0x0
+    field public static final int ERROR_NO_VALID_DATA = 8; // 0x8
+    field public static final int ERROR_RECORDER_ERROR = 7; // 0x7
+    field public static final int ERROR_RECORDING_GARBAGE_COLLECTED = 10; // 0xa
+    field public static final int ERROR_SOURCE_INACTIVE = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Pause extends androidx.camera.video.VideoRecordEvent {
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Resume extends androidx.camera.video.VideoRecordEvent {
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Start extends androidx.camera.video.VideoRecordEvent {
+  }
+
+  @RequiresApi(21) public static final class VideoRecordEvent.Status extends androidx.camera.video.VideoRecordEvent {
+  }
+
+}
+
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index df297e5..405c299 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -167,7 +167,7 @@
     method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
   }
 
-  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture!> {
     ctor public VideoCapture.Builder(T);
     method public androidx.camera.video.VideoCapture<T!> build();
     method public androidx.camera.video.VideoCapture.Builder<T!> setDynamicRange(androidx.camera.core.DynamicRange);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index aa18150..f1eebe3 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -516,8 +516,7 @@
         CameraInternal cameraInternal = getCamera();
         SurfaceEdge cameraEdge = mCameraEdge;
         if (cameraInternal != null && cameraEdge != null) {
-            mRotationDegrees = adjustRotationWithInProgressTransformation(
-                    getRelativeRotation(cameraInternal, isMirroringRequired(cameraInternal)));
+            mRotationDegrees = getCompensatedRotation(cameraInternal);
             cameraEdge.updateTransformation(mRotationDegrees, getAppTargetRotation());
         }
     }
@@ -535,15 +534,27 @@
         return adjustedCropRect;
     }
 
-    private int adjustRotationWithInProgressTransformation(int rotationDegrees) {
-        int adjustedRotationDegrees = rotationDegrees;
+    /**
+     * Gets the rotation that is compensated by the in-progress transformation.
+     *
+     * <p>If there's no in-progress recording, the returned rotation degrees will be the same as
+     * {@link #getRelativeRotation(CameraInternal)}.
+     */
+    private int getCompensatedRotation(@NonNull CameraInternal cameraInternal) {
+        boolean isMirroringRequired = isMirroringRequired(cameraInternal);
+        int rotationDegrees = getRelativeRotation(cameraInternal, isMirroringRequired);
         if (shouldCompensateTransformation()) {
             TransformationInfo transformationInfo =
                     requireNonNull(mStreamInfo.getInProgressTransformationInfo());
-            adjustedRotationDegrees = within360(
-                    rotationDegrees - transformationInfo.getRotationDegrees());
+            int inProgressDegrees = transformationInfo.getRotationDegrees();
+            if (isMirroringRequired != transformationInfo.isMirroring()) {
+                // If the mirroring states of the current stream and the existing stream are
+                // different, the existing rotation degrees should be inverted.
+                inProgressDegrees = -inProgressDegrees;
+            }
+            rotationDegrees = within360(rotationDegrees - inProgressDegrees);
         }
-        return adjustedRotationDegrees;
+        return rotationDegrees;
     }
 
     @NonNull
@@ -615,8 +626,7 @@
         VideoEncoderInfo videoEncoderInfo = resolveVideoEncoderInfo(
                 config.getVideoEncoderInfoFinder(), encoderProfiles, mediaSpec,  resolution,
                 dynamicRange, expectedFrameRate);
-        mRotationDegrees = adjustRotationWithInProgressTransformation(getRelativeRotation(camera,
-                isMirroringRequired(camera)));
+        mRotationDegrees = getCompensatedRotation(camera);
         Rect originalCropRect = calculateCropRect(resolution, videoEncoderInfo);
         mCropRect = adjustCropRectWithInProgressTransformation(originalCropRect, mRotationDegrees);
         Size nodeResolution = adjustResolutionWithInProgressTransformation(resolution,
@@ -884,7 +894,7 @@
         DynamicRange dynamicRange = streamSpec.getDynamicRange();
         if (!isStreamError && mDeferrableSurface != null) {
             if (isStreamActive) {
-                sessionConfigBuilder.addSurface(mDeferrableSurface, dynamicRange);
+                sessionConfigBuilder.addSurface(mDeferrableSurface, dynamicRange, null);
             } else {
                 sessionConfigBuilder.addNonRepeatingSurface(mDeferrableSurface, dynamicRange);
             }
diff --git a/camera/camera-view/api/1.4.0-beta01.txt b/camera/camera-view/api/1.4.0-beta01.txt
new file mode 100644
index 0000000..ea8f480
--- /dev/null
+++ b/camera/camera-view/api/1.4.0-beta01.txt
@@ -0,0 +1,199 @@
+// Signature format: 4.0
+package androidx.camera.view {
+
+  @RequiresApi(21) public abstract class CameraController {
+    method @MainThread public void clearEffects();
+    method @MainThread public void clearImageAnalysisAnalyzer();
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+    method @MainThread public androidx.camera.core.CameraControl? getCameraControl();
+    method @MainThread public androidx.camera.core.CameraInfo? getCameraInfo();
+    method @MainThread public androidx.camera.core.CameraSelector getCameraSelector();
+    method @MainThread public java.util.concurrent.Executor? getImageAnalysisBackgroundExecutor();
+    method @MainThread public int getImageAnalysisBackpressureStrategy();
+    method @MainThread public int getImageAnalysisImageQueueDepth();
+    method @MainThread public int getImageAnalysisOutputImageFormat();
+    method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageAnalysisResolutionSelector();
+    method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageAnalysisTargetSize();
+    method @MainThread public int getImageCaptureFlashMode();
+    method @MainThread public java.util.concurrent.Executor? getImageCaptureIoExecutor();
+    method @MainThread public int getImageCaptureMode();
+    method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageCaptureResolutionSelector();
+    method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageCaptureTargetSize();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> getInitializationFuture();
+    method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getPreviewResolutionSelector();
+    method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
+    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
+    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+    method @MainThread public androidx.camera.core.DynamicRange getVideoCaptureDynamicRange();
+    method @MainThread public int getVideoCaptureMirrorMode();
+    method @MainThread public androidx.camera.video.QualitySelector getVideoCaptureQualitySelector();
+    method @MainThread public android.util.Range<java.lang.Integer!> getVideoCaptureTargetFrameRate();
+    method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+    method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
+    method @MainThread public boolean isImageAnalysisEnabled();
+    method @MainThread public boolean isImageCaptureEnabled();
+    method @MainThread public boolean isPinchToZoomEnabled();
+    method @MainThread public boolean isRecording();
+    method @MainThread public boolean isTapToFocusEnabled();
+    method @MainThread public boolean isVideoCaptureEnabled();
+    method @MainThread public void setCameraSelector(androidx.camera.core.CameraSelector);
+    method @MainThread public void setEffects(java.util.Set<androidx.camera.core.CameraEffect!>);
+    method @MainThread public void setEnabledUseCases(int);
+    method @MainThread public void setImageAnalysisAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+    method @MainThread public void setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor?);
+    method @MainThread public void setImageAnalysisBackpressureStrategy(int);
+    method @MainThread public void setImageAnalysisImageQueueDepth(int);
+    method @MainThread public void setImageAnalysisOutputImageFormat(int);
+    method @MainThread public void setImageAnalysisResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+    method @Deprecated @MainThread public void setImageAnalysisTargetSize(androidx.camera.view.CameraController.OutputSize?);
+    method @MainThread public void setImageCaptureFlashMode(int);
+    method @MainThread public void setImageCaptureIoExecutor(java.util.concurrent.Executor?);
+    method @MainThread public void setImageCaptureMode(int);
+    method @MainThread public void setImageCaptureResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+    method @Deprecated @MainThread public void setImageCaptureTargetSize(androidx.camera.view.CameraController.OutputSize?);
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+    method @MainThread public void setPinchToZoomEnabled(boolean);
+    method @MainThread public void setPreviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+    method @Deprecated @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
+    method @MainThread public void setTapToFocusEnabled(boolean);
+    method @MainThread public void setVideoCaptureDynamicRange(androidx.camera.core.DynamicRange);
+    method @MainThread public void setVideoCaptureMirrorMode(int);
+    method @MainThread public void setVideoCaptureQualitySelector(androidx.camera.video.QualitySelector);
+    method @MainThread public void setVideoCaptureTargetFrameRate(android.util.Range<java.lang.Integer!>);
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+    method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+    method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+    field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
+    field public static final int IMAGE_ANALYSIS = 2; // 0x2
+    field public static final int IMAGE_CAPTURE = 1; // 0x1
+    field public static final int TAP_TO_FOCUS_FAILED = 4; // 0x4
+    field public static final int TAP_TO_FOCUS_FOCUSED = 2; // 0x2
+    field public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3; // 0x3
+    field public static final int TAP_TO_FOCUS_NOT_STARTED = 0; // 0x0
+    field public static final int TAP_TO_FOCUS_STARTED = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 4; // 0x4
+  }
+
+  @Deprecated @RequiresApi(21) public static final class CameraController.OutputSize {
+    ctor @Deprecated public CameraController.OutputSize(android.util.Size);
+    ctor @Deprecated public CameraController.OutputSize(int);
+    method @Deprecated public int getAspectRatio();
+    method @Deprecated public android.util.Size? getResolution();
+    field @Deprecated public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
+  }
+
+  @RequiresApi(21) public final class LifecycleCameraController extends androidx.camera.view.CameraController {
+    ctor public LifecycleCameraController(android.content.Context);
+    method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
+    method @MainThread public void unbind();
+  }
+
+  @RequiresApi(21) public final class PreviewView extends android.widget.FrameLayout {
+    ctor @UiThread public PreviewView(android.content.Context);
+    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?);
+    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int);
+    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int, int);
+    method @UiThread public android.graphics.Bitmap? getBitmap();
+    method @UiThread public androidx.camera.view.CameraController? getController();
+    method @UiThread public androidx.camera.view.PreviewView.ImplementationMode getImplementationMode();
+    method @UiThread public androidx.camera.core.MeteringPointFactory getMeteringPointFactory();
+    method @SuppressCompatibility public androidx.camera.view.transform.OutputTransform? getOutputTransform();
+    method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
+    method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
+    method @UiThread public android.graphics.Matrix? getSensorToViewTransform();
+    method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
+    method @UiThread public androidx.camera.core.ViewPort? getViewPort();
+    method @UiThread public androidx.camera.core.ViewPort? getViewPort(int);
+    method @UiThread public void setController(androidx.camera.view.CameraController?);
+    method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
+    method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
+    method @UiThread public void setScreenFlashWindow(android.view.Window?);
+  }
+
+  @RequiresApi(21) public enum PreviewView.ImplementationMode {
+    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode COMPATIBLE;
+    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode PERFORMANCE;
+  }
+
+  @RequiresApi(21) public enum PreviewView.ScaleType {
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_CENTER;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_END;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_START;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_CENTER;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_END;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_START;
+  }
+
+  public enum PreviewView.StreamState {
+    enum_constant public static final androidx.camera.view.PreviewView.StreamState IDLE;
+    enum_constant public static final androidx.camera.view.PreviewView.StreamState STREAMING;
+  }
+
+  @RequiresApi(21) public final class RotationProvider {
+    ctor public RotationProvider(android.content.Context);
+    method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
+    method public void removeListener(androidx.camera.view.RotationProvider.Listener);
+  }
+
+  public static interface RotationProvider.Listener {
+    method public void onRotationChanged(int);
+  }
+
+  @RequiresApi(21) public final class ScreenFlashView extends android.view.View {
+    ctor @UiThread public ScreenFlashView(android.content.Context);
+    ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?);
+    ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int);
+    ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int, int);
+    method @UiThread public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+    method @UiThread public void setController(androidx.camera.view.CameraController?);
+    method @UiThread public void setScreenFlashWindow(android.view.Window?);
+  }
+
+}
+
+package androidx.camera.view.transform {
+
+  @SuppressCompatibility @RequiresApi(21) public final class CoordinateTransform {
+    ctor public CoordinateTransform(androidx.camera.view.transform.OutputTransform, androidx.camera.view.transform.OutputTransform);
+    method public void mapPoint(android.graphics.PointF);
+    method public void mapPoints(float[]);
+    method public void mapRect(android.graphics.RectF);
+    method public void transform(android.graphics.Matrix);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) public final class FileTransformFactory {
+    ctor public FileTransformFactory();
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(android.content.ContentResolver, android.net.Uri) throws java.io.IOException;
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.File) throws java.io.IOException;
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.InputStream) throws java.io.IOException;
+    method public boolean isUsingExifOrientation();
+    method public void setUsingExifOrientation(boolean);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) public final class ImageProxyTransformFactory {
+    ctor public ImageProxyTransformFactory();
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(androidx.camera.core.ImageProxy);
+    method public boolean isUsingCropRect();
+    method public boolean isUsingRotationDegrees();
+    method public void setUsingCropRect(boolean);
+    method public void setUsingRotationDegrees(boolean);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) public final class OutputTransform {
+  }
+
+}
+
+package androidx.camera.view.video {
+
+  @RequiresApi(21) public class AudioConfig {
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
+    method public boolean getAudioEnabled();
+    field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
+  }
+
+}
+
diff --git a/camera/camera-view/api/res-1.4.0-beta01.txt b/camera/camera-view/api/res-1.4.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-view/api/res-1.4.0-beta01.txt
diff --git a/camera/camera-view/api/restricted_1.4.0-beta01.txt b/camera/camera-view/api/restricted_1.4.0-beta01.txt
new file mode 100644
index 0000000..ea8f480
--- /dev/null
+++ b/camera/camera-view/api/restricted_1.4.0-beta01.txt
@@ -0,0 +1,199 @@
+// Signature format: 4.0
+package androidx.camera.view {
+
+  @RequiresApi(21) public abstract class CameraController {
+    method @MainThread public void clearEffects();
+    method @MainThread public void clearImageAnalysisAnalyzer();
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> enableTorch(boolean);
+    method @MainThread public androidx.camera.core.CameraControl? getCameraControl();
+    method @MainThread public androidx.camera.core.CameraInfo? getCameraInfo();
+    method @MainThread public androidx.camera.core.CameraSelector getCameraSelector();
+    method @MainThread public java.util.concurrent.Executor? getImageAnalysisBackgroundExecutor();
+    method @MainThread public int getImageAnalysisBackpressureStrategy();
+    method @MainThread public int getImageAnalysisImageQueueDepth();
+    method @MainThread public int getImageAnalysisOutputImageFormat();
+    method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageAnalysisResolutionSelector();
+    method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageAnalysisTargetSize();
+    method @MainThread public int getImageCaptureFlashMode();
+    method @MainThread public java.util.concurrent.Executor? getImageCaptureIoExecutor();
+    method @MainThread public int getImageCaptureMode();
+    method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getImageCaptureResolutionSelector();
+    method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getImageCaptureTargetSize();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> getInitializationFuture();
+    method @MainThread public androidx.camera.core.resolutionselector.ResolutionSelector? getPreviewResolutionSelector();
+    method @Deprecated @MainThread public androidx.camera.view.CameraController.OutputSize? getPreviewTargetSize();
+    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTapToFocusState();
+    method @MainThread public androidx.lifecycle.LiveData<java.lang.Integer!> getTorchState();
+    method @MainThread public androidx.camera.core.DynamicRange getVideoCaptureDynamicRange();
+    method @MainThread public int getVideoCaptureMirrorMode();
+    method @MainThread public androidx.camera.video.QualitySelector getVideoCaptureQualitySelector();
+    method @MainThread public android.util.Range<java.lang.Integer!> getVideoCaptureTargetFrameRate();
+    method @MainThread public androidx.lifecycle.LiveData<androidx.camera.core.ZoomState!> getZoomState();
+    method @MainThread public boolean hasCamera(androidx.camera.core.CameraSelector);
+    method @MainThread public boolean isImageAnalysisEnabled();
+    method @MainThread public boolean isImageCaptureEnabled();
+    method @MainThread public boolean isPinchToZoomEnabled();
+    method @MainThread public boolean isRecording();
+    method @MainThread public boolean isTapToFocusEnabled();
+    method @MainThread public boolean isVideoCaptureEnabled();
+    method @MainThread public void setCameraSelector(androidx.camera.core.CameraSelector);
+    method @MainThread public void setEffects(java.util.Set<androidx.camera.core.CameraEffect!>);
+    method @MainThread public void setEnabledUseCases(int);
+    method @MainThread public void setImageAnalysisAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
+    method @MainThread public void setImageAnalysisBackgroundExecutor(java.util.concurrent.Executor?);
+    method @MainThread public void setImageAnalysisBackpressureStrategy(int);
+    method @MainThread public void setImageAnalysisImageQueueDepth(int);
+    method @MainThread public void setImageAnalysisOutputImageFormat(int);
+    method @MainThread public void setImageAnalysisResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+    method @Deprecated @MainThread public void setImageAnalysisTargetSize(androidx.camera.view.CameraController.OutputSize?);
+    method @MainThread public void setImageCaptureFlashMode(int);
+    method @MainThread public void setImageCaptureIoExecutor(java.util.concurrent.Executor?);
+    method @MainThread public void setImageCaptureMode(int);
+    method @MainThread public void setImageCaptureResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+    method @Deprecated @MainThread public void setImageCaptureTargetSize(androidx.camera.view.CameraController.OutputSize?);
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setLinearZoom(@FloatRange(from=0.0f, to=1.0f) float);
+    method @MainThread public void setPinchToZoomEnabled(boolean);
+    method @MainThread public void setPreviewResolutionSelector(androidx.camera.core.resolutionselector.ResolutionSelector?);
+    method @Deprecated @MainThread public void setPreviewTargetSize(androidx.camera.view.CameraController.OutputSize?);
+    method @MainThread public void setTapToFocusEnabled(boolean);
+    method @MainThread public void setVideoCaptureDynamicRange(androidx.camera.core.DynamicRange);
+    method @MainThread public void setVideoCaptureMirrorMode(int);
+    method @MainThread public void setVideoCaptureQualitySelector(androidx.camera.video.QualitySelector);
+    method @MainThread public void setVideoCaptureTargetFrameRate(android.util.Range<java.lang.Integer!>);
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setZoomRatio(float);
+    method @MainThread @RequiresApi(26) public androidx.camera.video.Recording startRecording(androidx.camera.video.FileDescriptorOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.FileOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread public androidx.camera.video.Recording startRecording(androidx.camera.video.MediaStoreOutputOptions, androidx.camera.view.video.AudioConfig, java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.camera.video.VideoRecordEvent!>);
+    method @MainThread public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
+    method @MainThread public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
+    field public static final int COORDINATE_SYSTEM_VIEW_REFERENCED = 1; // 0x1
+    field public static final int IMAGE_ANALYSIS = 2; // 0x2
+    field public static final int IMAGE_CAPTURE = 1; // 0x1
+    field public static final int TAP_TO_FOCUS_FAILED = 4; // 0x4
+    field public static final int TAP_TO_FOCUS_FOCUSED = 2; // 0x2
+    field public static final int TAP_TO_FOCUS_NOT_FOCUSED = 3; // 0x3
+    field public static final int TAP_TO_FOCUS_NOT_STARTED = 0; // 0x0
+    field public static final int TAP_TO_FOCUS_STARTED = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 4; // 0x4
+  }
+
+  @Deprecated @RequiresApi(21) public static final class CameraController.OutputSize {
+    ctor @Deprecated public CameraController.OutputSize(android.util.Size);
+    ctor @Deprecated public CameraController.OutputSize(int);
+    method @Deprecated public int getAspectRatio();
+    method @Deprecated public android.util.Size? getResolution();
+    field @Deprecated public static final int UNASSIGNED_ASPECT_RATIO = -1; // 0xffffffff
+  }
+
+  @RequiresApi(21) public final class LifecycleCameraController extends androidx.camera.view.CameraController {
+    ctor public LifecycleCameraController(android.content.Context);
+    method @MainThread public void bindToLifecycle(androidx.lifecycle.LifecycleOwner);
+    method @MainThread public void unbind();
+  }
+
+  @RequiresApi(21) public final class PreviewView extends android.widget.FrameLayout {
+    ctor @UiThread public PreviewView(android.content.Context);
+    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?);
+    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int);
+    ctor @UiThread public PreviewView(android.content.Context, android.util.AttributeSet?, int, int);
+    method @UiThread public android.graphics.Bitmap? getBitmap();
+    method @UiThread public androidx.camera.view.CameraController? getController();
+    method @UiThread public androidx.camera.view.PreviewView.ImplementationMode getImplementationMode();
+    method @UiThread public androidx.camera.core.MeteringPointFactory getMeteringPointFactory();
+    method @SuppressCompatibility public androidx.camera.view.transform.OutputTransform? getOutputTransform();
+    method public androidx.lifecycle.LiveData<androidx.camera.view.PreviewView.StreamState!> getPreviewStreamState();
+    method @UiThread public androidx.camera.view.PreviewView.ScaleType getScaleType();
+    method @UiThread public android.graphics.Matrix? getSensorToViewTransform();
+    method @UiThread public androidx.camera.core.Preview.SurfaceProvider getSurfaceProvider();
+    method @UiThread public androidx.camera.core.ViewPort? getViewPort();
+    method @UiThread public androidx.camera.core.ViewPort? getViewPort(int);
+    method @UiThread public void setController(androidx.camera.view.CameraController?);
+    method @UiThread public void setImplementationMode(androidx.camera.view.PreviewView.ImplementationMode);
+    method @UiThread public void setScaleType(androidx.camera.view.PreviewView.ScaleType);
+    method @UiThread public void setScreenFlashWindow(android.view.Window?);
+  }
+
+  @RequiresApi(21) public enum PreviewView.ImplementationMode {
+    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode COMPATIBLE;
+    enum_constant public static final androidx.camera.view.PreviewView.ImplementationMode PERFORMANCE;
+  }
+
+  @RequiresApi(21) public enum PreviewView.ScaleType {
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_CENTER;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_END;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FILL_START;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_CENTER;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_END;
+    enum_constant public static final androidx.camera.view.PreviewView.ScaleType FIT_START;
+  }
+
+  public enum PreviewView.StreamState {
+    enum_constant public static final androidx.camera.view.PreviewView.StreamState IDLE;
+    enum_constant public static final androidx.camera.view.PreviewView.StreamState STREAMING;
+  }
+
+  @RequiresApi(21) public final class RotationProvider {
+    ctor public RotationProvider(android.content.Context);
+    method @CheckResult public boolean addListener(java.util.concurrent.Executor, androidx.camera.view.RotationProvider.Listener);
+    method public void removeListener(androidx.camera.view.RotationProvider.Listener);
+  }
+
+  public static interface RotationProvider.Listener {
+    method public void onRotationChanged(int);
+  }
+
+  @RequiresApi(21) public final class ScreenFlashView extends android.view.View {
+    ctor @UiThread public ScreenFlashView(android.content.Context);
+    ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?);
+    ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int);
+    ctor @UiThread public ScreenFlashView(android.content.Context, android.util.AttributeSet?, int, int);
+    method @UiThread public androidx.camera.core.ImageCapture.ScreenFlash? getScreenFlash();
+    method @UiThread public void setController(androidx.camera.view.CameraController?);
+    method @UiThread public void setScreenFlashWindow(android.view.Window?);
+  }
+
+}
+
+package androidx.camera.view.transform {
+
+  @SuppressCompatibility @RequiresApi(21) public final class CoordinateTransform {
+    ctor public CoordinateTransform(androidx.camera.view.transform.OutputTransform, androidx.camera.view.transform.OutputTransform);
+    method public void mapPoint(android.graphics.PointF);
+    method public void mapPoints(float[]);
+    method public void mapRect(android.graphics.RectF);
+    method public void transform(android.graphics.Matrix);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) public final class FileTransformFactory {
+    ctor public FileTransformFactory();
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(android.content.ContentResolver, android.net.Uri) throws java.io.IOException;
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.File) throws java.io.IOException;
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(java.io.InputStream) throws java.io.IOException;
+    method public boolean isUsingExifOrientation();
+    method public void setUsingExifOrientation(boolean);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) public final class ImageProxyTransformFactory {
+    ctor public ImageProxyTransformFactory();
+    method public androidx.camera.view.transform.OutputTransform getOutputTransform(androidx.camera.core.ImageProxy);
+    method public boolean isUsingCropRect();
+    method public boolean isUsingRotationDegrees();
+    method public void setUsingCropRect(boolean);
+    method public void setUsingRotationDegrees(boolean);
+  }
+
+  @SuppressCompatibility @RequiresApi(21) public final class OutputTransform {
+  }
+
+}
+
+package androidx.camera.view.video {
+
+  @RequiresApi(21) public class AudioConfig {
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public static androidx.camera.view.video.AudioConfig create(boolean);
+    method public boolean getAudioEnabled();
+    field public static final androidx.camera.view.video.AudioConfig AUDIO_DISABLED;
+  }
+
+}
+
diff --git a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorScreen.kt b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorScreen.kt
index eabc522b..cdc6214 100644
--- a/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorScreen.kt
+++ b/camera/integration-tests/avsynctestapp/src/main/java/androidx/camera/integration/avsync/SignalGeneratorScreen.kt
@@ -37,11 +37,11 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
 
 @Composable
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
index 4c33201..441d427 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -19,7 +19,10 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import android.annotation.SuppressLint;
 import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraCharacteristics;
+import android.os.Build;
 import android.os.Bundle;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
@@ -41,6 +44,7 @@
 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.MeteringPoint;
+import androidx.camera.core.PhysicalCameraInfo;
 import androidx.camera.core.Preview;
 import androidx.camera.core.UseCaseGroup;
 import androidx.camera.lifecycle.ProcessCameraProvider;
@@ -50,6 +54,7 @@
 import androidx.core.math.MathUtils;
 import androidx.lifecycle.LifecycleOwner;
 
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -75,6 +80,7 @@
     @NonNull private ToggleButton mModeButton;
     @NonNull private ToggleButton mLayoutButton;
     @NonNull private ToggleButton mToggleButton;
+    @NonNull private ToggleButton mDualSelfieButton;
     @NonNull private LinearLayout mSideBySideLayout;
     @NonNull private FrameLayout mPiPLayout;
     @Nullable private ProcessCameraProvider mCameraProvider;
@@ -82,6 +88,8 @@
     private boolean mIsLayoutPiP = true;
     private boolean mIsFrontPrimary = true;
 
+    private boolean mIsDualSelfieEnabled = false;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -96,6 +104,7 @@
         mModeButton = findViewById(R.id.mode_button);
         mLayoutButton = findViewById(R.id.layout_button);
         mToggleButton = findViewById(R.id.toggle_button);
+        mDualSelfieButton = findViewById(R.id.dual_selfie);
 
         boolean isConcurrentCameraSupported =
                 getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_CONCURRENT);
@@ -144,6 +153,10 @@
                 bindPreviewForSingle(mCameraProvider);
             }
         });
+        mDualSelfieButton.setOnClickListener(view -> {
+            mIsDualSelfieEnabled = mDualSelfieButton.isChecked();
+            mDualSelfieButton.setChecked(mIsDualSelfieEnabled);
+        });
         if (allPermissionsGranted()) {
             if (mCameraProvider != null) {
                 mCameraProvider.unbindAll();
@@ -167,6 +180,8 @@
             }
         }, ContextCompat.getMainExecutor(this));
     }
+
+    @SuppressLint("RestrictedApiAndroidX")
     void bindPreviewForSingle(@NonNull ProcessCameraProvider cameraProvider) {
         cameraProvider.unbindAll();
         mSideBySideLayout.setVisibility(GONE);
@@ -186,13 +201,18 @@
         previewFront.setSurfaceProvider(mSinglePreviewView.getSurfaceProvider());
         Camera camera = cameraProvider.bindToLifecycle(
                 this, cameraSelectorFront, previewFront);
+        mDualSelfieButton.setVisibility(camera.getCameraInfo().isLogicalMultiCameraSupported()
+                ? VISIBLE : GONE);
+        mIsDualSelfieEnabled = false;
         setupZoomAndTapToFocus(camera, mSinglePreviewView);
     }
+
     void bindPreviewForPiP(@NonNull ProcessCameraProvider cameraProvider) {
         mSideBySideLayout.setVisibility(GONE);
         mFrontPreviewViewForPip.setVisibility(VISIBLE);
         mBackPreviewViewForPip.setVisibility(VISIBLE);
         mPiPLayout.setVisibility(VISIBLE);
+        mDualSelfieButton.setVisibility(GONE);
         if (mFrontPreviewView == null && mBackPreviewView == null) {
             // Front
             mFrontPreviewView = new PreviewView(this);
@@ -223,9 +243,11 @@
                     mBackPreviewView);
         }
     }
+
     void bindPreviewForSideBySide() {
         mSideBySideLayout.setVisibility(VISIBLE);
         mPiPLayout.setVisibility(GONE);
+        mDualSelfieButton.setVisibility(GONE);
         if (mFrontPreviewView == null && mBackPreviewView == null) {
             mFrontPreviewView = new PreviewView(this);
             mFrontPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
@@ -239,62 +261,121 @@
                 mFrontPreviewView,
                 mBackPreviewView);
     }
+
+    @SuppressLint("RestrictedApiAndroidX")
     private void bindToLifecycleForConcurrentCamera(
             @NonNull ProcessCameraProvider cameraProvider,
             @NonNull LifecycleOwner lifecycleOwner,
             @NonNull PreviewView frontPreviewView,
             @NonNull PreviewView backPreviewView) {
-        Preview previewFront = new Preview.Builder()
-                .build();
-        CameraSelector cameraSelectorPrimary = null;
-        CameraSelector cameraSelectorSecondary = null;
-        for (List<CameraInfo> cameraInfoList : cameraProvider.getAvailableConcurrentCameraInfos()) {
-            for (CameraInfo cameraInfo : cameraInfoList) {
+        if (mIsDualSelfieEnabled) {
+            CameraInfo cameraInfoPrimary = null;
+            for (CameraInfo cameraInfo : cameraProvider.getAvailableCameraInfos()) {
                 if (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) {
-                    cameraSelectorPrimary = cameraInfo.getCameraSelector();
-                } else if (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_BACK) {
-                    cameraSelectorSecondary = cameraInfo.getCameraSelector();
+                    cameraInfoPrimary = cameraInfo;
+                    break;
+                }
+            }
+            if (cameraInfoPrimary == null
+                    || cameraInfoPrimary.getPhysicalCameraInfos().size() != 2) {
+                return;
+            }
+
+            String innerPhysicalCameraId = null;
+            String outerPhysicalCameraId = null;
+            for (PhysicalCameraInfo info : cameraInfoPrimary.getPhysicalCameraInfos()) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                    if (info.getLensPoseReference()
+                            == CameraCharacteristics.LENS_POSE_REFERENCE_PRIMARY_CAMERA) {
+                        innerPhysicalCameraId = info.getPhysicalCameraId();
+                    } else {
+                        outerPhysicalCameraId = info.getPhysicalCameraId();
+                    }
                 }
             }
 
-            if (cameraSelectorPrimary == null || cameraSelectorSecondary == null) {
-                // If either a primary or secondary selector wasn't found, reset both
-                // to move on to the next list of CameraInfos.
-                cameraSelectorPrimary = null;
-                cameraSelectorSecondary = null;
-            } else {
-                // If both primary and secondary camera selectors were found, we can
-                // conclude the search.
-                break;
+            if (Objects.equal(innerPhysicalCameraId, outerPhysicalCameraId)) {
+                return;
             }
-        }
-        if (cameraSelectorPrimary == null || cameraSelectorSecondary == null) {
-            return;
-        }
-        previewFront.setSurfaceProvider(frontPreviewView.getSurfaceProvider());
-        SingleCameraConfig primary = new SingleCameraConfig(
-                cameraSelectorPrimary,
-                new UseCaseGroup.Builder()
-                        .addUseCase(previewFront)
-                        .build(),
-                lifecycleOwner);
-        Preview previewBack = new Preview.Builder()
-                .build();
-        previewBack.setSurfaceProvider(backPreviewView.getSurfaceProvider());
-        SingleCameraConfig secondary = new SingleCameraConfig(
-                cameraSelectorSecondary,
-                new UseCaseGroup.Builder()
-                        .addUseCase(previewBack)
-                        .build(),
-                lifecycleOwner);
-        ConcurrentCamera concurrentCamera =
-                cameraProvider.bindToLifecycle(ImmutableList.of(primary, secondary));
 
-        setupZoomAndTapToFocus(concurrentCamera.getCameras().get(0), frontPreviewView);
-        setupZoomAndTapToFocus(concurrentCamera.getCameras().get(1), backPreviewView);
+            Preview previewFront = new Preview.Builder()
+                    .build();
+            previewFront.setSurfaceProvider(frontPreviewView.getSurfaceProvider());
+            SingleCameraConfig primary = new SingleCameraConfig(
+                    new CameraSelector.Builder()
+                            .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
+                            .setPhysicalCameraId(innerPhysicalCameraId)
+                            .build(),
+                    new UseCaseGroup.Builder()
+                            .addUseCase(previewFront)
+                            .build(),
+                    lifecycleOwner);
+            Preview previewBack = new Preview.Builder()
+                    .build();
+            previewBack.setSurfaceProvider(backPreviewView.getSurfaceProvider());
+            SingleCameraConfig secondary = new SingleCameraConfig(
+                    new CameraSelector.Builder()
+                            .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
+                            .setPhysicalCameraId(outerPhysicalCameraId)
+                            .build(),
+                    new UseCaseGroup.Builder()
+                            .addUseCase(previewBack)
+                            .build(),
+                    lifecycleOwner);
+            cameraProvider.bindToLifecycle(ImmutableList.of(primary, secondary));
+        } else {
+            CameraSelector cameraSelectorPrimary = null;
+            CameraSelector cameraSelectorSecondary = null;
+            for (List<CameraInfo> cameraInfoList : cameraProvider
+                    .getAvailableConcurrentCameraInfos()) {
+                for (CameraInfo cameraInfo : cameraInfoList) {
+                    if (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) {
+                        cameraSelectorPrimary = cameraInfo.getCameraSelector();
+                    } else if (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_BACK) {
+                        cameraSelectorSecondary = cameraInfo.getCameraSelector();
+                    }
+                }
+
+                if (cameraSelectorPrimary == null || cameraSelectorSecondary == null) {
+                    // If either a primary or secondary selector wasn't found, reset both
+                    // to move on to the next list of CameraInfos.
+                    cameraSelectorPrimary = null;
+                    cameraSelectorSecondary = null;
+                } else {
+                    // If both primary and secondary camera selectors were found, we can
+                    // conclude the search.
+                    break;
+                }
+            }
+            if (cameraSelectorPrimary == null || cameraSelectorSecondary == null) {
+                return;
+            }
+            Preview previewFront = new Preview.Builder()
+                    .build();
+            previewFront.setSurfaceProvider(frontPreviewView.getSurfaceProvider());
+            SingleCameraConfig primary = new SingleCameraConfig(
+                    cameraSelectorPrimary,
+                    new UseCaseGroup.Builder()
+                            .addUseCase(previewFront)
+                            .build(),
+                    lifecycleOwner);
+            Preview previewBack = new Preview.Builder()
+                    .build();
+            previewBack.setSurfaceProvider(backPreviewView.getSurfaceProvider());
+            SingleCameraConfig secondary = new SingleCameraConfig(
+                    cameraSelectorSecondary,
+                    new UseCaseGroup.Builder()
+                            .addUseCase(previewBack)
+                            .build(),
+                    lifecycleOwner);
+            ConcurrentCamera concurrentCamera =
+                    cameraProvider.bindToLifecycle(ImmutableList.of(primary, secondary));
+
+            setupZoomAndTapToFocus(concurrentCamera.getCameras().get(0), frontPreviewView);
+            setupZoomAndTapToFocus(concurrentCamera.getCameras().get(1), backPreviewView);
+        }
     }
 
-
     private void setupZoomAndTapToFocus(Camera camera, PreviewView previewView) {
         ScaleGestureDetector scaleDetector = new ScaleGestureDetector(this,
                 new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@@ -330,6 +411,7 @@
             return true;
         });
     }
+
     private static void updateFrontAndBackView(
             boolean isFrontPrimary,
             @NonNull ViewGroup frontParent,
@@ -360,6 +442,7 @@
                             ViewGroup.LayoutParams.MATCH_PARENT));
         }
     }
+
     private boolean allPermissionsGranted() {
         for (String permission : REQUIRED_PERMISSIONS) {
             if (ContextCompat.checkSelfPermission(this, permission)
@@ -369,6 +452,7 @@
         }
         return true;
     }
+
     @Override
     public void onRequestPermissionsResult(int requestCode,
             @NonNull String[] permissions,
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
index 84a51b0..9e7449f 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
@@ -16,6 +16,10 @@
 
 package androidx.camera.integration.core;
 
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_OFF;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON;
+import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+
 import android.annotation.SuppressLint;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
@@ -62,6 +66,7 @@
     private static final String INTENT_EXTRA_DURATION = "recording_duration";
     // Launch the activity with the specified camera switching time.
     private static final String INTENT_EXTRA_SWITCH_TIME = "recording_switch_time";
+    private static final String INTENT_EXTRA_MIRROR_MODE = "mirror_mode";
     private static final long INVALID_TIME_VALUE = -1;
     private static final int REQUEST_CODE_PERMISSIONS = 1001;
     private static final String[] REQUIRED_PERMISSIONS = new String[] {
@@ -96,6 +101,7 @@
     private boolean mNotYetSwitched = true;
     private Integer mDeviceOrientation = null;
     private OrientationEventListener mOrientationEventListener;
+    private int mMirrorMode = MIRROR_MODE_OFF;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -108,7 +114,7 @@
         if (bundle != null) {
             String extraCameraDirection = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
             if (extraCameraDirection != null) {
-                if (extraCameraDirection.equals("FORWARD")) {
+                if (extraCameraDirection.equalsIgnoreCase("FORWARD")) {
                     mCameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA;
                 } else {
                     mCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
@@ -116,6 +122,14 @@
             }
             extraDurationMillis = bundle.getLong(INTENT_EXTRA_DURATION, INVALID_TIME_VALUE);
             extraSwitchTimeMillis = bundle.getLong(INTENT_EXTRA_SWITCH_TIME, INVALID_TIME_VALUE);
+            String mirrorMode = bundle.getString(INTENT_EXTRA_MIRROR_MODE, "OFF");
+            if (mirrorMode.equalsIgnoreCase("ON")) {
+                mMirrorMode = MIRROR_MODE_ON;
+            } else if (mirrorMode.equalsIgnoreCase("FRONT_ONLY")) {
+                mMirrorMode = MIRROR_MODE_ON_FRONT_ONLY;
+            } else {
+                mMirrorMode = MIRROR_MODE_OFF;
+            }
         }
 
         mOrientationEventListener = new OrientationEventListener(this) {
@@ -185,7 +199,8 @@
         mPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
         mPreview = new Preview.Builder().build();
         mPreview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
-        mVideoCapture = VideoCapture.withOutput(new Recorder.Builder().build());
+        mVideoCapture = new VideoCapture.Builder<>(new Recorder.Builder().build()).setMirrorMode(
+                mMirrorMode).build();
         mCamera = cameraProvider.bindToLifecycle(this, mCameraSelector, mPreview, mVideoCapture);
     }
 
diff --git a/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml b/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml
index 45e472a..9c75f56 100644
--- a/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml
@@ -122,6 +122,22 @@
                 android:layout_gravity="top|right"
                 android:layout_marginRight="15dp"
                 android:layout_marginTop="15dp" />
+
+            <ToggleButton
+                android:id="@+id/dual_selfie"
+                android:textOn="@string/dual_selfie_on"
+                android:textOff="@string/dual_selfie_off"
+                android:layout_width="46dp"
+                android:layout_height="wrap_content"
+                android:scaleType="fitXY"
+                android:textColor="#EEEEEE"
+                android:textSize="10dp"
+                android:checked="false"
+                android:background="@drawable/round_toggle_button"
+                android:visibility="gone"
+                android:layout_gravity="top|right"
+                android:layout_marginRight="15dp"
+                android:layout_marginTop="15dp" />
         </LinearLayout>
     </FrameLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/strings.xml b/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
index b4fd308..2a8d205 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
@@ -20,6 +20,8 @@
     <string name="switch_mode">Switch Mode</string>
     <string name="change_layout">Change Layout</string>
     <string name="toggle_camera">Toggle Camera</string>
+    <string name="dual_selfie_on">Dual Selfie On</string>
+    <string name="dual_selfie_off">Dual Selfie Off</string>
     <string name="toggle">Toggle</string>
     <string name="finish">Finish</string>
     <string name="is_front_primary">IsFrontPrimary</string>
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
index 7053aa6..e2b31f8 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/Camera2ExtensionsActivity.kt
@@ -1387,7 +1387,7 @@
 
         zoomRatio =
             (zoomRatio * scaleFactor).coerceIn(
-                1.0f,
+                ZoomUtil.minZoom(cameraManager.getCameraCharacteristics(currentCameraId)),
                 ZoomUtil.maxZoom(cameraManager.getCameraCharacteristics(currentCameraId))
             )
         Log.d(TAG, "onScale: $zoomRatio")
@@ -1428,8 +1428,11 @@
             return availableCaptureRequestKeys.contains(CaptureRequest.CONTROL_ZOOM_RATIO)
         }
 
+        fun minZoom(characteristics: CameraCharacteristics): Float =
+            characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)?.lower ?: 1.0f
+
         fun maxZoom(characteristics: CameraCharacteristics): Float =
-            characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 1.0f
+            characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)?.upper ?: 1.0f
     }
 }
 
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/imagecapture/ImageCaptureScreen.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/imagecapture/ImageCaptureScreen.kt
index 1f54e41..f6ed832 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/imagecapture/ImageCaptureScreen.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/imagecapture/ImageCaptureScreen.kt
@@ -55,10 +55,10 @@
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.Observer
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 private const val TAG = "ImageCaptureScreen"
 
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/videocapture/VideoCaptureScreen.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/videocapture/VideoCaptureScreen.kt
index 71f10a5..4bf2a6e 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/videocapture/VideoCaptureScreen.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/videocapture/VideoCaptureScreen.kt
@@ -47,10 +47,10 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.Observer
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 private const val TAG = "VideoCaptureScreen"
 
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/viewfinder/ViewfinderScreen.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/viewfinder/ViewfinderScreen.kt
index 0c95af8..73e13bb 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/viewfinder/ViewfinderScreen.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/viewfinder/ViewfinderScreen.kt
@@ -41,9 +41,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 private const val TAG = "ViewfinderScreen"
 
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 9c394df..61aef58 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.collection {
 
-  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K!,V!> implements java.util.Map<K!,V!> {
     ctor public ArrayMap();
     ctor public ArrayMap(androidx.collection.SimpleArrayMap?);
     ctor public ArrayMap(int);
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 042ced8..0c8d12c 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.collection {
 
-  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+  public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K!,V!> implements java.util.Map<K!,V!> {
     ctor public ArrayMap();
     ctor public ArrayMap(androidx.collection.SimpleArrayMap?);
     ctor public ArrayMap(int);
diff --git a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/TransitionTest.kt b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
index 308af1d..340a1c1 100644
--- a/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
+++ b/compose/animation/animation-core/src/androidInstrumentedTest/kotlin/androidx/compose/animation/core/TransitionTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.graphics.Color
@@ -32,7 +33,9 @@
 import junit.framework.TestCase.assertEquals
 import junit.framework.TestCase.assertFalse
 import junit.framework.TestCase.assertTrue
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -446,4 +449,90 @@
             assertEquals(0f, childTransitionFloat.value)
         }
     }
+
+    @OptIn(ExperimentalTransitionApi::class)
+    @Test
+    fun addAnimationToCompletedChildTransition() {
+        rule.mainClock.autoAdvance = false
+        var value1 = 0f
+        var value2 = 0f
+        var value3 = 0f
+        lateinit var coroutineScope: CoroutineScope
+        val state = MutableTransitionState(false)
+
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            val parent = rememberTransition(state)
+            value1 = parent.animateFloat({ tween(1600, easing = LinearEasing) }) {
+                if (it) 1000f else 0f
+            }.value
+
+            val child = parent.createChildTransition { it }
+            value2 = child.animateFloat({ tween(160, easing = LinearEasing) }) {
+                if (it) 1000f else 0f
+            }.value
+
+            value3 = if (!parent.targetState) {
+                child.animateFloat({ tween(160, easing = LinearEasing) }) {
+                    if (it) 0f else 1000f
+                }.value
+            } else {
+                0f
+            }
+        }
+        coroutineScope.launch {
+            state.targetState = true
+        }
+        rule.mainClock.advanceTimeByFrame() // wait for composition
+        rule.runOnIdle {
+            assertEquals(0f, value1, 0f)
+            assertEquals(0f, value2, 0f)
+            assertEquals(0f, value3, 0f)
+        }
+        rule.mainClock.advanceTimeByFrame() // latch the animation start value
+        rule.runOnIdle {
+            assertEquals(0f, value1, 0f)
+            assertEquals(0f, value2, 0f)
+            assertEquals(0f, value3, 0f)
+        }
+        rule.mainClock.advanceTimeByFrame() // first frame of animation
+        rule.runOnIdle {
+            assertEquals(10f, value1, 0.1f)
+            assertEquals(100f, value2, 0.1f)
+            assertEquals(0f, value3, 0f) // hasn't started yet
+        }
+        rule.mainClock.advanceTimeBy(160)
+        rule.runOnIdle {
+            assertEquals(110f, value1, 0.1f)
+            assertEquals(1000f, value2, 0f)
+            assertEquals(0f, value3, 0f) // hasn't started yet
+        }
+        coroutineScope.launch {
+            state.targetState = false
+        }
+        rule.mainClock.advanceTimeByFrame() // compose the change
+        rule.runOnIdle {
+            assertEquals(120f, value1, 0.1f)
+            assertEquals(1000f, value2, 0f)
+            assertEquals(0f, value3, 0f)
+        }
+        rule.mainClock.advanceTimeByFrame()
+        var prevValue1 = 120f
+        var prevValue2 = 1000f
+        rule.runOnIdle {
+            // value1 and value2 have spring interrupted values, so we can't
+            // easily know their exact values
+            assertTrue(value1 < prevValue1)
+            prevValue1 = value1
+            assertTrue(value2 < prevValue2)
+            prevValue2 = value2
+            assertEquals(100f, value3, 0.1f)
+        }
+        rule.mainClock.advanceTimeByFrame()
+        rule.runOnIdle {
+            assertTrue(value1 < prevValue1)
+            assertTrue(value2 < prevValue2)
+            assertEquals(200f, value3, 0.1f)
+        }
+    }
 }
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
index 585c768..c5494fc 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Transition.kt
@@ -34,6 +34,7 @@
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateObserver
 import androidx.compose.runtime.withFrameNanos
@@ -52,6 +53,7 @@
 import kotlin.math.roundToLong
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -877,20 +879,27 @@
  */
 // TODO: Support creating Transition outside of composition and support imperative use of Transition
 @Stable
-class Transition<S> @PublishedApi internal constructor(
+class Transition<S> internal constructor(
     private val transitionState: TransitionState<S>,
+    private val parentTransition: Transition<*>?,
     val label: String? = null
 ) {
+    @PublishedApi
+    internal constructor(
+        transitionState: TransitionState<S>,
+        label: String? = null
+    ) : this(transitionState, null, label)
+
     internal constructor(
         initialState: S,
         label: String?
-    ) : this(MutableTransitionState(initialState), label)
+    ) : this(MutableTransitionState(initialState), null, label)
 
     @PublishedApi
     internal constructor(
         transitionState: MutableTransitionState<S>,
         label: String? = null
-    ) : this(transitionState as TransitionState<S>, label)
+    ) : this(transitionState as TransitionState<S>, null, label)
 
     /**
      * Current state of the transition. This will always be the initialState of the transition
@@ -920,19 +929,30 @@
     val isRunning: Boolean
         get() = startTimeNanos != AnimationConstants.UnspecifiedTime
 
+    private var _playTimeNanos by mutableLongStateOf(0L)
+
     /**
      * Play time in nano-seconds. [playTimeNanos] is always non-negative. It starts from 0L at the
      * beginning of the transition and increment until all child animations have finished.
      */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY)
     @set:RestrictTo(RestrictTo.Scope.LIBRARY)
-    var playTimeNanos by mutableLongStateOf(0L)
+    var playTimeNanos: Long
+        get() {
+            return parentTransition?.playTimeNanos ?: _playTimeNanos
+        }
+        set(value) {
+            if (parentTransition == null) {
+                _playTimeNanos = value
+            }
+        }
+
     // startTimeNanos is in real frame time nanos for the root transition and
     // scaled frame time for child transitions (as offset from the root start)
     internal var startTimeNanos by mutableLongStateOf(AnimationConstants.UnspecifiedTime)
 
     // This gets calculated every time child is updated/added
-    internal var updateChildrenNeeded: Boolean by mutableStateOf(true)
+    private var updateChildrenNeeded: Boolean by mutableStateOf(false)
 
     private val _animations = mutableStateListOf<TransitionAnimationState<*, *>>()
     private val _transitions = mutableStateListOf<Transition<*>>()
@@ -1009,6 +1029,7 @@
         } else {
             (deltaT / durationScale).roundToLong()
         }
+        playTimeNanos = scaledPlayTimeNanos
         onFrame(scaledPlayTimeNanos, durationScale == 0f)
     }
 
@@ -1020,8 +1041,6 @@
         }
         updateChildrenNeeded = false
 
-        // Update play time
-        playTimeNanos = scaledPlayTimeNanos
         var allFinished = true
         // Pulse new playtime
         _animations.fastForEach {
@@ -1176,21 +1195,34 @@
     internal fun animateTo(targetState: S) {
         if (!isSeeking) {
             updateTarget(targetState)
-            // target != currentState adds LaunchedEffect into the tree in the same frame as
+            // target != currentState adds the effect into the tree in the same frame as
             // target change.
             if (targetState != currentState || isRunning || updateChildrenNeeded) {
-                LaunchedEffect(this) {
-                    while (true) {
+                // We're using a composition-obtained scope + DisposableEffect here to give us
+                // control over coroutine dispatching
+                val coroutineScope = rememberCoroutineScope()
+                DisposableEffect(coroutineScope, this) {
+                    // Launch the coroutine undispatched so the block is executed in the current
+                    // frame. This is important as this initializes the state.
+                    coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
                         val durationScale = coroutineContext.durationScale
                         withFrameNanos {
-                            // This check is very important, as isSeeking may be changed off-band
-                            // between the last check in composition and this callback which
-                            // happens in the animation callback the next frame.
                             if (!isSeeking) {
                                 onFrame(it / AnimationDebugDurationScale, durationScale)
                             }
                         }
+                        while (isRunning) {
+                            withFrameNanos {
+                                // This check is very important, as isSeeking may be changed
+                                // off-band between the last check in composition and this callback
+                                // which happens in the animation callback the next frame.
+                                if (!isSeeking) {
+                                    onFrame(it / AnimationDebugDurationScale, durationScale)
+                                }
+                            }
+                        }
                     }
+                    onDispose { }
                 }
             }
         }
@@ -1767,11 +1799,7 @@
     childLabel: String,
 ): Transition<T> {
     val transition = remember(this) {
-        Transition(MutableTransitionState(initialState), "${this.label} > $childLabel").also {
-            // By setting these now, we don't have to wait a frame for the animation to start.
-            it.startTimeNanos = playTimeNanos
-            it.playTimeNanos = playTimeNanos
-        }
+        Transition(MutableTransitionState(initialState), this, "${this.label} > $childLabel")
     }
 
     DisposableEffect(transition) {
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimateItemPlacement.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimateItemPlacement.kt
index 493556a..9e06d34 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimateItemPlacement.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/lookahead/LookaheadWithAnimateItemPlacement.kt
@@ -42,7 +42,7 @@
 @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
 @Preview
 @Composable
-fun LookaheadWithLAnimateItemPlacement() {
+fun LookaheadWithAnimateItem() {
     val visible by produceState(true) {
         while (true) {
             delay(2000)
@@ -54,7 +54,7 @@
             items(3, key = { it }) {
                 Column(
                     Modifier
-                        .animateItemPlacement()
+                        .animateItem()
                         .clip(RoundedCornerShape(15.dp))
                         .background(turquoiseColors[it])
 
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index 00830a4..e00da50 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -1046,6 +1046,7 @@
         """,
         """
             public abstract class CompositionLocal2 {
+              PERMITTEDSUBCLASS ProvidableCompositionLocal2
               private <init>()V
               public final getCurrent(Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
               public final foo(Landroidx/compose/runtime/Composer;I)V
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
index 8498808..d3c67fb 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
@@ -114,7 +114,7 @@
                 put(CommonConfigurationKeys.MODULE_NAME, TEST_MODULE_NAME)
                 put(JVMConfigurationKeys.IR, true)
                 put(JVMConfigurationKeys.VALIDATE_IR, true)
-                put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
+                put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_17)
                 put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, TestMessageCollector)
                 put(IrMessageLogger.IR_MESSAGE_LOGGER, IrMessageCollector(TestMessageCollector))
                 updateConfiguration()
diff --git a/compose/compiler/compiler-hosted/runtime-tests/build.gradle b/compose/compiler/compiler-hosted/runtime-tests/build.gradle
index cb9c454..095b280 100644
--- a/compose/compiler/compiler-hosted/runtime-tests/build.gradle
+++ b/compose/compiler/compiler-hosted/runtime-tests/build.gradle
@@ -22,7 +22,7 @@
 }
 
 androidXMultiplatform {
-    jvm()
+    desktop()
 
     sourceSets {
         commonMain {
@@ -39,13 +39,13 @@
             }
         }
 
-        jvmMain {
+        desktopMain {
             dependsOn(commonMain)
             dependencies {
             }
         }
 
-        jvmTest {
+        desktopTest {
             dependsOn(commonTest)
             dependencies {
             }
diff --git a/compose/compiler/compiler-hosted/runtime-tests/src/jvmTest/kotlin/androidx/compose/compiler/test/JvmCompositionTests.kt b/compose/compiler/compiler-hosted/runtime-tests/src/desktopTest/kotlin/androidx/compose/compiler/test/JvmCompositionTests.kt
similarity index 100%
rename from compose/compiler/compiler-hosted/runtime-tests/src/jvmTest/kotlin/androidx/compose/compiler/test/JvmCompositionTests.kt
rename to compose/compiler/compiler-hosted/runtime-tests/src/desktopTest/kotlin/androidx/compose/compiler/test/JvmCompositionTests.kt
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
index 1059872..ae7286b 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposeIrGenerationExtension.kt
@@ -36,6 +36,8 @@
 import androidx.compose.compiler.plugins.kotlin.lower.decoys.CreateDecoysTransformer
 import androidx.compose.compiler.plugins.kotlin.lower.decoys.RecordDecoySignaturesTransformer
 import androidx.compose.compiler.plugins.kotlin.lower.decoys.SubstituteDecoyCallsTransformer
+import androidx.compose.compiler.plugins.kotlin.lower.hiddenfromobjc.AddHiddenFromObjCLowering
+import androidx.compose.compiler.plugins.kotlin.lower.hiddenfromobjc.HideFromObjCDeclarationsSet
 import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
 import org.jetbrains.kotlin.backend.common.serialization.DeclarationTable
@@ -47,6 +49,7 @@
 import org.jetbrains.kotlin.ir.visitors.acceptVoid
 import org.jetbrains.kotlin.platform.isJs
 import org.jetbrains.kotlin.platform.jvm.isJvm
+import org.jetbrains.kotlin.platform.konan.isNative
 
 class ComposeIrGenerationExtension(
     @Suppress("unused") private val liveLiteralsEnabled: Boolean = false,
@@ -63,7 +66,8 @@
     private val useK2: Boolean = false,
     private val strongSkippingEnabled: Boolean = false,
     private val stableTypeMatchers: Set<FqNameMatcher> = emptySet(),
-    private val moduleMetricsFactory: ((StabilityInferencer) -> ModuleMetrics)? = null
+    private val moduleMetricsFactory: ((StabilityInferencer) -> ModuleMetrics)? = null,
+    private val hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet? = null,
 ) : IrGenerationExtension {
     var metrics: ModuleMetrics = EmptyModuleMetrics
         private set
@@ -100,6 +104,16 @@
             }
         }
 
+        if (pluginContext.platform.isNative() && hideFromObjCDeclarationsSet != null) {
+            AddHiddenFromObjCLowering(
+                pluginContext,
+                symbolRemapper,
+                metrics,
+                hideFromObjCDeclarationsSet,
+                stabilityInferencer
+            ).lower(moduleFragment)
+        }
+
         ClassStabilityTransformer(
             useK2,
             pluginContext,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index 08676bb..0cb9943 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -26,6 +26,8 @@
 import androidx.compose.compiler.plugins.kotlin.k1.ComposeTypeResolutionInterceptorExtension
 import androidx.compose.compiler.plugins.kotlin.k2.ComposeFirExtensionRegistrar
 import androidx.compose.compiler.plugins.kotlin.lower.ClassStabilityFieldSerializationPlugin
+import androidx.compose.compiler.plugins.kotlin.lower.hiddenfromobjc.AddHiddenFromObjCSerializationPlugin
+import androidx.compose.compiler.plugins.kotlin.lower.hiddenfromobjc.HideFromObjCDeclarationsSet
 import com.intellij.mock.MockProject
 import com.intellij.openapi.project.Project
 import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
@@ -86,6 +88,9 @@
         )
     val TRACE_MARKERS_ENABLED_KEY =
         CompilerConfigurationKey<Boolean>("Include composition trace markers in generated code")
+    val HIDE_FROM_OBJC_ENABLED_KEY =
+        CompilerConfigurationKey<Boolean>(
+            "Add HiddenFromObjC annotation to @Composable declarations")
 }
 
 @OptIn(ExperimentalCompilerApi::class)
@@ -163,6 +168,13 @@
             required = false,
             allowMultipleOccurrences = false
         )
+        val HIDE_FROM_OBJC_OPTION = CliOption(
+            "hideFromObjC",
+            "<true|false>",
+            "Add HiddenFromObjC annotation to Composable declarations",
+            required = false,
+            allowMultipleOccurrences = false
+        )
         val STRONG_SKIPPING_OPTION = CliOption(
             "experimentalStrongSkipping",
             "<true|false>",
@@ -200,7 +212,8 @@
         DECOYS_ENABLED_OPTION,
         STRONG_SKIPPING_OPTION,
         STABLE_CONFIG_PATH_OPTION,
-        TRACE_MARKERS_OPTION
+        TRACE_MARKERS_OPTION,
+        HIDE_FROM_OBJC_OPTION,
     )
 
     override fun processOption(
@@ -248,6 +261,10 @@
             ComposeConfiguration.DECOYS_ENABLED_KEY,
             value == "true"
         )
+        HIDE_FROM_OBJC_OPTION -> configuration.put(
+            ComposeConfiguration.HIDE_FROM_OBJC_ENABLED_KEY,
+            value == "true"
+        )
         STRONG_SKIPPING_OPTION -> configuration.put(
             ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY,
             value == "true"
@@ -276,10 +293,27 @@
         configuration: CompilerConfiguration
     ) {
         if (checkCompilerVersion(configuration)) {
+
             registerCommonExtensions(project)
+
+            val hideFromObjC = configuration.get(
+                ComposeConfiguration.HIDE_FROM_OBJC_ENABLED_KEY,
+                true
+            )
+            val hideFromObjCDeclarationsSet = if (hideFromObjC) {
+                val set = HideFromObjCDeclarationsSet()
+                registerNativeExtensions(project, set)
+                set
+            } else {
+                null
+            }
+
             IrGenerationExtension.registerExtension(
                 project,
-                createComposeIrExtension(configuration)
+                createComposeIrExtension(
+                    configuration,
+                    hideFromObjCDeclarationsSet = hideFromObjCDeclarationsSet
+                )
             )
         }
     }
@@ -388,8 +422,21 @@
             FirExtensionRegistrarAdapter.registerExtension(project, ComposeFirExtensionRegistrar())
         }
 
+        fun registerNativeExtensions(
+            project: Project,
+            hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet?
+        ) {
+            if (hideFromObjCDeclarationsSet != null) {
+                DescriptorSerializerPlugin.registerExtension(
+                    project,
+                    AddHiddenFromObjCSerializationPlugin(hideFromObjCDeclarationsSet)
+                )
+            }
+        }
+
         fun createComposeIrExtension(
             configuration: CompilerConfiguration,
+            hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet? = null,
             moduleMetricsFactory: ((StabilityInferencer) -> ModuleMetrics)? = null
         ): ComposeIrGenerationExtension {
             val liveLiteralsEnabled = configuration.getBoolean(
@@ -477,7 +524,8 @@
                 useK2 = useK2,
                 strongSkippingEnabled = strongSkippingEnabled,
                 stableTypeMatchers = stableTypeMatchers,
-                moduleMetricsFactory = moduleMetricsFactory
+                moduleMetricsFactory = moduleMetricsFactory,
+                hideFromObjCDeclarationsSet = hideFromObjCDeclarationsSet,
             )
         }
     }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index cdcea5f..62bdfbf 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -134,6 +134,7 @@
             12200 to "1.7.0-alpha03",
             12300 to "1.7.0-alpha04",
             12400 to "1.7.0-alpha05",
+            12500 to "1.7.0-alpha06",
         )
 
         /**
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index 51fb33d..82dc8f6 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -63,6 +63,7 @@
 import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
 import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
 import org.jetbrains.kotlin.ir.declarations.IrProperty
+import org.jetbrains.kotlin.ir.declarations.IrScript
 import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
 import org.jetbrains.kotlin.ir.declarations.IrTypeAlias
 import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
@@ -1947,7 +1948,8 @@
             is IrAnonymousInitializer,
             is IrTypeParameter,
             is IrLocalDelegatedProperty,
-            is IrValueDeclaration -> {
+            is IrValueDeclaration,
+            is IrScript -> {
                 // these declarations do not create new "scopes", so we do nothing
                 return super.visitDeclaration(declaration)
             }
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 761309b..d9f37c7 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
@@ -69,6 +69,29 @@
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.types.Variance
 
+internal fun IrFunction.needsComposableRemapping(): Boolean {
+    if (
+        dispatchReceiverParameter?.type.containsComposableAnnotation() ||
+        extensionReceiverParameter?.type.containsComposableAnnotation() ||
+        returnType.containsComposableAnnotation()
+    ) return true
+
+    for (param in valueParameters) {
+        if (param.type.containsComposableAnnotation()) return true
+    }
+    return false
+}
+
+internal fun IrType?.containsComposableAnnotation(): Boolean {
+    if (this == null) return false
+    if (hasComposableAnnotation()) return true
+
+    return when (this) {
+        is IrSimpleType -> arguments.any { it.typeOrNull.containsComposableAnnotation() }
+        else -> false
+    }
+}
+
 internal class DeepCopyIrTreeWithRemappedComposableTypes(
     private val context: IrPluginContext,
     private val symbolRemapper: DeepCopySymbolRemapper,
@@ -216,27 +239,6 @@
         return super.visitTypeOperator(expression)
     }
 
-    private fun IrFunction.needsComposableRemapping(): Boolean {
-        if (
-            needsComposableRemapping(dispatchReceiverParameter?.type) ||
-            needsComposableRemapping(extensionReceiverParameter?.type) ||
-            needsComposableRemapping(returnType)
-        ) return true
-
-        for (param in valueParameters) {
-            if (needsComposableRemapping(param.type)) return true
-        }
-        return false
-    }
-
-    private fun needsComposableRemapping(type: IrType?): Boolean {
-        if (type == null) return false
-        if (type !is IrSimpleType) return false
-        if (type.hasComposableAnnotation()) return true
-        if (type.arguments.any { needsComposableRemapping(it.typeOrNull) }) return true
-        return false
-    }
-
     override fun visitDelegatingConstructorCall(
         expression: IrDelegatingConstructorCall
     ): IrDelegatingConstructorCall {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/AbstractDecoysLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/AbstractDecoysLowering.kt
index f232b69..171c6f4 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/AbstractDecoysLowering.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/decoys/AbstractDecoysLowering.kt
@@ -16,10 +16,10 @@
 
 package androidx.compose.compiler.plugins.kotlin.lower.decoys
 
-import androidx.compose.compiler.plugins.kotlin.ComposeFqNames
 import androidx.compose.compiler.plugins.kotlin.ModuleMetrics
 import androidx.compose.compiler.plugins.kotlin.analysis.StabilityInferencer
 import androidx.compose.compiler.plugins.kotlin.lower.AbstractComposeLowering
+import androidx.compose.compiler.plugins.kotlin.lower.containsComposableAnnotation
 import androidx.compose.compiler.plugins.kotlin.lower.includeFileNameInExceptionTrace
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
 import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureSerializer
@@ -28,10 +28,7 @@
 import org.jetbrains.kotlin.ir.declarations.IrFile
 import org.jetbrains.kotlin.ir.declarations.IrFunction
 import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
-import org.jetbrains.kotlin.ir.types.IrSimpleType
-import org.jetbrains.kotlin.ir.types.IrType
 import org.jetbrains.kotlin.ir.util.DeepCopySymbolRemapper
-import org.jetbrains.kotlin.ir.util.hasAnnotation
 import org.jetbrains.kotlin.ir.util.isEnumClass
 import org.jetbrains.kotlin.ir.util.isLocal
 import org.jetbrains.kotlin.ir.util.parentAsClass
@@ -77,20 +74,9 @@
         }
 
     private fun IrFunction.hasComposableParameter() =
-        valueParameters.any { it.type.hasComposable() } ||
-            extensionReceiverParameter?.type?.hasComposable() == true
+        valueParameters.any { it.type.containsComposableAnnotation() } ||
+            extensionReceiverParameter?.type.containsComposableAnnotation()
 
     private fun IrFunction.isEnumConstructor() =
         this is IrConstructor && parentAsClass.isEnumClass
-
-    private fun IrType.hasComposable(): Boolean {
-        if (hasAnnotation(ComposeFqNames.Composable)) {
-            return true
-        }
-
-        return when (this) {
-            is IrSimpleType -> arguments.any { (it as? IrType)?.hasComposable() == true }
-            else -> false
-        }
-    }
 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt
new file mode 100644
index 0000000..d749568
--- /dev/null
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCLowering.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.compiler.plugins.kotlin.lower.hiddenfromobjc
+
+import androidx.compose.compiler.plugins.kotlin.ModuleMetrics
+import androidx.compose.compiler.plugins.kotlin.analysis.StabilityInferencer
+import androidx.compose.compiler.plugins.kotlin.lower.AbstractComposeLowering
+import androidx.compose.compiler.plugins.kotlin.lower.ComposableSymbolRemapper
+import androidx.compose.compiler.plugins.kotlin.lower.containsComposableAnnotation
+import androidx.compose.compiler.plugins.kotlin.lower.needsComposableRemapping
+import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
+import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
+import org.jetbrains.kotlin.ir.IrStatement
+import org.jetbrains.kotlin.ir.declarations.IrDeclaration
+import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
+import org.jetbrains.kotlin.ir.declarations.IrProperty
+import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
+import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.types.defaultType
+import org.jetbrains.kotlin.ir.util.constructors
+import org.jetbrains.kotlin.ir.util.isLocal
+import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.platform.konan.isNative
+
+/**
+ *  AddHiddenFromObjCLowering looks for functions and properties with @Composable types and
+ *  adds the `kotlin.native.HiddenFromObjC` annotation to them.
+ *  @see https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native/-hidden-from-obj-c/
+ */
+class AddHiddenFromObjCLowering(
+    private val pluginContext: IrPluginContext,
+    symbolRemapper: ComposableSymbolRemapper,
+    metrics: ModuleMetrics,
+    private val hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet,
+    stabilityInferencer: StabilityInferencer,
+) : AbstractComposeLowering(pluginContext, symbolRemapper, metrics, stabilityInferencer) {
+
+    private val hiddenFromObjCAnnotation: IrClassSymbol by lazy {
+        getTopLevelClass(ClassId.fromString("kotlin/native/HiddenFromObjC"))
+    }
+
+    override fun lower(module: IrModuleFragment) {
+        require(context.platform.isNative()) {
+            "AddHiddenFromObjCLowering is expected to run only for k/native. " +
+                "The platform - ${context.platform}"
+        }
+        module.transformChildrenVoid(this)
+    }
+
+    override fun visitFunction(declaration: IrFunction): IrStatement {
+        val f = super.visitFunction(declaration) as IrFunction
+        if (f.isLocal ||
+            !(f.visibility == DescriptorVisibilities.PUBLIC ||
+                f.visibility == DescriptorVisibilities.PROTECTED))
+            return f
+
+        if (f.hasComposableAnnotation() || f.needsComposableRemapping()) {
+            f.addHiddenFromObjCAnnotation()
+            hideFromObjCDeclarationsSet.add(f)
+        }
+
+        return f
+    }
+
+    override fun visitProperty(declaration: IrProperty): IrStatement {
+        val p = super.visitProperty(declaration) as IrProperty
+        if (p.isLocal || p.visibility != DescriptorVisibilities.PUBLIC) return p
+
+        val shouldAdd = p.getter?.hasComposableAnnotation() ?: false ||
+            p.getter?.needsComposableRemapping() ?: false ||
+            p.backingField?.type.containsComposableAnnotation()
+
+        if (shouldAdd) {
+            p.addHiddenFromObjCAnnotation()
+            hideFromObjCDeclarationsSet.add(p)
+        }
+
+        return p
+    }
+
+    private fun IrDeclaration.addHiddenFromObjCAnnotation() {
+        val annotation = IrConstructorCallImpl.fromSymbolOwner(
+            type = hiddenFromObjCAnnotation.defaultType,
+            constructorSymbol = hiddenFromObjCAnnotation.constructors.first()
+        )
+        pluginContext.annotationsRegistrar.addMetadataVisibleAnnotationsToElement(this, annotation)
+    }
+}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt
new file mode 100644
index 0000000..62e06a8
--- /dev/null
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/AddHiddenFromObjCSerializationPlugin.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.compiler.plugins.kotlin.lower.hiddenfromobjc
+
+import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.PropertyDescriptor
+import org.jetbrains.kotlin.library.metadata.KlibMetadataSerializerProtocol
+import org.jetbrains.kotlin.metadata.ProtoBuf
+import org.jetbrains.kotlin.metadata.deserialization.Flags
+import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.serialization.DescriptorSerializer
+import org.jetbrains.kotlin.serialization.DescriptorSerializerPlugin
+import org.jetbrains.kotlin.serialization.SerializerExtension
+
+/**
+ * Adds the kotlin.native.HiddenFromObjC annotation to the descriptors of declarations
+ * in [hideFromObjCDeclarationsSet].
+ *
+ * @see [HideFromObjCDeclarationsSet]
+ */
+class AddHiddenFromObjCSerializationPlugin(
+    private val hideFromObjCDeclarationsSet: HideFromObjCDeclarationsSet
+) : DescriptorSerializerPlugin {
+
+    private val hasAnnotationFlag = Flags.HAS_ANNOTATIONS.toFlags(true)
+
+    private val annotationToAdd = ClassId.fromString("kotlin/native/HiddenFromObjC")
+
+    private fun createAnnotationProto(extension: SerializerExtension) =
+        ProtoBuf.Annotation.newBuilder().apply {
+            id = extension.stringTable.getQualifiedClassNameIndex(annotationToAdd)
+        }.build()
+
+    override fun afterConstructor(
+        descriptor: ConstructorDescriptor,
+        proto: ProtoBuf.Constructor.Builder,
+        versionRequirementTable: MutableVersionRequirementTable?,
+        childSerializer: DescriptorSerializer,
+        extension: SerializerExtension
+    ) {
+        if (descriptor in hideFromObjCDeclarationsSet) {
+            val annotationProto = createAnnotationProto(extension)
+            proto.addExtension(
+                KlibMetadataSerializerProtocol.constructorAnnotation,
+                annotationProto
+            )
+            proto.flags = proto.flags or hasAnnotationFlag
+        }
+    }
+
+    override fun afterFunction(
+        descriptor: FunctionDescriptor,
+        proto: ProtoBuf.Function.Builder,
+        versionRequirementTable: MutableVersionRequirementTable?,
+        childSerializer: DescriptorSerializer,
+        extension: SerializerExtension
+    ) {
+        if (descriptor in hideFromObjCDeclarationsSet) {
+            val annotationProto = createAnnotationProto(extension)
+            proto.addExtension(KlibMetadataSerializerProtocol.functionAnnotation, annotationProto)
+            proto.flags = proto.flags or hasAnnotationFlag
+        }
+    }
+
+    override fun afterProperty(
+        descriptor: PropertyDescriptor,
+        proto: ProtoBuf.Property.Builder,
+        versionRequirementTable: MutableVersionRequirementTable?,
+        childSerializer: DescriptorSerializer,
+        extension: SerializerExtension
+    ) {
+        if (descriptor in hideFromObjCDeclarationsSet) {
+            val annotationProto = createAnnotationProto(extension)
+            proto.addExtension(KlibMetadataSerializerProtocol.propertyAnnotation, annotationProto)
+            proto.flags = proto.flags or hasAnnotationFlag
+
+            // Add the annotation for the getter too if it's Composable
+            val getterDescriptor = descriptor.getter
+            if (getterDescriptor != null && getterDescriptor in hideFromObjCDeclarationsSet) {
+                val annotationForGetter = createAnnotationProto(extension)
+                proto.addExtension(
+                    KlibMetadataSerializerProtocol.propertyGetterAnnotation,
+                    annotationForGetter
+                )
+                proto.getterFlags = proto.getterFlags or hasAnnotationFlag
+            }
+
+            // Add the annotation for the setter too if it's Composable
+            val setterDescriptor = descriptor.getter
+            if (setterDescriptor != null && setterDescriptor in hideFromObjCDeclarationsSet) {
+                val annotationForSetter = createAnnotationProto(extension)
+                proto.addExtension(
+                    KlibMetadataSerializerProtocol.propertySetterAnnotation,
+                    annotationForSetter
+                )
+                proto.setterFlags = proto.setterFlags or hasAnnotationFlag
+            }
+        }
+    }
+}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt
new file mode 100644
index 0000000..ec2b064
--- /dev/null
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/hiddenfromobjc/HideFromObjCDeclarationsSet.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.compiler.plugins.kotlin.lower.hiddenfromobjc
+
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
+import org.jetbrains.kotlin.ir.declarations.IrFunction
+import org.jetbrains.kotlin.ir.declarations.IrProperty
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+
+/**
+ * Represents a set of declarations that should have
+ * kotlin.native.HiddenFromObjC annotation added in their IR and descriptors.
+ *
+ * It's used by [AddHiddenFromObjCSerializationPlugin] to determine
+ * if there's a need to modify the declaration descriptor before it's serialized.
+ * This set is populated by [AddHiddenFromObjCLowering].
+ *
+ * More context:
+ * The reason why we need this set is due to k/native ObjCExportMapper.kt is
+ * using descriptors to look at the declaration annotations.
+ * When ObjCExportMapper.kt migrates to FIR, we will need to simply remove this interface
+ * and [AddHiddenFromObjCSerializationPlugin].
+ * Adding the annotation in IR - [AddHiddenFromObjCLowering] will likely be enough.
+ */
+@OptIn(ObsoleteDescriptorBasedAPI::class)
+class HideFromObjCDeclarationsSet {
+
+    private val set = mutableSetOf<FqName>()
+
+    fun add(function: IrFunction) {
+        set.add(function.descriptor.fqNameSafe)
+    }
+
+    fun add(property: IrProperty) {
+        set.add(property.descriptor.fqNameSafe)
+    }
+
+    operator fun contains(item: DeclarationDescriptor): Boolean {
+        return set.contains(item.fqNameSafe)
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
index bc6097c..db46f25 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/ContextualFlowRowColumnTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -25,6 +26,7 @@
 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.Color
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.onGloballyPositioned
@@ -39,6 +41,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -421,6 +424,156 @@
     }
 
     @Test
+    fun testContextualFlowRow_horizontalArrangementStart_SeeMoreFullWidth() {
+        val eachSize = 50
+
+        val totalCount = 20
+        val positions: MutableList<Offset> = mutableListOf()
+        var seeMorePosition: Offset? = null
+        var seeMoreSize: IntSize? = null
+        var mainAxisSpacing = 10
+        var crossAxisSpacing = 20
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                var maxLines by remember {
+                    mutableStateOf(2)
+                }
+                Box(modifier = Modifier.width(320.dp).wrapContentHeight()) {
+                    ContextualFlowRow(
+                        itemCount = totalCount,
+                        Modifier
+                            .fillMaxWidth(1f)
+                            .wrapContentHeight(align = Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(mainAxisSpacing.dp),
+                        verticalArrangement = Arrangement.spacedBy(crossAxisSpacing.dp),
+                        maxLines = maxLines,
+                        overflow = ContextualFlowRowOverflow.expandIndicator {
+                            Box(modifier = Modifier
+                                .fillMaxWidth(1f)
+                                .height(eachSize.dp)
+                                .background(Color.Green)
+                                .clickable {
+                                    maxLines += 2
+                                }.onPlaced {
+                                    seeMorePosition = it.positionInParent()
+                                    seeMoreSize = it.size
+                                }
+                            ) {}
+                        },
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .width(eachSize.dp)
+                                .height(50.dp)
+                                .background(Color.Green)
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    positions.add(index, positionInParent)
+                                }
+                        ) {}
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        var expectedYPosition = 0
+        Truth.assertThat(positions.size).isEqualTo(5)
+        positions.forEach { position ->
+            Truth
+                .assertThat(position.x)
+                .isEqualTo(expectedXPosition)
+
+            Truth
+                .assertThat(position.y)
+                .isEqualTo(expectedYPosition)
+            expectedXPosition += eachSize + mainAxisSpacing
+        }
+        expectedYPosition += eachSize + crossAxisSpacing
+        Truth.assertThat(seeMorePosition?.x).isEqualTo(0)
+        Truth.assertThat(seeMorePosition?.y).isEqualTo(expectedYPosition)
+        Truth.assertThat(seeMoreSize?.width).isEqualTo(320)
+        Truth.assertThat(seeMoreSize?.height).isEqualTo(eachSize)
+    }
+
+    @Test
+    fun testContextualFlowColumn_verticalArrangementTop_SeeMoreFullHeight() {
+        val eachSize = 50
+
+        val totalCount = 20
+        val positions: MutableList<Offset> = mutableListOf()
+        var seeMorePosition: Offset? = null
+        var seeMoreSize: IntSize? = null
+        var mainAxisSpacing = 10
+        var crossAxisSpacing = 20
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                var maxLines by remember {
+                    mutableStateOf(2)
+                }
+                Box(modifier = Modifier.height(320.dp).wrapContentWidth()) {
+                    ContextualFlowColumn(
+                        itemCount = totalCount,
+                        Modifier
+                            .fillMaxHeight(1f)
+                            .wrapContentWidth(align = Alignment.Start),
+                        verticalArrangement = Arrangement.spacedBy(mainAxisSpacing.dp),
+                        horizontalArrangement = Arrangement.spacedBy(crossAxisSpacing.dp),
+                        maxLines = maxLines,
+                        overflow = ContextualFlowColumnOverflow.expandIndicator {
+                            Box(modifier = Modifier
+                                .fillMaxHeight(1f)
+                                .width(eachSize.dp)
+                                .clickable {
+                                    maxLines += 2
+                                }.onPlaced {
+                                    seeMorePosition = it.positionInParent()
+                                    seeMoreSize = it.size
+                                }
+                            ) {}
+                        }
+                    ) { index ->
+                        Box(
+                            Modifier
+                                .width(eachSize.dp)
+                                .height(eachSize.dp)
+                                .onPlaced {
+                                    val positionInParent = it.positionInParent()
+                                    positions.add(index, positionInParent)
+                                }
+                        ) {}
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        var expectedYPosition = 0
+        Truth.assertThat(positions.size).isEqualTo(5)
+        positions.forEach { position ->
+            Truth
+                .assertThat(position.x)
+                .isEqualTo(expectedXPosition)
+
+            Truth
+                .assertThat(position.y)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize + mainAxisSpacing
+        }
+        expectedXPosition += eachSize + crossAxisSpacing
+        Truth.assertThat(seeMorePosition?.x).isEqualTo(expectedXPosition)
+        Truth.assertThat(seeMorePosition?.y).isEqualTo(0)
+        Truth.assertThat(seeMoreSize?.height).isEqualTo(320)
+        Truth.assertThat(seeMoreSize?.width).isEqualTo(eachSize)
+    }
+
+    @Test
     fun testContextualFlowRow_equalHeight() {
         val listOfHeights = mutableListOf<Int>()
 
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
index 8a860f4..e6123e22 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -24,6 +25,7 @@
 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.Color
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.MultiContentMeasurePolicy
@@ -41,6 +43,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -1376,7 +1379,7 @@
                                 }
                                 .size(itemSize.dp)
                                 .testTag(seeMoreTag)
-                                .onPlaced {
+                                .onGloballyPositioned {
                                     seeMoreShown = true
                                 }
                         )
@@ -1455,7 +1458,7 @@
                                 }
                                 .size(itemSize.dp)
                                 .testTag(seeMoreTag)
-                                .onPlaced {
+                                .onGloballyPositioned {
                                     seeMoreShown = true
                                 }
                         )
@@ -2077,7 +2080,7 @@
                                     }
                                     .size(itemSize.dp)
                                     .testTag(seeMoreTag)
-                                    .onPlaced {
+                                    .onGloballyPositioned {
                                         seeMoreShown = true
                                     }
                             )
@@ -2095,7 +2098,7 @@
                                     }
                                     .size(collapseSize.dp)
                                     .testTag(collapseTag)
-                                    .onPlaced {
+                                    .onGloballyPositioned {
                                         collapseShown = true
                                     }
                             )
@@ -2217,7 +2220,7 @@
                                     }
                                     .size(itemSize.dp)
                                     .testTag(seeMoreTag)
-                                    .onPlaced {
+                                    .onGloballyPositioned {
                                         seeMoreShown = true
                                     }
                             )
@@ -2235,7 +2238,7 @@
                                     }
                                     .size(collapseSize.dp)
                                     .testTag(collapseTag)
-                                    .onPlaced {
+                                    .onGloballyPositioned {
                                         collapseShown = true
                                     }
                             )
@@ -2616,6 +2619,158 @@
     }
 
     @Test
+    fun testFlowRow_horizontalArrangementStart_SeeMoreFullWidth() {
+        val eachSize = 50
+
+        val totalCount = 20
+        val positions: MutableList<Offset> = mutableListOf()
+        var seeMorePosition: Offset? = null
+        var seeMoreSize: IntSize? = null
+        var mainAxisSpacing = 10
+        var crossAxisSpacing = 20
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                var maxLines by remember {
+                    mutableStateOf(2)
+                }
+                Box(modifier = Modifier.width(320.dp).wrapContentHeight()) {
+                    FlowRow(
+                        Modifier
+                            .fillMaxWidth(1f)
+                            .wrapContentHeight(align = Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(mainAxisSpacing.dp),
+                        verticalArrangement = Arrangement.spacedBy(crossAxisSpacing.dp),
+                        maxLines = maxLines,
+                        overflow = FlowRowOverflow.expandIndicator {
+                            Box(modifier = Modifier
+                                .fillMaxWidth(1f)
+                                .height(eachSize.dp)
+                                .background(Color.Green)
+                                .clickable {
+                                    maxLines += 2
+                                }.onPlaced {
+                                    seeMorePosition = it.positionInParent()
+                                    seeMoreSize = it.size
+                                }
+                            ) {}
+                        }
+                    ) {
+                        repeat(totalCount) { index ->
+                            Box(
+                                Modifier
+                                    .width(eachSize.dp)
+                                    .height(50.dp)
+                                    .background(Color.Green)
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        positions.add(index, positionInParent)
+                                    }
+                            ) {}
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        var expectedYPosition = 0
+        Truth.assertThat(positions.size).isEqualTo(5)
+        positions.forEach { position ->
+            Truth
+                .assertThat(position.x)
+                .isEqualTo(expectedXPosition)
+
+            Truth
+                .assertThat(position.y)
+                .isEqualTo(expectedYPosition)
+            expectedXPosition += eachSize + mainAxisSpacing
+        }
+        expectedYPosition += eachSize + crossAxisSpacing
+        Truth.assertThat(seeMorePosition?.x).isEqualTo(0)
+        Truth.assertThat(seeMorePosition?.y).isEqualTo(expectedYPosition)
+        Truth.assertThat(seeMoreSize?.width).isEqualTo(320)
+        Truth.assertThat(seeMoreSize?.height).isEqualTo(eachSize)
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementTop_SeeMoreFullHeight() {
+        val eachSize = 50
+
+        val totalCount = 20
+        val positions: MutableList<Offset> = mutableListOf()
+        var seeMorePosition: Offset? = null
+        var seeMoreSize: IntSize? = null
+        var mainAxisSpacing = 10
+        var crossAxisSpacing = 20
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                var maxLines by remember {
+                    mutableStateOf(2)
+                }
+                Box(modifier = Modifier.height(320.dp).wrapContentWidth()) {
+                    FlowColumn(
+                        Modifier
+                            .fillMaxHeight(1f)
+                            .wrapContentWidth(align = Alignment.Start),
+                        verticalArrangement = Arrangement.spacedBy(mainAxisSpacing.dp),
+                        horizontalArrangement = Arrangement.spacedBy(crossAxisSpacing.dp),
+                        maxLines = maxLines,
+                        overflow = FlowColumnOverflow.expandIndicator {
+                            Box(modifier = Modifier
+                                .fillMaxHeight(1f)
+                                .width(eachSize.dp)
+                                .clickable {
+                                    maxLines += 2
+                                }.onPlaced {
+                                    seeMorePosition = it.positionInParent()
+                                    seeMoreSize = it.size
+                                }
+                            ) {}
+                        }
+                    ) {
+                        repeat(totalCount) { index ->
+                            Box(
+                                Modifier
+                                    .width(eachSize.dp)
+                                    .height(eachSize.dp)
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        positions.add(index, positionInParent)
+                                    }
+                            ) {}
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        var expectedYPosition = 0
+        Truth.assertThat(positions.size).isEqualTo(5)
+        positions.forEach { position ->
+            Truth
+                .assertThat(position.x)
+                .isEqualTo(expectedXPosition)
+
+            Truth
+                .assertThat(position.y)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize + mainAxisSpacing
+        }
+        expectedXPosition += eachSize + crossAxisSpacing
+        Truth.assertThat(seeMorePosition?.x).isEqualTo(expectedXPosition)
+        Truth.assertThat(seeMorePosition?.y).isEqualTo(0)
+        Truth.assertThat(seeMoreSize?.height).isEqualTo(320)
+        Truth.assertThat(seeMoreSize?.width).isEqualTo(eachSize)
+    }
+
+    @Test
     fun testFlowRow_horizontalArrangementStart_MaxLines() {
         val eachSize = 20
         val maxItemsInMainAxis = 5
@@ -5679,7 +5834,7 @@
                             Box(
                                 Modifier
                                     .size(20.dp)
-                                    .onPlaced {
+                                    .onGloballyPositioned {
                                         itemShown = index + 1
                                     }
                             )
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/SizeTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/SizeTest.kt
index e0f239b..82927ce 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/SizeTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/SizeTest.kt
@@ -19,9 +19,11 @@
 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.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
@@ -60,7 +62,6 @@
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -1839,23 +1840,30 @@
         )
     }
 
-    @Ignore // b/281171119
     @Test
     fun testModifiers_doNotCauseUnnecessaryRemeasure() {
+        var drawLatch = CountDownLatch(1)
         var first by mutableStateOf(true)
         var totalMeasures = 0
         @Composable fun CountMeasures(modifier: Modifier) {
             Layout(
                 content = {},
                 modifier = modifier,
-                measurePolicy = { _, _ ->
-                    ++totalMeasures
-                    layout(0, 0) {}
+                // remember the measurePolicy so it doesn't change and cause a relayout when
+                // recomposed
+                measurePolicy = remember {
+                    { _, _ ->
+                        ++totalMeasures
+                        layout(0, 0) {}
+                    }
                 }
             )
         }
+        val drawLatchModifier = Modifier.drawBehind {
+            drawLatch.countDown()
+        }
         show {
-            Box {
+            Box(drawLatchModifier) {
                 if (first) Box {} else Row {}
                 CountMeasures(Modifier.size(10.dp))
                 CountMeasures(Modifier.requiredSize(10.dp))
@@ -1865,14 +1873,16 @@
             }
         }
 
-        val root = findComposeView()
-        waitForDraw(root)
+        assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
 
         activityTestRule.runOnUiThread {
             assertEquals(5, totalMeasures)
+            drawLatch = CountDownLatch(1)
             first = false
         }
 
+        assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+
         activityTestRule.runOnUiThread {
             assertEquals(5, totalMeasures)
         }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
index 05e5e55..dd2f5b2 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Column.kt
@@ -23,10 +23,18 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.Measured
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.VerticalAlignmentLine
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * A layout composable that places its children in a vertical sequence. For a layout composable
@@ -84,13 +92,9 @@
 }
 
 @PublishedApi
-internal val DefaultColumnMeasurePolicy: MeasurePolicy = RowColumnMeasurePolicy(
-    orientation = LayoutOrientation.Vertical,
+internal val DefaultColumnMeasurePolicy: MeasurePolicy = ColumnMeasurePolicy(
     verticalArrangement = Arrangement.Top,
-    horizontalArrangement = null,
-    arrangementSpacing = Arrangement.Top.spacing,
-    crossAxisAlignment = CrossAxisAlignment.horizontal(Alignment.Start),
-    crossAxisSize = SizeMode.Wrap
+    horizontalAlignment = Alignment.Start,
 )
 
 @PublishedApi
@@ -103,17 +107,178 @@
         DefaultColumnMeasurePolicy
     } else {
         remember(verticalArrangement, horizontalAlignment) {
-            RowColumnMeasurePolicy(
-                orientation = LayoutOrientation.Vertical,
+            ColumnMeasurePolicy(
                 verticalArrangement = verticalArrangement,
-                horizontalArrangement = null,
-                arrangementSpacing = verticalArrangement.spacing,
-                crossAxisAlignment = CrossAxisAlignment.horizontal(horizontalAlignment),
-                crossAxisSize = SizeMode.Wrap
+                horizontalAlignment = horizontalAlignment,
             )
         }
     }
 
+internal data class ColumnMeasurePolicy(
+    private val verticalArrangement: Arrangement.Vertical,
+    private val horizontalAlignment: Alignment.Horizontal
+) : MeasurePolicy, RowColumnMeasurePolicy {
+
+    override fun Placeable.mainAxisSize(): Int = height
+    override fun Placeable.crossAxisSize(): Int = width
+    override fun populateMainAxisPositions(
+        mainAxisLayoutSize: Int,
+        childrenMainAxisSize: IntArray,
+        mainAxisPositions: IntArray,
+        measureScope: MeasureScope
+    ) {
+        with(verticalArrangement) {
+            measureScope.arrange(
+                mainAxisLayoutSize,
+                childrenMainAxisSize,
+                mainAxisPositions
+            )
+        }
+    }
+
+    override fun placeHelper(
+        placeables: Array<Placeable?>,
+        measureScope: MeasureScope,
+        beforeCrossAxisAlignmentLine: Int,
+        mainAxisPositions: IntArray,
+        mainAxisLayoutSize: Int,
+        crossAxisLayoutSize: Int,
+        crossAxisOffset: IntArray?,
+        currentLineIndex: Int,
+        startIndex: Int,
+        endIndex: Int
+    ): MeasureResult {
+        return with(measureScope) {
+            layout(crossAxisLayoutSize, mainAxisLayoutSize) {
+                placeables.forEachIndexed { i, placeable ->
+                    val crossAxisPosition = getCrossAxisPosition(
+                        placeable!!,
+                        placeable.rowColumnParentData,
+                        crossAxisLayoutSize,
+                        beforeCrossAxisAlignmentLine,
+                        measureScope.layoutDirection
+                    )
+                    placeable.place(
+                        crossAxisPosition,
+                        mainAxisPositions[i],
+                 )
+                }
+            }
+        }
+    }
+
+    private fun getCrossAxisPosition(
+        placeable: Placeable,
+        parentData: RowColumnParentData?,
+        crossAxisLayoutSize: Int,
+        beforeCrossAxisAlignmentLine: Int,
+        layoutDirection: LayoutDirection
+    ): Int {
+        val childCrossAlignment = parentData?.crossAxisAlignment
+        return childCrossAlignment?.align(
+            size = crossAxisLayoutSize - placeable.width,
+            layoutDirection = layoutDirection,
+            placeable = placeable,
+            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
+        ) ?: horizontalAlignment.align(0, crossAxisLayoutSize - placeable.width,
+            layoutDirection)
+    }
+
+    override fun createConstraints(
+        mainAxisMin: Int,
+        crossAxisMin: Int,
+        mainAxisMax: Int,
+        crossAxisMax: Int,
+        isPrioritizing: Boolean
+    ): Constraints {
+        return createColumnConstraints(
+            isPrioritizing,
+            mainAxisMin,
+            crossAxisMin,
+            mainAxisMax,
+            crossAxisMax
+        )
+    }
+
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult {
+        return measure(
+            constraints.minHeight,
+            constraints.minWidth,
+            constraints.maxHeight,
+            constraints.maxWidth,
+            verticalArrangement.spacing.roundToPx(),
+            this,
+            measurables,
+            arrayOfNulls(measurables.size),
+            0,
+            measurables.size
+        )
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicWidth(
+        measurables: List<IntrinsicMeasurable>,
+        height: Int
+    ) = IntrinsicMeasureBlocks.VerticalMinWidth(
+        measurables,
+        height,
+        verticalArrangement.spacing.roundToPx(),
+    )
+
+    override fun IntrinsicMeasureScope.minIntrinsicHeight(
+        measurables: List<IntrinsicMeasurable>,
+        width: Int
+    ) = IntrinsicMeasureBlocks.VerticalMinHeight(
+        measurables,
+        width,
+        verticalArrangement.spacing.roundToPx(),
+    )
+
+    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+        measurables: List<IntrinsicMeasurable>,
+        height: Int
+    ) = IntrinsicMeasureBlocks.VerticalMaxWidth(
+        measurables,
+        height,
+        verticalArrangement.spacing.roundToPx(),
+    )
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurables: List<IntrinsicMeasurable>,
+        width: Int
+    ) = IntrinsicMeasureBlocks.VerticalMaxHeight(
+        measurables,
+        width,
+        verticalArrangement.spacing.roundToPx(),
+    )
+}
+
+internal fun createColumnConstraints(
+    isPrioritizing: Boolean,
+    mainAxisMin: Int,
+    crossAxisMin: Int,
+    mainAxisMax: Int,
+    crossAxisMax: Int,
+): Constraints {
+    return if (!isPrioritizing) {
+        Constraints(
+            minHeight = mainAxisMin,
+            minWidth = crossAxisMin,
+            maxHeight = mainAxisMax,
+            maxWidth = crossAxisMax
+        )
+    } else {
+        Constraints.fitPrioritizingHeight(
+            minHeight = mainAxisMin,
+            minWidth = crossAxisMin,
+            maxHeight = mainAxisMax,
+            maxWidth = crossAxisMax
+        )
+    }
+}
+
 /**
  * Scope for the children of [Column].
  */
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
index 60fe7ae..537b801 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
@@ -351,10 +351,9 @@
         getComposable
     ) {
         FlowMeasureLazyPolicy(
-            orientation = LayoutOrientation.Horizontal,
+            isHorizontal = true,
             horizontalArrangement = horizontalArrangement,
-            mainAxisArrangementSpacing = horizontalArrangement.spacing,
-            crossAxisSize = SizeMode.Wrap,
+            mainAxisSpacing = horizontalArrangement.spacing,
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_TOP,
             verticalArrangement = verticalArrangement,
             crossAxisArrangementSpacing = verticalArrangement.spacing,
@@ -392,10 +391,9 @@
         getComposable
     ) {
         FlowMeasureLazyPolicy(
-            orientation = LayoutOrientation.Vertical,
+            isHorizontal = false,
             verticalArrangement = verticalArrangement,
-            mainAxisArrangementSpacing = verticalArrangement.spacing,
-            crossAxisSize = SizeMode.Wrap,
+            mainAxisSpacing = verticalArrangement.spacing,
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_START,
             horizontalArrangement = horizontalArrangement,
             crossAxisArrangementSpacing = horizontalArrangement.spacing,
@@ -414,12 +412,11 @@
  */
 @OptIn(ExperimentalLayoutApi::class)
 private data class FlowMeasureLazyPolicy(
-    private val orientation: LayoutOrientation,
-    private val horizontalArrangement: Arrangement.Horizontal,
-    private val verticalArrangement: Arrangement.Vertical,
-    private val mainAxisArrangementSpacing: Dp,
-    private val crossAxisSize: SizeMode,
-    private val crossAxisAlignment: CrossAxisAlignment,
+    override val isHorizontal: Boolean,
+    override val horizontalArrangement: Arrangement.Horizontal,
+    override val verticalArrangement: Arrangement.Vertical,
+    private val mainAxisSpacing: Dp,
+    override val crossAxisAlignment: CrossAxisAlignment,
     private val crossAxisArrangementSpacing: Dp,
     private val itemCount: Int,
     private val maxLines: Int,
@@ -430,7 +427,8 @@
         index: Int,
         info: FlowLineInfo
     ) -> Unit
-) {
+) : FlowLineMeasurePolicy {
+
     fun getMeasurePolicy(): (SubcomposeMeasureScope, Constraints) -> MeasureResult {
         return { measureScope, constraints ->
             measureScope.measure(constraints)
@@ -457,7 +455,7 @@
         }
         overflow.itemCount = itemCount
         overflow.setOverflowMeasurables(
-            orientation,
+            this@FlowMeasureLazyPolicy,
             constraints
         ) { canExpand, noOfItemsShown ->
             val composableIndex = if (canExpand) 0 else 1
@@ -468,13 +466,17 @@
             }
         }
         return breakDownItems(
-            orientation,
-            horizontalArrangement,
-            verticalArrangement,
-            crossAxisSize,
-            crossAxisAlignment,
+            this@FlowMeasureLazyPolicy,
             measurablesIterator,
-            constraints,
+            mainAxisSpacing,
+            crossAxisArrangementSpacing,
+            OrientationIndependentConstraints(
+                constraints, if (isHorizontal) {
+                    LayoutOrientation.Horizontal
+                } else {
+                    LayoutOrientation.Vertical
+                }
+            ),
             maxItemsInMainAxis,
             maxLines,
             overflow
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
index adbac9c..6535884 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -42,8 +42,7 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.constrainHeight
-import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEachIndexed
 import kotlin.math.ceil
 import kotlin.math.max
@@ -385,10 +384,9 @@
         maxItemsInMainAxis,
     ) {
         val measurePolicy = FlowMeasurePolicy(
-            orientation = LayoutOrientation.Horizontal,
+            isHorizontal = true,
             horizontalArrangement = horizontalArrangement,
-            mainAxisArrangementSpacing = horizontalArrangement.spacing,
-            crossAxisSize = SizeMode.Wrap,
+            mainAxisSpacing = horizontalArrangement.spacing,
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_TOP,
             verticalArrangement = verticalArrangement,
             crossAxisArrangementSpacing = verticalArrangement.spacing,
@@ -422,10 +420,9 @@
         overflowState
     ) {
         FlowMeasurePolicy(
-            orientation = LayoutOrientation.Horizontal,
+            isHorizontal = true,
             horizontalArrangement = horizontalArrangement,
-            mainAxisArrangementSpacing = horizontalArrangement.spacing,
-            crossAxisSize = SizeMode.Wrap,
+            mainAxisSpacing = horizontalArrangement.spacing,
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_TOP,
             verticalArrangement = verticalArrangement,
             crossAxisArrangementSpacing = verticalArrangement.spacing,
@@ -450,10 +447,9 @@
         maxItemsInMainAxis,
     ) {
         val measurePolicy = FlowMeasurePolicy(
-            orientation = LayoutOrientation.Vertical,
+            isHorizontal = false,
             verticalArrangement = verticalArrangement,
-            mainAxisArrangementSpacing = verticalArrangement.spacing,
-            crossAxisSize = SizeMode.Wrap,
+            mainAxisSpacing = verticalArrangement.spacing,
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_START,
             horizontalArrangement = horizontalArrangement,
             crossAxisArrangementSpacing = horizontalArrangement.spacing,
@@ -469,7 +465,6 @@
     }
 }
 
-@OptIn(ExperimentalLayoutApi::class)
 @Composable
 internal fun columnMeasurementMultiContentHelper(
     verticalArrangement: Arrangement.Vertical,
@@ -486,10 +481,9 @@
         overflowState
     ) {
         FlowMeasurePolicy(
-            orientation = LayoutOrientation.Vertical,
+            isHorizontal = false,
             verticalArrangement = verticalArrangement,
-            mainAxisArrangementSpacing = verticalArrangement.spacing,
-            crossAxisSize = SizeMode.Wrap,
+            mainAxisSpacing = verticalArrangement.spacing,
             crossAxisAlignment = CROSS_AXIS_ALIGNMENT_START,
             horizontalArrangement = horizontalArrangement,
             crossAxisArrangementSpacing = horizontalArrangement.spacing,
@@ -500,22 +494,154 @@
     }
 }
 
+internal interface FlowLineMeasurePolicy : RowColumnMeasurePolicy {
+    val isHorizontal: Boolean
+    val horizontalArrangement: Arrangement.Horizontal
+    val verticalArrangement: Arrangement.Vertical
+    val crossAxisAlignment: CrossAxisAlignment
+
+    override fun Placeable.mainAxisSize() =
+        if (isHorizontal) measuredWidth else measuredHeight
+
+    override fun Placeable.crossAxisSize() =
+        if (isHorizontal) measuredHeight else measuredWidth
+
+    override fun createConstraints(
+        mainAxisMin: Int,
+        crossAxisMin: Int,
+        mainAxisMax: Int,
+        crossAxisMax: Int,
+        isPrioritizing: Boolean
+    ): Constraints {
+        return if (isHorizontal) {
+            createRowConstraints(
+                isPrioritizing,
+                mainAxisMin,
+                crossAxisMin,
+                mainAxisMax,
+                crossAxisMax,
+            )
+        } else {
+            createColumnConstraints(
+                isPrioritizing,
+                mainAxisMin,
+                crossAxisMin,
+                mainAxisMax,
+                crossAxisMax,
+            )
+        }
+    }
+
+    override fun placeHelper(
+        placeables: Array<Placeable?>,
+        measureScope: MeasureScope,
+        beforeCrossAxisAlignmentLine: Int,
+        mainAxisPositions: IntArray,
+        mainAxisLayoutSize: Int,
+        crossAxisLayoutSize: Int,
+        crossAxisOffset: IntArray?,
+        currentLineIndex: Int,
+        startIndex: Int,
+        endIndex: Int
+    ): MeasureResult {
+        with(measureScope) {
+            var width: Int
+            var height: Int
+            if (isHorizontal) {
+                width = mainAxisLayoutSize
+                height = crossAxisLayoutSize
+            } else {
+                width = crossAxisLayoutSize
+                height = mainAxisLayoutSize
+            }
+            return layout(width, height) {
+                val crossAxisLineOffset = crossAxisOffset?.get(currentLineIndex) ?: 0
+                for (i in startIndex until endIndex) {
+                    val placeable = placeables[i]!!
+                    val crossAxisPosition = getCrossAxisPosition(
+                        placeable,
+                        placeable.rowColumnParentData,
+                        crossAxisLayoutSize,
+                        layoutDirection,
+                        beforeCrossAxisAlignmentLine
+                    ) + crossAxisLineOffset
+                    if (isHorizontal) {
+                        placeable.place(
+                            mainAxisPositions[i - startIndex],
+                            crossAxisPosition
+                        )
+                    } else {
+                        placeable.place(
+                            crossAxisPosition,
+                            mainAxisPositions[i - startIndex]
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    fun getCrossAxisPosition(
+        placeable: Placeable,
+        rowColumnParentData: RowColumnParentData?,
+        crossAxisLayoutSize: Int,
+        layoutDirection: LayoutDirection,
+        beforeCrossAxisAlignmentLine: Int
+    ): Int {
+        val childCrossAlignment = rowColumnParentData?.crossAxisAlignment ?: crossAxisAlignment
+        return childCrossAlignment.align(
+            size = crossAxisLayoutSize - placeable.crossAxisSize(),
+            layoutDirection = if (isHorizontal) {
+                LayoutDirection.Ltr
+            } else {
+                layoutDirection
+            },
+            placeable = placeable,
+            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
+        )
+    }
+
+    override fun populateMainAxisPositions(
+        mainAxisLayoutSize: Int,
+        childrenMainAxisSize: IntArray,
+        mainAxisPositions: IntArray,
+        measureScope: MeasureScope
+    ) {
+        if (isHorizontal) {
+            with(horizontalArrangement) {
+                measureScope.arrange(
+                    mainAxisLayoutSize,
+                    childrenMainAxisSize,
+                    measureScope.layoutDirection,
+                    mainAxisPositions
+                )
+            }
+        } else {
+            with(verticalArrangement) {
+                measureScope.arrange(
+                    mainAxisLayoutSize,
+                    childrenMainAxisSize,
+                    mainAxisPositions,
+                )
+            }
+        }
+    }
+}
 /**
  * Returns a Flow Measure Policy
  */
 @OptIn(ExperimentalLayoutApi::class)
 private data class FlowMeasurePolicy(
-    private val orientation: LayoutOrientation,
-    private val horizontalArrangement: Arrangement.Horizontal,
-    private val verticalArrangement: Arrangement.Vertical,
-    private val mainAxisArrangementSpacing: Dp,
-    private val crossAxisSize: SizeMode,
-    private val crossAxisAlignment: CrossAxisAlignment,
+    override val isHorizontal: Boolean,
+    override val horizontalArrangement: Arrangement.Horizontal,
+    override val verticalArrangement: Arrangement.Vertical,
+    private val mainAxisSpacing: Dp,
+    override val crossAxisAlignment: CrossAxisAlignment,
     private val crossAxisArrangementSpacing: Dp,
     private val maxItemsInMainAxis: Int,
     private val maxLines: Int,
     private val overflow: FlowLayoutOverflowState,
-) : MultiContentMeasurePolicy {
+) : MultiContentMeasurePolicy, FlowLineMeasurePolicy {
 
     override fun MeasureScope.measure(
         measurables: List<List<Measurable>>,
@@ -534,19 +660,23 @@
         val collapseMeasurable = measurables.getOrNull(2)?.firstOrNull()
         overflow.itemCount = list.size
         overflow.setOverflowMeasurables(
+            this@FlowMeasurePolicy,
             seeMoreMeasurable,
             collapseMeasurable,
-            orientation,
             constraints,
         )
         return breakDownItems(
-            orientation,
-            horizontalArrangement,
-            verticalArrangement,
-            crossAxisSize,
-            crossAxisAlignment,
+            this@FlowMeasurePolicy,
             list.iterator(),
-            constraints,
+            mainAxisSpacing,
+            crossAxisArrangementSpacing,
+            OrientationIndependentConstraints(
+                constraints, if (isHorizontal) {
+                    LayoutOrientation.Horizontal
+                } else {
+                    LayoutOrientation.Vertical
+                }
+            ),
             maxItemsInMainAxis,
             maxLines,
             overflow
@@ -560,14 +690,14 @@
         overflow.setOverflowMeasurables(
             seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
             collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
-            orientation = orientation,
+            isHorizontal,
             constraints = Constraints(maxHeight = height)
         )
-        return if (orientation == LayoutOrientation.Horizontal) {
+        return if (isHorizontal) {
             minIntrinsicMainAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 height,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
                 crossAxisArrangementSpacing.roundToPx(),
                 maxLines = maxLines,
                 maxItemsInMainAxis = maxItemsInMainAxis,
@@ -577,7 +707,7 @@
             intrinsicCrossAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 height,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
                 crossAxisArrangementSpacing.roundToPx(),
                 maxLines = maxLines,
                 maxItemsInMainAxis = maxItemsInMainAxis,
@@ -593,14 +723,14 @@
         overflow.setOverflowMeasurables(
             seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
             collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
-            orientation = orientation,
+            isHorizontal,
             constraints = Constraints(maxWidth = width)
         )
-        return if (orientation == LayoutOrientation.Horizontal) {
+        return if (isHorizontal) {
             intrinsicCrossAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 width,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
                 crossAxisArrangementSpacing.roundToPx(),
                 maxLines = maxLines,
                 maxItemsInMainAxis = maxItemsInMainAxis,
@@ -610,7 +740,7 @@
             minIntrinsicMainAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 width,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
                 crossAxisArrangementSpacing.roundToPx(),
                 maxLines = maxLines,
                 maxItemsInMainAxis = maxItemsInMainAxis,
@@ -626,14 +756,14 @@
         overflow.setOverflowMeasurables(
             seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
             collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
-            orientation = orientation,
+            isHorizontal,
             constraints = Constraints(maxWidth = width)
         )
-        return if (orientation == LayoutOrientation.Horizontal) {
+        return if (isHorizontal) {
             intrinsicCrossAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 width,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
                 crossAxisArrangementSpacing.roundToPx(),
                 maxLines = maxLines,
                 maxItemsInMainAxis = maxItemsInMainAxis,
@@ -643,7 +773,7 @@
             maxIntrinsicMainAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 width,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
             )
         }
     }
@@ -655,20 +785,20 @@
         overflow.setOverflowMeasurables(
             seeMoreMeasurable = measurables.getOrNull(1)?.firstOrNull(),
             collapseMeasurable = measurables.getOrNull(2)?.firstOrNull(),
-            orientation = orientation,
+            isHorizontal,
             constraints = Constraints(maxHeight = height)
         )
-        return if (orientation == LayoutOrientation.Horizontal) {
+        return if (isHorizontal) {
             maxIntrinsicMainAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 height,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
             )
         } else {
             intrinsicCrossAxisSize(
                 measurables.firstOrNull() ?: listOf(),
                 height,
-                mainAxisArrangementSpacing.roundToPx(),
+                mainAxisSpacing.roundToPx(),
                 crossAxisArrangementSpacing.roundToPx(),
                 maxLines = maxLines,
                 maxItemsInMainAxis = maxItemsInMainAxis,
@@ -730,7 +860,7 @@
     ).first
 
     val maxMainAxisIntrinsicItemSize: IntrinsicMeasurable.(Int, Int) -> Int =
-        if (orientation == LayoutOrientation.Horizontal) { _, h ->
+        if (isHorizontal) { _, h ->
             maxIntrinsicWidth(h)
         }
         else { _, w ->
@@ -738,7 +868,7 @@
         }
 
     val maxCrossAxisIntrinsicItemSize: IntrinsicMeasurable.(Int, Int) -> Int =
-        if (orientation == LayoutOrientation.Horizontal) { _, w ->
+        if (isHorizontal) { _, w ->
             maxIntrinsicHeight(w)
         }
         else { _, h ->
@@ -746,7 +876,7 @@
         }
 
     val minCrossAxisIntrinsicItemSize: IntrinsicMeasurable.(Int, Int) -> Int =
-        if (orientation == LayoutOrientation.Horizontal) { _, w ->
+        if (isHorizontal) { _, w ->
             minIntrinsicHeight(w)
         }
         else { _, h ->
@@ -754,7 +884,7 @@
         }
 
     val minMainAxisIntrinsicItemSize: IntrinsicMeasurable.(Int, Int) -> Int =
-        if (orientation == LayoutOrientation.Horizontal) { _, h ->
+        if (isHorizontal) { _, h ->
             minIntrinsicWidth(h)
         }
         else { _, w ->
@@ -1033,47 +1163,39 @@
  * it moves to the next "line" and moves the next batch of items to a new list of items
  */
 internal fun MeasureScope.breakDownItems(
-    orientation: LayoutOrientation,
-    horizontalArrangement: Arrangement.Horizontal,
-    verticalArrangement: Arrangement.Vertical,
-    sizeMode: SizeMode,
-    crossAxisAlignment: CrossAxisAlignment,
+    measurePolicy: FlowLineMeasurePolicy,
     measurablesIterator: Iterator<Measurable>,
-    constraints: Constraints,
+    mainAxisSpacingDp: Dp,
+    crossAxisSpacingDp: Dp,
+    constraints: OrientationIndependentConstraints,
     maxItemsInMainAxis: Int,
     maxLines: Int,
     overflow: FlowLayoutOverflowState,
 ): MeasureResult {
-    val items = mutableVectorOf<RowColumnMeasureHelperResult>()
-    val independentConstraints = OrientationIndependentConstraints(constraints, orientation)
-    val mainAxisMax = independentConstraints.mainAxisMax
-    val mainAxisMin = independentConstraints.mainAxisMin
-    val crossAxisMax = independentConstraints.crossAxisMax
+    val items = mutableVectorOf<MeasureResult>()
+    val mainAxisMax = constraints.mainAxisMax
+    val mainAxisMin = constraints.mainAxisMin
+    val crossAxisMax = constraints.crossAxisMax
     val placeables = mutableIntObjectMapOf<Placeable?>()
     val measurables = mutableListOf<Measurable>()
 
-    val mainAxisSpacingDp = if (orientation == LayoutOrientation.Horizontal) {
-        horizontalArrangement.spacing
-    } else {
-        verticalArrangement.spacing
-    }
-    val crossAxisSpacingDp = if (orientation == LayoutOrientation.Horizontal) {
-        verticalArrangement.spacing
-    } else {
-        horizontalArrangement.spacing
-    }
-
     val spacing = ceil(mainAxisSpacingDp.toPx()).toInt()
     val crossAxisSpacing = ceil(crossAxisSpacingDp.toPx()).toInt()
     val subsetConstraints = OrientationIndependentConstraints(
-        mainAxisMin,
+        0,
         mainAxisMax,
         0,
         crossAxisMax
     )
+    val measureConstraints = subsetConstraints.copy(
+        mainAxisMin = 0
+    ).toBoxConstraints(
+        if (measurePolicy.isHorizontal) LayoutOrientation.Horizontal else LayoutOrientation.Vertical
+    )
 
     var index = 0
     var measurable: Measurable?
+    var placeableItem: Placeable? = null
 
     var lineIndex = 0
     var leftOver = mainAxisMax
@@ -1091,8 +1213,11 @@
 
     var nextSize = measurablesIterator.hasNext().run {
         measurable = if (!this) null else measurablesIterator.safeNext(lineInfo)
-        measurable?.measureAndCache(subsetConstraints, orientation) { placeable ->
-            placeables[0] = placeable
+        measurable?.measureAndCache(
+            measurePolicy,
+            measureConstraints
+        ) { placeable ->
+            placeableItem = placeable
         }
     }
     var nextMainAxisSize: Int? = nextSize?.first
@@ -1106,7 +1231,7 @@
         maxItemsInMainAxis = maxItemsInMainAxis,
         mainAxisSpacing = spacing,
         crossAxisSpacing = crossAxisSpacing,
-        constraints = independentConstraints,
+        constraints = constraints,
         maxLines = maxLines,
         overflow = overflow
     )
@@ -1147,6 +1272,7 @@
         leftOver -= itemMainAxisSize
         overflow.itemShown = index + 1
         measurables.add(measurable!!)
+        placeables[index] = placeableItem
 
         val nextIndexInLine = (index + 1) - startBreakLineIndex
         val willFitLine = nextIndexInLine < maxItemsInMainAxis
@@ -1169,8 +1295,12 @@
 
         nextSize = measurablesIterator.hasNext().run {
             measurable = if (!this) null else measurablesIterator.safeNext(lineInfo)
-            measurable?.measureAndCache(subsetConstraints, orientation) { placeable ->
-                placeables[index + 1] = placeable
+            placeableItem = null
+            measurable?.measureAndCache(
+                measurePolicy,
+                measureConstraints
+            ) { placeable ->
+                placeableItem = placeable
             }
         }
         nextMainAxisSize = nextSize?.first?.plus(spacing)
@@ -1215,69 +1345,76 @@
         index++
     }
 
-    val measureHelper = RowColumnMeasurementHelper(
-        orientation,
-        horizontalArrangement,
-        verticalArrangement,
-        sizeMode,
-        crossAxisAlignment,
-        RowColumnMeasurablesWrapper(
-            measurables,
-            placeables,
-            ellipsisWrapInfo?.ellipsis,
-            ellipsisWrapInfo?.placeEllipsisOnLastContentLine,
-        ),
-    )
-
     ellipsisWrapInfo?.let {
+        measurables.add(it.ellipsis)
+        placeables[measurables.size - 1] = it.placeable
         lineIndex = endBreakLineList.lastIndex
         if (it.placeEllipsisOnLastContentLine) {
+            val lastIndex = endBreakLineList.size - 1
             val lastLineCrossAxis = crossAxisSizes[lineIndex]
             crossAxisSizes[lineIndex] = max(lastLineCrossAxis, it.ellipsisSize.second)
+            endBreakLineList[lastIndex] = endBreakLineList.last() + 1
         } else {
             crossAxisSizes.add(it.ellipsisSize.second)
+            endBreakLineList.add(endBreakLineList.last() + 1)
         }
     }
 
-    val subsetBoxConstraints = subsetConstraints.copy(
-        mainAxisMin = mainAxisTotalSize
-    )
-
+    val arrayOfPlaceables: Array<Placeable?> = Array(measurables.size) {
+        placeables[it]
+    }
+    val crossAxisOffsets = IntArray(endBreakLineList.size) { 0 }
+    val crossAxisSizesArray = IntArray(endBreakLineList.size) { 0 }
     crossAxisTotalSize = 0
-    measureHelper.listWrapper.forEachLine(
-        endBreakLineList
-    ) { currentLineIndex, startIndex, endIndex ->
-        val crossAxisSize = crossAxisSizes[currentLineIndex]
-        val result = measureHelper.measureWithoutPlacing(
+
+    var startIndex = 0
+    endBreakLineList.forEachIndexed { currentLineIndex, endIndex ->
+        var crossAxisSize = crossAxisSizes[currentLineIndex]
+        val result = measurePolicy.measure(
+            mainAxisMin = mainAxisTotalSize,
+            crossAxisMin = subsetConstraints.crossAxisMin,
+            mainAxisMax = subsetConstraints.mainAxisMax,
+            crossAxisMax = crossAxisSize,
+            spacing,
             this,
-            subsetBoxConstraints.copy(
-                crossAxisMax = crossAxisSize
-            ).toBoxConstraints(orientation),
+            measurables,
+            arrayOfPlaceables,
             startIndex,
-            endIndex
+            endIndex,
+            crossAxisOffsets,
+            currentLineIndex
         )
-        crossAxisTotalSize += result.crossAxisSize
-        mainAxisTotalSize = maxOf(mainAxisTotalSize, result.mainAxisSize)
+        var mainAxisSize: Int
+        if (measurePolicy.isHorizontal) {
+            mainAxisSize = result.width
+            crossAxisSize = result.height
+        } else {
+            mainAxisSize = result.height
+            crossAxisSize = result.width
+        }
+        crossAxisSizesArray[currentLineIndex] = crossAxisSize
+        crossAxisTotalSize += crossAxisSize
+        mainAxisTotalSize = maxOf(mainAxisTotalSize, mainAxisSize)
         items.add(
             result
         )
+        startIndex = endIndex
     }
 
-    crossAxisTotalSize = maxOf(crossAxisTotalSize, independentConstraints.crossAxisMin)
-    mainAxisTotalSize = maxOf(mainAxisTotalSize, independentConstraints.mainAxisMin)
-    val flowResult = FlowResult(
+    if (items.isEmpty()) {
+        mainAxisTotalSize = 0
+        crossAxisTotalSize = 0
+    }
+
+    return placeHelper(
+        constraints,
         mainAxisTotalSize,
         crossAxisTotalSize,
+        crossAxisSizesArray,
         items,
+        measurePolicy,
+        crossAxisOffsets
     )
-
-    if (flowResult.items.isEmpty()) {
-        return layout(
-        constraints.constrainWidth(0),
-        constraints.constrainHeight(0)) {}
-    }
-
-    return handleFlowResult(flowResult, constraints, measureHelper)
 }
 
 private fun Iterator<Measurable>.safeNext(info: FlowLineInfo?): Measurable? {
@@ -1292,26 +1429,20 @@
     }
 }
 
-internal fun IntrinsicMeasurable.mainAxisMin(orientation: LayoutOrientation, crossAxisSize: Int) =
-    if (orientation == LayoutOrientation.Horizontal) {
+internal fun IntrinsicMeasurable.mainAxisMin(isHorizontal: Boolean, crossAxisSize: Int) =
+    if (isHorizontal) {
         minIntrinsicWidth(crossAxisSize)
     } else {
         minIntrinsicHeight(crossAxisSize)
     }
 
-internal fun IntrinsicMeasurable.crossAxisMin(orientation: LayoutOrientation, mainAxisSize: Int) =
-    if (orientation == LayoutOrientation.Horizontal) {
+internal fun IntrinsicMeasurable.crossAxisMin(isHorizontal: Boolean, mainAxisSize: Int) =
+    if (isHorizontal) {
         minIntrinsicHeight(mainAxisSize)
     } else {
         minIntrinsicWidth(mainAxisSize)
     }
 
-internal fun Placeable.mainAxisSize(orientation: LayoutOrientation) =
-    if (orientation == LayoutOrientation.Horizontal) measuredWidth else measuredHeight
-
-internal fun Placeable.crossAxisSize(orientation: LayoutOrientation) =
-    if (orientation == LayoutOrientation.Horizontal) measuredHeight else measuredWidth
-
 internal val CROSS_AXIS_ALIGNMENT_TOP = CrossAxisAlignment.vertical(Alignment.Top)
 internal val CROSS_AXIS_ALIGNMENT_START = CrossAxisAlignment.horizontal(Alignment.Start)
 
@@ -1320,9 +1451,9 @@
 // For weighted items, we continue to use their intrinsic widths.
 // This is because their fixed sizes are only determined after we determine
 // the number of items that can fit in the row/column it only lies on.
-private fun Measurable.measureAndCache(
-    constraints: OrientationIndependentConstraints,
-    orientation: LayoutOrientation,
+internal fun Measurable.measureAndCache(
+    measurePolicy: FlowLineMeasurePolicy,
+    constraints: Constraints,
     storePlaceable: (Placeable?) -> Unit
 ): IntIntPair {
     return if (
@@ -1330,41 +1461,42 @@
         rowColumnParentData?.flowLayoutData?.fillCrossAxisFraction == null
     ) {
         // fixed sizes: measure once
-        val placeable = measure(
-            constraints.copy(
-                mainAxisMin = 0,
-            ).toBoxConstraints(orientation)
-        ).also(storePlaceable)
-        val mainAxis = placeable.mainAxisSize(orientation)
-        val crossAxis = placeable.crossAxisSize(orientation)
-        IntIntPair(mainAxis, crossAxis)
+        val placeable = measure(constraints).also(storePlaceable)
+        with(measurePolicy) {
+            val mainAxis = placeable.mainAxisSize()
+            val crossAxis = placeable.crossAxisSize()
+            IntIntPair(mainAxis, crossAxis)
+        }
     } else {
-        val mainAxis = mainAxisMin(orientation, Constraints.Infinity)
-        val crossAxis = crossAxisMin(orientation, mainAxis)
+        val mainAxis = mainAxisMin(measurePolicy.isHorizontal, Constraints.Infinity)
+        val crossAxis = crossAxisMin(measurePolicy.isHorizontal, mainAxis)
         IntIntPair(mainAxis, crossAxis)
     }
 }
 
-internal fun MeasureScope.handleFlowResult(
-    flowResult: FlowResult,
-    constraints: Constraints,
-    measureHelper: RowColumnMeasurementHelper
+internal fun MeasureScope.placeHelper(
+    constraints: OrientationIndependentConstraints,
+    mainAxisTotalSize: Int,
+    crossAxisTotalSize: Int,
+    crossAxisSizes: IntArray,
+    items: MutableVector<MeasureResult>,
+    measureHelper: FlowLineMeasurePolicy,
+    outPosition: IntArray,
 ): MeasureResult {
-    val orientation = measureHelper.orientation
+    val isHorizontal = measureHelper.isHorizontal
     val verticalArrangement = measureHelper.verticalArrangement
     val horizontalArrangement = measureHelper.horizontalArrangement
-    val items = flowResult.items
-    val crossAxisSizes = IntArray(items.size) { index ->
-        items[index].crossAxisSize
-    }
     // space in between children, except for the last child
-    val outPosition = IntArray(crossAxisSizes.size)
-    var totalCrossAxisSize = flowResult.crossAxisTotalSize
+    var totalCrossAxisSize = crossAxisTotalSize
     // cross axis arrangement
-    if (orientation == LayoutOrientation.Horizontal) {
+    if (isHorizontal) {
         with(requireNotNull(verticalArrangement) { "null verticalArrangement" }) {
             val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
             totalCrossAxisSize += totalCrossAxisSpacing
+            totalCrossAxisSize = totalCrossAxisSize.coerceIn(
+                constraints.crossAxisMin,
+                constraints.crossAxisMax
+            )
             arrange(
                 totalCrossAxisSize,
                 crossAxisSizes,
@@ -1375,6 +1507,10 @@
         with(requireNotNull(horizontalArrangement) { "null horizontalArrangement" }) {
             val totalCrossAxisSpacing = spacing.roundToPx() * (items.size - 1)
             totalCrossAxisSize += totalCrossAxisSpacing
+            totalCrossAxisSize = totalCrossAxisSize.coerceIn(
+                constraints.crossAxisMin,
+                constraints.crossAxisMax
+            )
             arrange(
                 totalCrossAxisSize,
                 crossAxisSizes,
@@ -1384,41 +1520,24 @@
         }
     }
 
+    val finalMainAxisTotalSize = mainAxisTotalSize.coerceIn(
+        constraints.mainAxisMin,
+        constraints.mainAxisMax
+    )
+
     var layoutWidth: Int
     var layoutHeight: Int
-    if (orientation == LayoutOrientation.Horizontal) {
-        layoutWidth = flowResult.mainAxisTotalSize
+    if (isHorizontal) {
+        layoutWidth = finalMainAxisTotalSize
         layoutHeight = totalCrossAxisSize
     } else {
         layoutWidth = totalCrossAxisSize
-        layoutHeight = flowResult.mainAxisTotalSize
+        layoutHeight = finalMainAxisTotalSize
     }
-    layoutWidth = constraints.constrainWidth(layoutWidth)
-    layoutHeight = constraints.constrainHeight(layoutHeight)
 
     return layout(layoutWidth, layoutHeight) {
-        flowResult.items.forEachIndexed { currentRowOrColumnIndex,
-            measureResult ->
-            measureHelper.placeHelper(
-                this,
-                measureResult,
-                outPosition[currentRowOrColumnIndex],
-                this@handleFlowResult.layoutDirection
-            )
+        items.forEach { measureResult ->
+            measureResult.placeChildren()
         }
     }
 }
-
-/**
- * FlowResult when broken down to multiple rows or columns based on [breakDownItems] algorithm
- *
- * @param mainAxisTotalSize the total size of the main axis
- * @param crossAxisTotalSize the total size of the cross axis when taken into account
- * the cross axis sizes of all items
- * @param items the row or column measurements for each row or column
- */
-internal class FlowResult(
-    val mainAxisTotalSize: Int,
-    val crossAxisTotalSize: Int,
-    val items: MutableVector<RowColumnMeasureHelperResult>,
-)
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt
index 3dfa4ecc..d49e626 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutBuildingBlocks.kt
@@ -17,6 +17,7 @@
 
 import androidx.collection.IntIntPair
 import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.Placeable
 import kotlin.math.max
 
 @OptIn(ExperimentalLayoutApi::class)
@@ -35,8 +36,9 @@
 
     class WrapEllipsisInfo(
         val ellipsis: Measurable,
+        val placeable: Placeable?,
         val ellipsisSize: IntIntPair,
-        val placeEllipsisOnLastContentLine: Boolean,
+        var placeEllipsisOnLastContentLine: Boolean = true,
     )
 
     fun getWrapEllipsisInfo(
@@ -49,29 +51,18 @@
     ): WrapEllipsisInfo? {
         if (!wrapInfo.isLastItemInContainer) return null
 
-        val ellipsis = overflow.ellipsis(
+        val ellipsisInfo = overflow.ellipsisInfo(
             hasNext,
             lastContentLineIndex,
-            getFinalMeasurable = true,
             totalCrossAxisSize
         ) ?: return null
 
-        val ellipsisSize = ellipsis.let {
-            overflow.ellipsisSize(
-                hasNext,
-                lastContentLineIndex,
-                totalCrossAxisSize
-            )
-        } ?: return null
-
         val canFitLine = lastContentLineIndex >= 0 && (nextIndexInLine == 0 ||
-            !(leftOverMainAxis - ellipsisSize.first < 0 || nextIndexInLine >= maxItemsInMainAxis))
+            !(leftOverMainAxis - ellipsisInfo.ellipsisSize.first < 0 ||
+                nextIndexInLine >= maxItemsInMainAxis))
 
-        return WrapEllipsisInfo(
-            ellipsis,
-            ellipsisSize,
-            canFitLine
-        )
+        ellipsisInfo.placeEllipsisOnLastContentLine = canFitLine
+        return ellipsisInfo
     }
 
     fun getWrapInfo(
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt
index 8d19b8d..8fc7e1b 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayoutOverflow.kt
@@ -30,6 +30,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
@@ -718,7 +719,9 @@
     internal var itemShown: Int = -1
     internal var itemCount = 0
     private var seeMoreMeasurable: Measurable? = null
+    private var seeMorePlaceable: Placeable? = null
     private var collapseMeasurable: Measurable? = null
+    private var collapsePlaceable: Placeable? = null
     private var seeMoreSize: IntIntPair? = null
     private var collapseSize: IntIntPair? = null
     // for contextual flow row
@@ -750,42 +753,46 @@
         }
     }
 
-    internal fun ellipsis(
+    internal fun ellipsisInfo(
         hasNext: Boolean,
         lineIndex: Int,
-        getFinalMeasurable: Boolean,
         totalCrossAxisSize: Int
-    ): Measurable? {
+    ): FlowLayoutBuildingBlocks.WrapEllipsisInfo? {
         return when (type) {
             FlowLayoutOverflow.OverflowType.Visible -> null
             FlowLayoutOverflow.OverflowType.Clip -> null
             FlowLayoutOverflow.OverflowType.ExpandIndicator,
             FlowLayoutOverflow.OverflowType.ExpandOrCollapseIndicator -> {
+                var measurable: Measurable? = null
+                var placeable: Placeable? = null
+                var ellipsisSize: IntIntPair?
                 if (hasNext) {
-                    if (getFinalMeasurable) {
-                        getOverflowMeasurable?.invoke(
-                            /* isExpandable */ true,
-                            noOfItemsShown
-                        ) ?: seeMoreMeasurable
-                    } else {
-                        seeMoreMeasurable
+                    measurable = getOverflowMeasurable?.invoke(
+                        /* isExpandable */ true, noOfItemsShown
+                    ) ?: seeMoreMeasurable
+                    ellipsisSize = seeMoreSize
+                    if (getOverflowMeasurable == null) {
+                        placeable = seeMorePlaceable
                     }
                 } else {
-                    if (lineIndex < (minLinesToShowCollapse - 1) ||
-                        totalCrossAxisSize < (minCrossAxisSizeToShowCollapse)
-                    ) {
-                        null
-                    } else {
-                        if (getFinalMeasurable) {
-                            getOverflowMeasurable?.invoke(
-                                /* isExpandable */ false,
-                                noOfItemsShown
-                            ) ?: collapseMeasurable
-                        } else {
-                            collapseMeasurable
-                        }
+                    if (lineIndex >= (minLinesToShowCollapse - 1) &&
+                        totalCrossAxisSize >= (minCrossAxisSizeToShowCollapse)
+                     ) {
+                        measurable = getOverflowMeasurable?.invoke(
+                            /* isExpandable */ false, noOfItemsShown
+                        ) ?: collapseMeasurable
+                    }
+                    ellipsisSize = collapseSize
+                    if (getOverflowMeasurable == null) {
+                        placeable = collapsePlaceable
                     }
                 }
+                measurable ?: return null
+                FlowLayoutBuildingBlocks.WrapEllipsisInfo(
+                    measurable,
+                    placeable,
+                    ellipsisSize!!
+                )
             }
         }
     }
@@ -793,35 +800,90 @@
     internal fun setOverflowMeasurables(
         seeMoreMeasurable: IntrinsicMeasurable?,
         collapseMeasurable: IntrinsicMeasurable?,
-        orientation: LayoutOrientation,
+        isHorizontal: Boolean,
         constraints: Constraints,
     ) {
+        val orientation = if (isHorizontal)
+            LayoutOrientation.Horizontal else LayoutOrientation.Vertical
         val orientationIndependentConstraints =
             OrientationIndependentConstraints(constraints, orientation)
         seeMoreMeasurable?.let { item ->
             val mainAxisSize = item.mainAxisMin(
-                orientation,
+                isHorizontal,
                 orientationIndependentConstraints.crossAxisMax
             )
-            val crossAxisSize = item.crossAxisMin(orientation,
+            val crossAxisSize = item.crossAxisMin(isHorizontal,
                 mainAxisSize
             )
             this.seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
             this.seeMoreMeasurable = item as? Measurable
+            this.seeMorePlaceable = null
         }
         collapseMeasurable?.let { item ->
             val mainAxisSize = item.mainAxisMin(
-                orientation,
+                isHorizontal,
                 orientationIndependentConstraints.crossAxisMax
             )
-            val crossAxisSize = item.crossAxisMin(orientation, mainAxisSize)
+            val crossAxisSize = item.crossAxisMin(isHorizontal, mainAxisSize)
             this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
             this.collapseMeasurable = item as? Measurable
+            this.collapsePlaceable = null
         }
     }
 
     internal fun setOverflowMeasurables(
-        orientation: LayoutOrientation,
+        measurePolicy: FlowLineMeasurePolicy,
+        seeMoreMeasurable: Measurable?,
+        collapseMeasurable: Measurable?,
+        constraints: Constraints,
+    ) {
+        val isHorizontal = measurePolicy.isHorizontal
+        val orientation = if (isHorizontal)
+            LayoutOrientation.Horizontal else LayoutOrientation.Vertical
+        val orientationIndependentConstraints =
+            OrientationIndependentConstraints(constraints, orientation)
+                .copy(mainAxisMin = 0, crossAxisMin = 0)
+        val finalConstraints = orientationIndependentConstraints.toBoxConstraints(orientation)
+        seeMoreMeasurable?.let { item ->
+            item.measureAndCache(
+                measurePolicy,
+                finalConstraints
+            ) { placeable ->
+                var mainAxisSize = 0
+                var crossAxisSize = 0
+                placeable?.let {
+                    with(measurePolicy) {
+                        mainAxisSize = placeable.mainAxisSize()
+                        crossAxisSize = placeable.crossAxisSize()
+                    }
+                }
+                seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
+                seeMorePlaceable = placeable
+            }
+            this.seeMoreMeasurable = item
+        }
+        collapseMeasurable?.let { item ->
+            item.measureAndCache(
+                measurePolicy,
+                finalConstraints
+            ) { placeable ->
+                var mainAxisSize = 0
+                var crossAxisSize = 0
+                placeable?.let {
+                    with(measurePolicy) {
+                        mainAxisSize = placeable.mainAxisSize()
+                        crossAxisSize = placeable.crossAxisSize()
+                    }
+                }
+                this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
+                collapsePlaceable = placeable
+            }
+            this.collapseMeasurable = item
+        }
+    }
+
+    internal fun setOverflowMeasurables(
+        measurePolicy: FlowLineMeasurePolicy,
         constraints: Constraints,
         getOverflowMeasurable: ((isExpandable: Boolean, numberOfItemsShown: Int) -> Measurable?)
     ) {
@@ -834,9 +896,9 @@
             /* isExpandable */ false, /* numberOfItemsShown */ 0
         )
         setOverflowMeasurables(
+            measurePolicy,
             seeMoreMeasurable,
             collapseMeasurable,
-            orientation,
             constraints
         )
     }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
index d7d58b2..766585c 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Row.kt
@@ -25,10 +25,18 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.layout.HorizontalAlignmentLine
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.LastBaseline
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.Measured
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.LayoutDirection
 
 /**
  * A layout composable that places its children in a horizontal sequence. For a layout composable
@@ -100,13 +108,9 @@
  * MeasureBlocks to use when horizontalArrangement and verticalAlignment are not provided.
  */
 @PublishedApi
-internal val DefaultRowMeasurePolicy: MeasurePolicy = RowColumnMeasurePolicy(
-    orientation = LayoutOrientation.Horizontal,
+internal val DefaultRowMeasurePolicy: MeasurePolicy = RowMeasurePolicy(
     horizontalArrangement = Arrangement.Start,
-    verticalArrangement = null,
-    arrangementSpacing = Arrangement.Start.spacing,
-    crossAxisAlignment = CrossAxisAlignment.vertical(Alignment.Top),
-    crossAxisSize = SizeMode.Wrap
+    verticalAlignment = Alignment.Top,
 )
 
 @PublishedApi
@@ -119,17 +123,177 @@
         DefaultRowMeasurePolicy
     } else {
         remember(horizontalArrangement, verticalAlignment) {
-            RowColumnMeasurePolicy(
-                orientation = LayoutOrientation.Horizontal,
+            RowMeasurePolicy(
                 horizontalArrangement = horizontalArrangement,
-                verticalArrangement = null,
-                arrangementSpacing = horizontalArrangement.spacing,
-                crossAxisAlignment = CrossAxisAlignment.vertical(verticalAlignment),
-                crossAxisSize = SizeMode.Wrap
+                verticalAlignment = verticalAlignment,
             )
         }
     }
 
+internal data class RowMeasurePolicy(
+    private val horizontalArrangement: Arrangement.Horizontal,
+    private val verticalAlignment: Alignment.Vertical
+) : MeasurePolicy,
+    RowColumnMeasurePolicy {
+    override fun Placeable.mainAxisSize() = width
+    override fun Placeable.crossAxisSize() = height
+
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult {
+        return measure(
+            constraints.minWidth,
+            constraints.minHeight,
+            constraints.maxWidth,
+            constraints.maxHeight,
+            horizontalArrangement.spacing.roundToPx(),
+            this,
+            measurables,
+            arrayOfNulls(measurables.size),
+            0,
+            measurables.size
+        )
+    }
+
+    override fun populateMainAxisPositions(
+        mainAxisLayoutSize: Int,
+        childrenMainAxisSize: IntArray,
+        mainAxisPositions: IntArray,
+        measureScope: MeasureScope
+    ) {
+        with(horizontalArrangement) {
+            measureScope.arrange(
+                mainAxisLayoutSize,
+                childrenMainAxisSize,
+                measureScope.layoutDirection,
+                mainAxisPositions
+            )
+        }
+    }
+
+    override fun placeHelper(
+        placeables: Array<Placeable?>,
+        measureScope: MeasureScope,
+        beforeCrossAxisAlignmentLine: Int,
+        mainAxisPositions: IntArray,
+        mainAxisLayoutSize: Int,
+        crossAxisLayoutSize: Int,
+        crossAxisOffset: IntArray?,
+        currentLineIndex: Int,
+        startIndex: Int,
+        endIndex: Int
+    ): MeasureResult {
+        return with(measureScope) {
+            layout(mainAxisLayoutSize, crossAxisLayoutSize) {
+                placeables.forEachIndexed { i, placeable ->
+                    val crossAxisPosition = getCrossAxisPosition(
+                        placeable!!,
+                        placeable.rowColumnParentData,
+                        crossAxisLayoutSize,
+                        beforeCrossAxisAlignmentLine
+                    )
+                    placeable.place(
+                        mainAxisPositions[i],
+                        crossAxisPosition
+                    )
+                }
+            }
+        }
+    }
+
+    override fun createConstraints(
+        mainAxisMin: Int,
+        crossAxisMin: Int,
+        mainAxisMax: Int,
+        crossAxisMax: Int,
+        isPrioritizing: Boolean
+    ): Constraints {
+        return createRowConstraints(
+            isPrioritizing,
+            mainAxisMin,
+            crossAxisMin,
+            mainAxisMax,
+            crossAxisMax
+        )
+    }
+
+    private fun getCrossAxisPosition(
+        placeable: Placeable,
+        parentData: RowColumnParentData?,
+        crossAxisLayoutSize: Int,
+        beforeCrossAxisAlignmentLine: Int
+    ): Int {
+        val childCrossAlignment = parentData?.crossAxisAlignment
+        return childCrossAlignment?.align(
+            size = crossAxisLayoutSize - placeable.height,
+            layoutDirection = LayoutDirection.Ltr,
+            placeable = placeable,
+            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
+        ) ?: verticalAlignment.align(0, crossAxisLayoutSize - placeable.height)
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicWidth(
+        measurables: List<IntrinsicMeasurable>,
+        height: Int
+    ) = IntrinsicMeasureBlocks.HorizontalMinWidth(
+        measurables,
+        height,
+        horizontalArrangement.spacing.roundToPx(),
+    )
+
+    override fun IntrinsicMeasureScope.minIntrinsicHeight(
+        measurables: List<IntrinsicMeasurable>,
+        width: Int
+    ) = IntrinsicMeasureBlocks.HorizontalMinHeight(
+        measurables,
+        width,
+        horizontalArrangement.spacing.roundToPx(),
+    )
+
+    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+        measurables: List<IntrinsicMeasurable>,
+        height: Int
+    ) = IntrinsicMeasureBlocks.HorizontalMaxWidth(
+        measurables,
+        height,
+        horizontalArrangement.spacing.roundToPx(),
+    )
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurables: List<IntrinsicMeasurable>,
+        width: Int
+    ) = IntrinsicMeasureBlocks.HorizontalMaxHeight(
+        measurables,
+        width,
+        horizontalArrangement.spacing.roundToPx(),
+    )
+}
+
+internal fun createRowConstraints(
+    isPrioritizing: Boolean,
+    mainAxisMin: Int,
+    crossAxisMin: Int,
+    mainAxisMax: Int,
+    crossAxisMax: Int
+): Constraints {
+    return if (!isPrioritizing) {
+        Constraints(
+            maxWidth = mainAxisMax,
+            maxHeight = crossAxisMax,
+            minWidth = mainAxisMin,
+            minHeight = crossAxisMin
+        )
+    } else {
+        Constraints.fitPrioritizingWidth(
+            maxWidth = mainAxisMax,
+            maxHeight = crossAxisMax,
+            minWidth = mainAxisMin,
+            minHeight = crossAxisMin
+        )
+    }
+}
+
 /**
  * Scope for the children of [Row].
  */
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index a6c7d31..e011e59 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -24,11 +24,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.IntrinsicMeasurable
-import androidx.compose.ui.layout.IntrinsicMeasureScope
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasurePolicy
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.Measured
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.node.ModifierNodeElement
@@ -36,113 +31,12 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastRoundToInt
 import kotlin.math.max
 import kotlin.math.min
 
-internal data class RowColumnMeasurePolicy(
-    private val orientation: LayoutOrientation,
-    private val horizontalArrangement: Arrangement.Horizontal?,
-    private val verticalArrangement: Arrangement.Vertical?,
-    private val arrangementSpacing: Dp,
-    private val crossAxisSize: SizeMode,
-    private val crossAxisAlignment: CrossAxisAlignment
-) : MeasurePolicy {
-    override fun MeasureScope.measure(
-        measurables: List<Measurable>,
-        constraints: Constraints
-    ): MeasureResult {
-        val rowColumnMeasureHelper =
-            RowColumnMeasurementHelper(
-                orientation,
-                horizontalArrangement,
-                verticalArrangement,
-                crossAxisSize,
-                crossAxisAlignment,
-                RowColumnMeasurablesWrapper(measurables)
-            )
-
-        val measureResult = rowColumnMeasureHelper
-            .measureWithoutPlacing(
-                this,
-                constraints, 0, measurables.size
-            )
-
-        val layoutWidth: Int
-        val layoutHeight: Int
-        if (orientation == LayoutOrientation.Horizontal) {
-            layoutWidth = measureResult.mainAxisSize
-            layoutHeight = measureResult.crossAxisSize
-        } else {
-            layoutWidth = measureResult.crossAxisSize
-            layoutHeight = measureResult.mainAxisSize
-        }
-        return layout(layoutWidth, layoutHeight) {
-            rowColumnMeasureHelper.placeHelper(
-                this,
-                measureResult,
-                0,
-                layoutDirection
-            )
-        }
-    }
-
-    override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurables: List<IntrinsicMeasurable>,
-        height: Int
-    ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMinWidth(
-        measurables,
-        height,
-        arrangementSpacing.roundToPx()
-    ) else IntrinsicMeasureBlocks.VerticalMinWidth(
-        measurables,
-        height,
-        arrangementSpacing.roundToPx()
-    )
-
-    override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurables: List<IntrinsicMeasurable>,
-        width: Int
-    ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMinHeight(
-        measurables,
-        width,
-        arrangementSpacing.roundToPx()
-    ) else IntrinsicMeasureBlocks.VerticalMinHeight(
-        measurables,
-        width,
-        arrangementSpacing.roundToPx()
-    )
-
-    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurables: List<IntrinsicMeasurable>,
-        height: Int
-    ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMaxWidth(
-        measurables,
-        height,
-        arrangementSpacing.roundToPx()
-    ) else IntrinsicMeasureBlocks.VerticalMaxWidth(
-        measurables,
-        height,
-        arrangementSpacing.roundToPx()
-    )
-
-    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurables: List<IntrinsicMeasurable>,
-        width: Int
-    ) = if (orientation == Horizontal) IntrinsicMeasureBlocks.HorizontalMaxHeight(
-        measurables,
-        width,
-        arrangementSpacing.roundToPx()
-    ) else IntrinsicMeasureBlocks.VerticalMaxHeight(
-        measurables,
-        width,
-        arrangementSpacing.roundToPx()
-    )
-}
-
 /**
  * [Row] will be [Horizontal], [Column] is [Vertical].
  */
@@ -351,10 +245,10 @@
     )
 
     constructor(c: Constraints, orientation: LayoutOrientation) : this(
-        if (orientation === LayoutOrientation.Horizontal) c.minWidth else c.minHeight,
-        if (orientation === LayoutOrientation.Horizontal) c.maxWidth else c.maxHeight,
-        if (orientation === LayoutOrientation.Horizontal) c.minHeight else c.minWidth,
-        if (orientation === LayoutOrientation.Horizontal) c.maxHeight else c.maxWidth
+        if (orientation === Horizontal) c.minWidth else c.minHeight,
+        if (orientation === Horizontal) c.maxWidth else c.maxHeight,
+        if (orientation === Horizontal) c.minHeight else c.minWidth,
+        if (orientation === Horizontal) c.maxHeight else c.maxWidth
     )
 
     // Creates a new instance with the same main axis constraints and maximum tight cross axis.
@@ -367,7 +261,7 @@
 
     // Given an orientation, resolves the current instance to traditional constraints.
     fun toBoxConstraints(orientation: LayoutOrientation) =
-        if (orientation === LayoutOrientation.Horizontal) {
+        if (orientation === Horizontal) {
             Constraints(mainAxisMin, mainAxisMax, crossAxisMin, crossAxisMax)
         } else {
             Constraints(crossAxisMin, crossAxisMax, mainAxisMin, mainAxisMax)
@@ -375,7 +269,7 @@
 
     // Given an orientation, resolves the max width constraint this instance represents.
     fun maxWidth(orientation: LayoutOrientation) =
-        if (orientation === LayoutOrientation.Horizontal) {
+        if (orientation === Horizontal) {
             mainAxisMax
         } else {
             crossAxisMax
@@ -383,7 +277,7 @@
 
     // Given an orientation, resolves the max height constraint this instance represents.
     fun maxHeight(orientation: LayoutOrientation) =
-        if (orientation === LayoutOrientation.Horizontal) {
+        if (orientation === Horizontal) {
             crossAxisMax
         } else {
             mainAxisMax
@@ -421,7 +315,7 @@
 internal val RowColumnParentData?.isRelative: Boolean
     get() = this.crossAxisAlignment?.isRelative ?: false
 
-private object IntrinsicMeasureBlocks {
+internal object IntrinsicMeasureBlocks {
     fun HorizontalMinWidth(
         measurables: List<IntrinsicMeasurable>,
         availableHeight: Int,
@@ -825,21 +719,3 @@
         }
     }
 }
-
-/**
- * Used to specify how a layout chooses its own size when multiple behaviors are possible.
- */
-// TODO(popam): remove this when Flow is reworked
-internal enum class SizeMode {
-    /**
-     * Minimize the amount of free space by wrapping the children,
-     * subject to the incoming layout constraints.
-     */
-    Wrap,
-
-    /**
-     * Maximize the amount of free space by expanding to fill the available space,
-     * subject to the incoming layout constraints.
-     */
-    Expand
-}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
new file mode 100644
index 0000000..a7437e6
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurePolicy.kt
@@ -0,0 +1,269 @@
+/*
+ * 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.foundation.layout
+
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.util.fastRoundToInt
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sign
+
+internal interface RowColumnMeasurePolicy {
+    fun Placeable.mainAxisSize(): Int
+    fun Placeable.crossAxisSize(): Int
+    fun populateMainAxisPositions(
+        mainAxisLayoutSize: Int,
+        childrenMainAxisSize: IntArray,
+        mainAxisPositions: IntArray,
+        measureScope: MeasureScope
+    )
+
+    fun placeHelper(
+        placeables: Array<Placeable?>,
+        measureScope: MeasureScope,
+        beforeCrossAxisAlignmentLine: Int,
+        mainAxisPositions: IntArray,
+        mainAxisLayoutSize: Int,
+        crossAxisLayoutSize: Int,
+        crossAxisOffset: IntArray?,
+        currentLineIndex: Int,
+        startIndex: Int,
+        endIndex: Int
+    ): MeasureResult
+
+    fun createConstraints(
+        mainAxisMin: Int,
+        crossAxisMin: Int,
+        mainAxisMax: Int,
+        crossAxisMax: Int,
+        isPrioritizing: Boolean = false
+    ): Constraints
+}
+
+/**
+ * Measures the row and column
+ *
+ * @param measureScope The measure scope to retrieve density
+ * @param startIndex The startIndex (inclusive) when examining measurables, placeable
+ * and parentData
+ * @param endIndex The ending index (exclusive) when examining measurable, placeable
+ * and parentData
+ * @param crossAxisOffset The offset to apply to the cross axis when placing
+ * @param currentLineIndex The index of the current line if in a multi-row/column setting like
+ * [FlowRow]
+ */
+internal fun RowColumnMeasurePolicy.measure(
+    mainAxisMin: Int,
+    crossAxisMin: Int,
+    mainAxisMax: Int,
+    crossAxisMax: Int,
+    arrangementSpacingInt: Int,
+    measureScope: MeasureScope,
+    measurables: List<Measurable>,
+    placeables: Array<Placeable?>,
+    startIndex: Int,
+    endIndex: Int,
+    crossAxisOffset: IntArray? = null,
+    currentLineIndex: Int = 0,
+): MeasureResult {
+    val arrangementSpacingPx = arrangementSpacingInt.toLong()
+
+    var totalWeight = 0f
+    var fixedSpace = 0
+    var crossAxisSpace = 0
+    var weightChildrenCount = 0
+
+    var anyAlignBy = false
+    val subSize = endIndex - startIndex
+    val childrenMainAxisSize = IntArray(subSize)
+
+    var beforeCrossAxisAlignmentLine = 0
+    var afterCrossAxisAlignmentLine = 0
+    // First measure children with zero weight.
+    var spaceAfterLastNoWeight = 0
+
+    for (i in startIndex until endIndex) {
+        val child = measurables[i]
+        val parentData = child.rowColumnParentData
+        val weight = parentData.weight
+        anyAlignBy = anyAlignBy || parentData.isRelative
+
+        if (weight > 0f) {
+            totalWeight += weight
+            ++weightChildrenCount
+        } else {
+            val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) null else
+                parentData?.flowLayoutData?.let {
+                    (it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
+                }
+            val remaining = mainAxisMax - fixedSpace
+            val placeable = placeables[i] ?: child.measure(
+                // Ask for preferred main axis size.
+                createConstraints(
+                    mainAxisMin = 0,
+                    crossAxisMin = crossAxisDesiredSize ?: 0,
+                    mainAxisMax = if (mainAxisMax == Constraints.Infinity) {
+                        Constraints.Infinity
+                    } else {
+                        remaining.coerceAtLeast(0)
+                    },
+                    crossAxisMax = crossAxisDesiredSize ?: crossAxisMax
+                )
+            )
+            val placeableMainAxisSize = placeable.mainAxisSize()
+            val placeableCrossAxisSize = placeable.crossAxisSize()
+            childrenMainAxisSize[i - startIndex] = placeableMainAxisSize
+            spaceAfterLastNoWeight = min(
+                arrangementSpacingInt,
+                (remaining - placeableMainAxisSize).coerceAtLeast(0)
+            )
+            fixedSpace += placeableMainAxisSize + spaceAfterLastNoWeight
+            crossAxisSpace = max(crossAxisSpace, placeableCrossAxisSize)
+            placeables[i] = placeable
+        }
+    }
+
+    var weightedSpace = 0
+    if (weightChildrenCount == 0) {
+        // fixedSpace contains an extra spacing after the last non-weight child.
+        fixedSpace -= spaceAfterLastNoWeight
+    } else {
+        // Measure the rest according to their weights in the remaining main axis space.
+        val targetSpace =
+            if (mainAxisMax != Constraints.Infinity) {
+                mainAxisMax
+            } else {
+                mainAxisMin
+            }
+        val arrangementSpacingTotal = arrangementSpacingPx * (weightChildrenCount - 1)
+        val remainingToTarget =
+            (targetSpace - fixedSpace - arrangementSpacingTotal).coerceAtLeast(0)
+
+        val weightUnitSpace = remainingToTarget / totalWeight
+        var remainder = remainingToTarget
+        for (i in startIndex until endIndex) {
+            val measurable = measurables[i]
+            remainder -=
+                (weightUnitSpace * measurable.rowColumnParentData.weight).fastRoundToInt()
+        }
+
+        for (i in startIndex until endIndex) {
+            if (placeables[i] == null) {
+                val child = measurables[i]
+                val parentData = child.rowColumnParentData
+                val weight = parentData.weight
+                val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) null else
+                    parentData?.flowLayoutData?.let {
+                        (it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
+                    }
+                check(weight > 0) { "All weights <= 0 should have placeables" }
+                // After the weightUnitSpace rounding, the total space going to be occupied
+                // can be smaller or larger than remainingToTarget. Here we distribute the
+                // loss or gain remainder evenly to the first children.
+                val remainderUnit = remainder.sign
+                remainder -= remainderUnit
+                val childMainAxisSize = max(
+                    0,
+                    (weightUnitSpace * weight).fastRoundToInt() + remainderUnit
+                )
+                val childConstraints = createConstraints(
+                    mainAxisMin = if (parentData.fill &&
+                        childMainAxisSize != Constraints.Infinity
+                    ) {
+                        childMainAxisSize
+                    } else {
+                        0
+                    },
+                    crossAxisMin = crossAxisDesiredSize ?: 0,
+                    mainAxisMax = childMainAxisSize,
+                    crossAxisMax = crossAxisDesiredSize ?: crossAxisMax,
+                    isPrioritizing = true
+                )
+                val placeable = child.measure(childConstraints)
+                val placeableMainAxisSize = placeable.mainAxisSize()
+                val placeableCrossAxisSize = placeable.crossAxisSize()
+                childrenMainAxisSize[i - startIndex] = placeableMainAxisSize
+                weightedSpace += placeableMainAxisSize
+                crossAxisSpace = max(crossAxisSpace, placeableCrossAxisSize)
+                placeables[i] = placeable
+            }
+        }
+        weightedSpace = (weightedSpace + arrangementSpacingTotal)
+            .toInt()
+            .coerceIn(0, mainAxisMax - fixedSpace)
+    }
+
+    // we've done this check in weights as to avoid going through another loop
+    if (anyAlignBy) {
+        for (i in startIndex until endIndex) {
+            val placeable = placeables[i]
+            val parentData = placeable!!.rowColumnParentData
+            val alignmentLinePosition = parentData.crossAxisAlignment
+                ?.calculateAlignmentLinePosition(placeable)
+            alignmentLinePosition?.let {
+                val placeableCrossAxisSize = placeable.crossAxisSize()
+                beforeCrossAxisAlignmentLine = max(
+                    beforeCrossAxisAlignmentLine,
+                    if (it != AlignmentLine.Unspecified) alignmentLinePosition else 0
+                )
+                afterCrossAxisAlignmentLine = max(
+                    afterCrossAxisAlignmentLine,
+                    placeableCrossAxisSize - if (it != AlignmentLine.Unspecified) {
+                        it
+                    } else {
+                        placeableCrossAxisSize
+                    }
+                )
+            }
+        }
+    }
+
+    // Compute the Row or Column size and position the children.
+    val mainAxisLayoutSize = max(
+        (fixedSpace + weightedSpace).coerceAtLeast(0),
+        mainAxisMin
+    )
+    val crossAxisLayoutSize = max(
+        crossAxisSpace,
+        max(crossAxisMin, beforeCrossAxisAlignmentLine + afterCrossAxisAlignmentLine)
+    )
+    val mainAxisPositions = IntArray(subSize) { 0 }
+    populateMainAxisPositions(
+        mainAxisLayoutSize,
+        childrenMainAxisSize,
+        mainAxisPositions,
+        measureScope
+    )
+
+    return placeHelper(
+        placeables,
+        measureScope,
+        beforeCrossAxisAlignmentLine,
+        mainAxisPositions,
+        mainAxisLayoutSize,
+        crossAxisLayoutSize,
+        crossAxisOffset,
+        currentLineIndex,
+        startIndex,
+        endIndex
+    )
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
deleted file mode 100644
index 90a6097..0000000
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
+++ /dev/null
@@ -1,479 +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.foundation.layout
-
-import androidx.collection.IntList
-import androidx.collection.MutableIntObjectMap
-import androidx.collection.mutableIntObjectMapOf
-import androidx.compose.ui.layout.AlignmentLine
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.util.fastRoundToInt
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.sign
-
-/**
- * This is a data class that holds the determined width, height of a row,
- * and information on how to retrieve main axis and cross axis positions.
- */
-internal class RowColumnMeasureHelperResult(
-    val crossAxisSize: Int,
-    val mainAxisSize: Int,
-    val startIndex: Int,
-    val endIndex: Int,
-    val beforeCrossAxisAlignmentLine: Int,
-    val mainAxisPositions: IntArray,
-)
-
-/**
- * RowColumnMeasurementHelper
- * Measures the row and column without placing, useful for reusing row/column logic
- */
-internal class RowColumnMeasurementHelper(
-    val orientation: LayoutOrientation,
-    val horizontalArrangement: Arrangement.Horizontal?,
-    val verticalArrangement: Arrangement.Vertical?,
-    val crossAxisSize: SizeMode,
-    val crossAxisAlignment: CrossAxisAlignment,
-    val listWrapper: RowColumnMeasurablesWrapper
-) {
-
-    val mainAxisSpacing = if (orientation == LayoutOrientation.Horizontal) {
-        requireNotNull(horizontalArrangement) { "null horizontalArrangement in Row/FlowRow" }
-        horizontalArrangement.spacing
-    } else {
-        requireNotNull(verticalArrangement) { "null verticalArrangement in Column/FlowColumn" }
-        verticalArrangement.spacing
-    }
-
-    fun Placeable.mainAxisSize() =
-        if (orientation == LayoutOrientation.Horizontal) width else height
-
-    fun Placeable.crossAxisSize() =
-        if (orientation == LayoutOrientation.Horizontal) height else width
-
-    /**
-     * Measures the row and column without placing, useful for reusing row/column logic
-     *
-     * @param measureScope The measure scope to retrieve density
-     * @param constraints The desired constraints for the startIndex and endIndex
-     * can hold null items if not measured.
-     * @param startIndex The startIndex (inclusive) when examining measurables, placeable
-     * and parentData
-     * @param endIndex The ending index (exclusive) when examinning measurable, placeable
-     * and parentData
-     */
-    fun measureWithoutPlacing(
-        measureScope: MeasureScope,
-        constraints: Constraints,
-        startIndex: Int,
-        endIndex: Int
-    ): RowColumnMeasureHelperResult {
-        @Suppress("NAME_SHADOWING")
-        val constraints = OrientationIndependentConstraints(constraints, orientation)
-        val arrangementSpacingPx = with(measureScope) {
-            mainAxisSpacing.roundToPx().toLong()
-        }
-
-        var totalWeight = 0f
-        var fixedSpace = 0L
-        var crossAxisSpace = 0
-        var weightChildrenCount = 0
-
-        var anyAlignBy = false
-        val subSize = listWrapper.subSize(startIndex, endIndex)
-
-        // First measure children with zero weight.
-        var spaceAfterLastNoWeight = 0
-        listWrapper.forEachIndexed(startIndex, endIndex) { i, child, placeableCache ->
-            val parentData = child.rowColumnParentData
-            val weight = parentData.weight
-
-            if (weight > 0f) {
-                totalWeight += weight
-                ++weightChildrenCount
-            } else {
-                val mainAxisMax = constraints.mainAxisMax
-                val crossAxisMax = constraints.crossAxisMax
-                val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) null else
-                    parentData?.flowLayoutData?.let {
-                        (it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
-                    }
-                val placeable = placeableCache ?: child.measure(
-                    // Ask for preferred main axis size.
-                    constraints.copy(
-                        mainAxisMin = 0,
-                        mainAxisMax = if (mainAxisMax == Constraints.Infinity) {
-                            Constraints.Infinity
-                        } else {
-                            (mainAxisMax - fixedSpace).coerceAtLeast(0).toInt()
-                        },
-                        crossAxisMin = crossAxisDesiredSize ?: 0,
-                        crossAxisMax = crossAxisDesiredSize ?: constraints.crossAxisMax
-                    ).toBoxConstraints(orientation)
-                )
-                spaceAfterLastNoWeight = min(
-                    arrangementSpacingPx.toInt(),
-                    (mainAxisMax - fixedSpace - placeable.mainAxisSize())
-                        .coerceAtLeast(0).toInt()
-                )
-                fixedSpace += placeable.mainAxisSize() + spaceAfterLastNoWeight
-                crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
-                anyAlignBy = anyAlignBy || parentData.isRelative
-                listWrapper.setPlaceable(i, placeable)
-            }
-        }
-
-        var weightedSpace = 0
-        if (weightChildrenCount == 0) {
-            // fixedSpace contains an extra spacing after the last non-weight child.
-            fixedSpace -= spaceAfterLastNoWeight
-        } else {
-            // Measure the rest according to their weights in the remaining main axis space.
-            val targetSpace =
-                if (totalWeight > 0f && constraints.mainAxisMax != Constraints.Infinity) {
-                    constraints.mainAxisMax
-                } else {
-                    constraints.mainAxisMin
-                }
-            val arrangementSpacingTotal = arrangementSpacingPx * (weightChildrenCount - 1)
-            val remainingToTarget =
-                (targetSpace - fixedSpace - arrangementSpacingTotal).coerceAtLeast(0)
-
-            val weightUnitSpace = if (totalWeight > 0) remainingToTarget / totalWeight else 0f
-            var remainder = remainingToTarget
-            listWrapper.forEachIndexed(startIndex, endIndex) { _, measurable, _ ->
-                remainder -=
-                    (weightUnitSpace * measurable.rowColumnParentData.weight).fastRoundToInt()
-            }
-
-            listWrapper.forEachIndexed(startIndex, endIndex) { i, child, placeableCache ->
-                if (placeableCache == null) {
-                    val parentData = child.rowColumnParentData
-                    val weight = parentData.weight
-                    val crossAxisMax = constraints.crossAxisMax
-                    val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) null else
-                        parentData?.flowLayoutData?.let {
-                            (it.fillCrossAxisFraction * crossAxisMax).fastRoundToInt()
-                        }
-                    check(weight > 0) { "All weights <= 0 should have placeables" }
-                    // After the weightUnitSpace rounding, the total space going to be occupied
-                    // can be smaller or larger than remainingToTarget. Here we distribute the
-                    // loss or gain remainder evenly to the first children.
-                    val remainderUnit = remainder.sign
-                    remainder -= remainderUnit
-                    val childMainAxisSize = max(
-                        0,
-                        (weightUnitSpace * weight).fastRoundToInt() + remainderUnit
-                    )
-                    val restrictedConstraints = Constraints.fitPrioritizingWidth(
-                        minWidth = if (parentData.fill &&
-                            childMainAxisSize != Constraints.Infinity
-                        ) {
-                            childMainAxisSize
-                        } else {
-                            0
-                        },
-                        maxWidth = childMainAxisSize,
-                        minHeight = crossAxisDesiredSize ?: 0,
-                        maxHeight = crossAxisDesiredSize ?: constraints.crossAxisMax
-                    )
-                    val childConstraints = if (orientation == LayoutOrientation.Horizontal) {
-                        restrictedConstraints
-                    } else {
-                        Constraints(
-                            minHeight = restrictedConstraints.minWidth,
-                            maxHeight = restrictedConstraints.maxWidth,
-                            minWidth = restrictedConstraints.minHeight,
-                            maxWidth = restrictedConstraints.maxHeight,
-                        )
-                    }
-                    val placeable = child.measure(childConstraints)
-                    weightedSpace += placeable.mainAxisSize()
-                    crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
-                    anyAlignBy = anyAlignBy || parentData.isRelative
-                    listWrapper.setPlaceable(i, placeable)
-                }
-            }
-            weightedSpace = (weightedSpace + arrangementSpacingTotal)
-                .coerceIn(0, constraints.mainAxisMax - fixedSpace)
-                .toInt()
-        }
-
-        var beforeCrossAxisAlignmentLine = 0
-        var afterCrossAxisAlignmentLine = 0
-        if (anyAlignBy) {
-            listWrapper.forEachIndexed(startIndex, endIndex) { _, _, placeable ->
-                val parentData = placeable!!.rowColumnParentData
-                val alignmentLinePosition = parentData.crossAxisAlignment
-                    ?.calculateAlignmentLinePosition(placeable)
-                if (alignmentLinePosition != null) {
-                    beforeCrossAxisAlignmentLine = max(
-                        beforeCrossAxisAlignmentLine,
-                        alignmentLinePosition.let {
-                            if (it != AlignmentLine.Unspecified) it else 0
-                        }
-                    )
-                    afterCrossAxisAlignmentLine = max(
-                        afterCrossAxisAlignmentLine,
-                        placeable.crossAxisSize() -
-                            (
-                                alignmentLinePosition.let {
-                                    if (it != AlignmentLine.Unspecified) {
-                                        it
-                                    } else {
-                                        placeable.crossAxisSize()
-                                    }
-                                }
-                                )
-                    )
-                }
-            }
-        }
-
-        // Compute the Row or Column size and position the children.
-        val mainAxisLayoutSize = max(
-            (fixedSpace + weightedSpace).coerceAtLeast(0).toInt(),
-            constraints.mainAxisMin
-        )
-        val crossAxisLayoutSize = if (constraints.crossAxisMax != Constraints.Infinity &&
-            crossAxisSize == SizeMode.Expand
-        ) {
-            constraints.crossAxisMax
-        } else {
-            max(
-                crossAxisSpace,
-                max(
-                    constraints.crossAxisMin,
-                    beforeCrossAxisAlignmentLine + afterCrossAxisAlignmentLine
-                )
-            )
-        }
-        val mainAxisPositions = IntArray(subSize) { 0 }
-        val childrenMainAxisSize = IntArray(subSize)
-        listWrapper.forEachIndexed(startIndex, endIndex) { i, _, placeable ->
-            childrenMainAxisSize[i - startIndex] = placeable!!.mainAxisSize()
-        }
-
-        return RowColumnMeasureHelperResult(
-            mainAxisSize = mainAxisLayoutSize,
-            crossAxisSize = crossAxisLayoutSize,
-            startIndex = startIndex,
-            endIndex = endIndex,
-            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine,
-            mainAxisPositions = mainAxisPositions(
-                mainAxisLayoutSize,
-                childrenMainAxisSize,
-                mainAxisPositions,
-                measureScope
-            ))
-    }
-
-    private fun mainAxisPositions(
-        mainAxisLayoutSize: Int,
-        childrenMainAxisSize: IntArray,
-        mainAxisPositions: IntArray,
-        measureScope: MeasureScope
-    ): IntArray {
-        if (orientation == LayoutOrientation.Vertical) {
-            with(requireNotNull(verticalArrangement) { "null verticalArrangement in Column" }) {
-                measureScope.arrange(
-                    mainAxisLayoutSize,
-                    childrenMainAxisSize,
-                    mainAxisPositions
-                )
-            }
-        } else {
-            with(requireNotNull(horizontalArrangement) { "null horizontalArrangement in Row" }) {
-                measureScope.arrange(
-                    mainAxisLayoutSize,
-                    childrenMainAxisSize,
-                    measureScope.layoutDirection,
-                    mainAxisPositions
-                )
-            }
-        }
-        return mainAxisPositions
-    }
-
-    private fun getCrossAxisPosition(
-        placeable: Placeable,
-        parentData: RowColumnParentData?,
-        crossAxisLayoutSize: Int,
-        layoutDirection: LayoutDirection,
-        beforeCrossAxisAlignmentLine: Int
-    ): Int {
-        val childCrossAlignment = parentData?.crossAxisAlignment ?: crossAxisAlignment
-        return childCrossAlignment.align(
-            size = crossAxisLayoutSize - placeable.crossAxisSize(),
-            layoutDirection = if (orientation == LayoutOrientation.Horizontal) {
-                LayoutDirection.Ltr
-            } else {
-                layoutDirection
-            },
-            placeable = placeable,
-            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
-        )
-    }
-    fun placeHelper(
-        placeableScope: Placeable.PlacementScope,
-        measureResult: RowColumnMeasureHelperResult,
-        crossAxisOffset: Int,
-        layoutDirection: LayoutDirection,
-    ) {
-        with(placeableScope) {
-            listWrapper.forEachIndexed(
-                measureResult.startIndex, measureResult.endIndex
-            ) { i, _, placeable ->
-                placeable!!
-                val mainAxisPositions = measureResult.mainAxisPositions
-                val crossAxisPosition = getCrossAxisPosition(
-                    placeable,
-                    placeable.rowColumnParentData,
-                    measureResult.crossAxisSize,
-                    layoutDirection,
-                    measureResult.beforeCrossAxisAlignmentLine
-                ) + crossAxisOffset
-                if (orientation == LayoutOrientation.Horizontal) {
-                    placeable.place(
-                        mainAxisPositions[i - measureResult.startIndex],
-                        crossAxisPosition
-                    )
-                } else {
-                    placeable.place(
-                        crossAxisPosition,
-                        mainAxisPositions[i - measureResult.startIndex]
-                    )
-                }
-            }
-        }
-    }
-}
-
-/**
- * The wrapper allows setting a range for processing its contents,
- * defined by startIndex and endIndex
- * and also considers the ellipsis handling as well.
- */
-internal class RowColumnMeasurablesWrapper(
-    private val measurables: List<Measurable>,
-    private val placeables: MutableIntObjectMap<Placeable?> = mutableIntObjectMapOf(),
-    private val ellipsis: Measurable? = null,
-    private val ellipsisOnLineContent: Boolean? = null,
-) {
-
-    private val contentStart: Int = 0
-    private val contentEnd: Int = measurables.size
-    private var ellipsisPlaceable: Placeable? = null
-
-    /**
-     * Breaks the loop into multiple lines, taking into considering the ellipsis
-     */
-    inline fun forEachLine(
-        breakLineIndices: IntList,
-        action: (lineNo: Int, startIndex: Int, endIndex: Int) -> Unit
-    ) {
-        var start = contentStart
-        var lineNo = 0
-        breakLineIndices.forEach { end ->
-            require(end in (start + 1)..contentEnd) {
-                "For each line, contentStartIndex must be less than or equal to contentEndIndex "
-            }
-            action(lineNo, start, end)
-            start = end
-            lineNo++
-        }
-        val end = start
-        if (end == contentEnd && ellipsis != null && ellipsisOnLineContent == false) {
-            // create a new line where no content is included, but just used for the ellipsis
-            action(lineNo, end, end)
-        }
-    }
-
-    /**
-     * ForEach loop that iterates through the range of the
-     * current [contentStart] and [contentEnd] to iterate through.
-     * If the [contentEnd] aligns with our [contentEnd], we also provide the ellipsis measurable
-     * as the last for each return.
-     */
-    inline fun forEachIndexed(
-        contentStart: Int,
-        contentEnd: Int,
-        action: (index: Int, measurable: Measurable, placeable: Placeable?) -> Unit
-    ) {
-        require(contentStart <= contentEnd && contentEnd <= measurables.size) {
-            "contentStartIndex must be less than or equal to contentEndIndex " +
-                "and contentEndIndex must be less than or equal to list size"
-        }
-        var i = contentStart
-        while (i < contentEnd) {
-            action(i, measurables[i], placeables[i])
-            i++
-        }
-        val hasEllipsisOnContentLine =
-            ellipsis != null &&
-                contentEnd == this.contentEnd &&
-                ellipsisOnLineContent == true
-        val isEllipsisLineOnly =
-            ellipsis != null &&
-                contentStart == contentEnd &&
-                contentEnd == this.contentEnd
-        if (hasEllipsisOnContentLine || isEllipsisLineOnly) {
-            ellipsis?.let { action(i, it, ellipsisPlaceable) }
-        }
-    }
-
-    fun subSize(
-        contentStart: Int,
-        contentEnd: Int
-    ): Int {
-        require(contentStart <= contentEnd && contentEnd <= measurables.size) {
-            "contentStartIndex must be less than or equal to contentEndIndex " +
-                "and contentEndIndex must be less than or equal to list size"
-        }
-        val subSize = contentEnd - contentStart
-
-        val hasEllipsisOnContentLine =
-            ellipsis != null &&
-                contentEnd == this.contentEnd &&
-                ellipsisOnLineContent == true
-        // lines with only ellipsis have content starts that
-        // have reached the end of the list allowed.
-        val isEllipsisLineOnly =
-            ellipsis != null &&
-                contentStart == contentEnd &&
-                contentEnd == this.contentEnd
-
-        return if ((hasEllipsisOnContentLine || isEllipsisLineOnly)) {
-            subSize + 1
-        } else {
-            subSize
-        }
-    }
-
-    /**
-     * setPlaceable based on the index provided by [forEachIndexed]
-     * If the index goes over the content end, the ellipsis placeable is set
-     */
-    fun setPlaceable(i: Int, placeable: Placeable) {
-        if (i < contentEnd) { placeables[i] = placeable } else ellipsisPlaceable = placeable
-    }
-}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 9d277bb..6d6f507 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -320,11 +320,10 @@
   }
 
   public final class ReceiveContentKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier receiveContent(androidx.compose.ui.Modifier, java.util.Set<androidx.compose.foundation.content.MediaType> hintMediaTypes, androidx.compose.foundation.content.ReceiveContentListener receiveContentListener);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier receiveContent(androidx.compose.ui.Modifier, java.util.Set<androidx.compose.foundation.content.MediaType> hintMediaTypes, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.content.TransferableContent,androidx.compose.foundation.content.TransferableContent?> onReceive);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier contentReceiver(androidx.compose.ui.Modifier, androidx.compose.foundation.content.ReceiveContentListener receiveContentListener);
   }
 
-  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface ReceiveContentListener {
+  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public fun interface ReceiveContentListener {
     method public default void onDragEnd();
     method public default void onDragEnter();
     method public default void onDragExit();
@@ -357,7 +356,7 @@
   }
 
   public final class TransferableContent_androidKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.content.TransferableContent? consumeEach(androidx.compose.foundation.content.TransferableContent, kotlin.jvm.functions.Function1<? super android.content.ClipData.Item,java.lang.Boolean> predicate);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.content.TransferableContent? consume(androidx.compose.foundation.content.TransferableContent, kotlin.jvm.functions.Function1<? super android.content.ClipData.Item,java.lang.Boolean> predicate);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static boolean hasMediaType(androidx.compose.foundation.content.TransferableContent, androidx.compose.foundation.content.MediaType mediaType);
   }
 
@@ -392,6 +391,7 @@
   public final class AnchoredDraggableKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.DraggableAnchors<T> DraggableAnchors(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.gestures.DraggableAnchorsConfig<T>,kotlin.Unit> builder);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? snapTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -794,7 +794,8 @@
   }
 
   @androidx.compose.foundation.lazy.LazyScopeMarker @androidx.compose.runtime.Stable @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyItemScope {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.ui.Modifier animateItemPlacement(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec);
+    method public default androidx.compose.ui.Modifier animateItem(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeInSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? placementSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeOutSpec);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public default androidx.compose.ui.Modifier animateItemPlacement(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec);
     method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
     method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
     method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
@@ -865,7 +866,6 @@
     method public androidx.compose.foundation.interaction.InteractionSource getInteractionSource();
     method public androidx.compose.foundation.lazy.LazyListLayoutInfo getLayoutInfo();
     method public boolean isScrollInProgress();
-    method public void notifyPrefetchOnScroll(float delta, androidx.compose.foundation.lazy.LazyListLayoutInfo layoutInfo);
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(@IntRange(from=0L) int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
@@ -1298,7 +1298,7 @@
 
   public final class PagerDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior(androidx.compose.foundation.pager.PagerState state, optional androidx.compose.foundation.pager.PagerSnapDistance pagerSnapDistance, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional @FloatRange(from=0.0, to=1.0) float snapPositionalThreshold);
-    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.pager.PagerState state, androidx.compose.foundation.gestures.Orientation orientation);
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.pager.PagerState state, androidx.compose.foundation.gestures.Orientation orientation);
     field public static final androidx.compose.foundation.pager.PagerDefaults INSTANCE;
     field public static final int OutOfBoundsPageCount = 0; // 0x0
   }
@@ -1536,7 +1536,7 @@
 package androidx.compose.foundation.text {
 
   public final class BasicSecureTextFieldKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional int textObfuscationMode);
   }
 
   public final class BasicTextFieldKt {
@@ -1609,26 +1609,26 @@
   @androidx.compose.runtime.Immutable public final class KeyboardOptions {
     ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
     ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
-    ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
-    ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
+    ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus);
+    ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
     method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
     method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
-    method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
-    method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
+    method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus);
+    method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
     method public boolean getAutoCorrect();
     method public int getCapitalization();
     method public androidx.compose.ui.text.intl.LocaleList? getHintLocales();
     method public int getImeAction();
     method public int getKeyboardType();
     method public androidx.compose.ui.text.input.PlatformImeOptions? getPlatformImeOptions();
-    method public boolean getShouldShowKeyboardOnFocus();
+    method public Boolean? getShowKeyboardOnFocus();
     property public final boolean autoCorrect;
     property public final int capitalization;
     property public final androidx.compose.ui.text.intl.LocaleList? hintLocales;
     property public final int imeAction;
     property public final int keyboardType;
     property public final androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions;
-    property public final boolean shouldShowKeyboardOnFocus;
+    property public final Boolean? showKeyboardOnFocus;
     field public static final androidx.compose.foundation.text.KeyboardOptions.Companion Companion;
   }
 
@@ -1639,6 +1639,18 @@
 
 }
 
+package androidx.compose.foundation.text.handwriting {
+
+  public final class HandwritingDelegate_androidKt {
+    method public static androidx.compose.ui.Modifier handwritingDelegate(androidx.compose.ui.Modifier);
+  }
+
+  public final class HandwritingDelegator_androidKt {
+    method public static androidx.compose.ui.Modifier handwritingDelegator(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> callback);
+  }
+
+}
+
 package androidx.compose.foundation.text.input {
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface ImeActionHandler {
@@ -1748,9 +1760,13 @@
   @androidx.compose.runtime.Stable public final class TextFieldState {
     ctor public TextFieldState(optional String initialText, optional long initialSelection);
     method public inline void edit(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.text.input.TextFieldBuffer,kotlin.Unit> block);
-    method public androidx.compose.foundation.text.input.TextFieldCharSequence getText();
+    method public androidx.compose.ui.text.TextRange? getComposition();
+    method public long getSelection();
+    method public CharSequence getText();
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.foundation.text.input.UndoState getUndoState();
-    property public final androidx.compose.foundation.text.input.TextFieldCharSequence text;
+    property public final androidx.compose.ui.text.TextRange? composition;
+    property public final long selection;
+    property public final CharSequence text;
     property @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final androidx.compose.foundation.text.input.UndoState undoState;
   }
 
@@ -1762,11 +1778,10 @@
 
   public final class TextFieldStateKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void clearText(androidx.compose.foundation.text.input.TextFieldState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend Object? forEachTextValue(androidx.compose.foundation.text.input.TextFieldState, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.text.input.TextFieldCharSequence,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<?>);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.text.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelection);
+    method @androidx.compose.runtime.Composable public static androidx.compose.foundation.text.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelection);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndPlaceCursorAtEnd(androidx.compose.foundation.text.input.TextFieldState, String text);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndSelectAll(androidx.compose.foundation.text.input.TextFieldState, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text.input.TextFieldCharSequence> textAsFlow(androidx.compose.foundation.text.input.TextFieldState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text.input.TextFieldCharSequence> valueAsFlow(androidx.compose.foundation.text.input.TextFieldState);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @kotlin.jvm.JvmInline public final value class TextObfuscationMode {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index f58c441..2dd045c 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -322,11 +322,10 @@
   }
 
   public final class ReceiveContentKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier receiveContent(androidx.compose.ui.Modifier, java.util.Set<androidx.compose.foundation.content.MediaType> hintMediaTypes, androidx.compose.foundation.content.ReceiveContentListener receiveContentListener);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier receiveContent(androidx.compose.ui.Modifier, java.util.Set<androidx.compose.foundation.content.MediaType> hintMediaTypes, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.content.TransferableContent,androidx.compose.foundation.content.TransferableContent?> onReceive);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier contentReceiver(androidx.compose.ui.Modifier, androidx.compose.foundation.content.ReceiveContentListener receiveContentListener);
   }
 
-  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface ReceiveContentListener {
+  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public fun interface ReceiveContentListener {
     method public default void onDragEnd();
     method public default void onDragEnter();
     method public default void onDragExit();
@@ -359,7 +358,7 @@
   }
 
   public final class TransferableContent_androidKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.content.TransferableContent? consumeEach(androidx.compose.foundation.content.TransferableContent, kotlin.jvm.functions.Function1<? super android.content.ClipData.Item,java.lang.Boolean> predicate);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.content.TransferableContent? consume(androidx.compose.foundation.content.TransferableContent, kotlin.jvm.functions.Function1<? super android.content.ClipData.Item,java.lang.Boolean> predicate);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static boolean hasMediaType(androidx.compose.foundation.content.TransferableContent, androidx.compose.foundation.content.MediaType mediaType);
   }
 
@@ -394,6 +393,7 @@
   public final class AnchoredDraggableKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.foundation.gestures.DraggableAnchors<T> DraggableAnchors(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.gestures.DraggableAnchorsConfig<T>,kotlin.Unit> builder);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean startDragImmediately);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend <T> Object? snapTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -796,7 +796,8 @@
   }
 
   @androidx.compose.foundation.lazy.LazyScopeMarker @androidx.compose.runtime.Stable @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyItemScope {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.ui.Modifier animateItemPlacement(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec);
+    method public default androidx.compose.ui.Modifier animateItem(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeInSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset>? placementSpec, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float>? fadeOutSpec);
+    method @Deprecated @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public default androidx.compose.ui.Modifier animateItemPlacement(androidx.compose.ui.Modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec);
     method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
     method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
     method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
@@ -867,7 +868,6 @@
     method public androidx.compose.foundation.interaction.InteractionSource getInteractionSource();
     method public androidx.compose.foundation.lazy.LazyListLayoutInfo getLayoutInfo();
     method public boolean isScrollInProgress();
-    method public void notifyPrefetchOnScroll(float delta, androidx.compose.foundation.lazy.LazyListLayoutInfo layoutInfo);
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(@IntRange(from=0L) int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
@@ -1300,7 +1300,7 @@
 
   public final class PagerDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior(androidx.compose.foundation.pager.PagerState state, optional androidx.compose.foundation.pager.PagerSnapDistance pagerSnapDistance, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional @FloatRange(from=0.0, to=1.0) float snapPositionalThreshold);
-    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.pager.PagerState state, androidx.compose.foundation.gestures.Orientation orientation);
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.input.nestedscroll.NestedScrollConnection pageNestedScrollConnection(androidx.compose.foundation.pager.PagerState state, androidx.compose.foundation.gestures.Orientation orientation);
     field public static final androidx.compose.foundation.pager.PagerDefaults INSTANCE;
     field public static final int OutOfBoundsPageCount = 0; // 0x0
   }
@@ -1538,7 +1538,7 @@
 package androidx.compose.foundation.text {
 
   public final class BasicSecureTextFieldKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional int imeAction, optional int textObfuscationMode, optional int keyboardType, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional androidx.compose.foundation.ScrollState scrollState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicSecureTextField(androidx.compose.foundation.text.input.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.text.input.ImeActionHandler? onSubmit, optional boolean enabled, optional androidx.compose.foundation.text.input.InputTransformation? inputTransformation, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super kotlin.jvm.functions.Function0<androidx.compose.ui.text.TextLayoutResult?>,kotlin.Unit>? onTextLayout, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional androidx.compose.foundation.text.input.TextFieldDecorator? decorator, optional int textObfuscationMode);
   }
 
   public final class BasicTextFieldKt {
@@ -1611,26 +1611,26 @@
   @androidx.compose.runtime.Immutable public final class KeyboardOptions {
     ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
     ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
-    ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
-    ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
+    ctor @Deprecated public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus);
+    ctor public KeyboardOptions(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
     method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction);
     method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions);
-    method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean shouldShowKeyboardOnFocus);
-    method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional boolean showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
+    method @Deprecated public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus);
+    method public androidx.compose.foundation.text.KeyboardOptions copy(optional int capitalization, optional boolean autoCorrect, optional int keyboardType, optional int imeAction, optional androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions, optional Boolean? showKeyboardOnFocus, optional androidx.compose.ui.text.intl.LocaleList? hintLocales);
     method public boolean getAutoCorrect();
     method public int getCapitalization();
     method public androidx.compose.ui.text.intl.LocaleList? getHintLocales();
     method public int getImeAction();
     method public int getKeyboardType();
     method public androidx.compose.ui.text.input.PlatformImeOptions? getPlatformImeOptions();
-    method public boolean getShouldShowKeyboardOnFocus();
+    method public Boolean? getShowKeyboardOnFocus();
     property public final boolean autoCorrect;
     property public final int capitalization;
     property public final androidx.compose.ui.text.intl.LocaleList? hintLocales;
     property public final int imeAction;
     property public final int keyboardType;
     property public final androidx.compose.ui.text.input.PlatformImeOptions? platformImeOptions;
-    property public final boolean shouldShowKeyboardOnFocus;
+    property public final Boolean? showKeyboardOnFocus;
     field public static final androidx.compose.foundation.text.KeyboardOptions.Companion Companion;
   }
 
@@ -1641,6 +1641,18 @@
 
 }
 
+package androidx.compose.foundation.text.handwriting {
+
+  public final class HandwritingDelegate_androidKt {
+    method public static androidx.compose.ui.Modifier handwritingDelegate(androidx.compose.ui.Modifier);
+  }
+
+  public final class HandwritingDelegator_androidKt {
+    method public static androidx.compose.ui.Modifier handwritingDelegator(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function0<kotlin.Unit> callback);
+  }
+
+}
+
 package androidx.compose.foundation.text.input {
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public fun interface ImeActionHandler {
@@ -1751,10 +1763,15 @@
     ctor public TextFieldState(optional String initialText, optional long initialSelection);
     method @kotlin.PublishedApi internal void commitEdit(androidx.compose.foundation.text.input.TextFieldBuffer newValue);
     method public inline void edit(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.text.input.TextFieldBuffer,kotlin.Unit> block);
-    method public androidx.compose.foundation.text.input.TextFieldCharSequence getText();
+    method @kotlin.PublishedApi internal void finishEditing();
+    method public androidx.compose.ui.text.TextRange? getComposition();
+    method public long getSelection();
+    method public CharSequence getText();
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public androidx.compose.foundation.text.input.UndoState getUndoState();
-    method @kotlin.PublishedApi internal androidx.compose.foundation.text.input.TextFieldBuffer startEdit(androidx.compose.foundation.text.input.TextFieldCharSequence value);
-    property public final androidx.compose.foundation.text.input.TextFieldCharSequence text;
+    method @kotlin.PublishedApi internal androidx.compose.foundation.text.input.TextFieldBuffer startEdit();
+    property public final androidx.compose.ui.text.TextRange? composition;
+    property public final long selection;
+    property public final CharSequence text;
     property @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final androidx.compose.foundation.text.input.UndoState undoState;
   }
 
@@ -1766,11 +1783,10 @@
 
   public final class TextFieldStateKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void clearText(androidx.compose.foundation.text.input.TextFieldState);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static suspend Object? forEachTextValue(androidx.compose.foundation.text.input.TextFieldState, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.text.input.TextFieldCharSequence,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<?>);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.foundation.text.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelection);
+    method @androidx.compose.runtime.Composable public static androidx.compose.foundation.text.input.TextFieldState rememberTextFieldState(optional String initialText, optional long initialSelection);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndPlaceCursorAtEnd(androidx.compose.foundation.text.input.TextFieldState, String text);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static void setTextAndSelectAll(androidx.compose.foundation.text.input.TextFieldState, String text);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text.input.TextFieldCharSequence> textAsFlow(androidx.compose.foundation.text.input.TextFieldState);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static kotlinx.coroutines.flow.Flow<androidx.compose.foundation.text.input.TextFieldCharSequence> valueAsFlow(androidx.compose.foundation.text.input.TextFieldState);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @kotlin.jvm.JvmInline public final value class TextObfuscationMode {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index 9a91c06..66b6ccb 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.demos
 
+import androidx.compose.foundation.demos.contextmenu.ContextMenuDemos
 import androidx.compose.foundation.demos.draganddrop.DragAndDropMultiAppDemo
 import androidx.compose.foundation.demos.draganddrop.DragAndDropNestedDemo
 import androidx.compose.foundation.demos.focus.FocusGroupDemo
@@ -91,6 +92,8 @@
         ComposableDemo("Scrollable with focused child") { ScrollableFocusedChildDemo() },
         ComposableDemo("Window insets") { WindowInsetsDemo() },
         ComposableDemo("Marquee") { BasicMarqueeDemo() },
-        DemoCategory("Pointer Icon", PointerIconDemos)
+        DemoCategory("Pointer Icon", PointerIconDemos),
+        DemoCategory("Long screenshots", ScrollingScreenshotsDemos),
+        DemoCategory("Context Menu", ContextMenuDemos),
     )
 )
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt
index 303631af..9b2a18f 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt
@@ -249,7 +249,7 @@
                 translationY = dragDropState.previousItemOffset.value
             }
     } else {
-        Modifier.animateItemPlacement()
+        Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
     }
     Column(modifier = modifier.then(draggingModifier)) {
         content(dragging)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index 4634941..2c7ae2c 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -871,7 +871,7 @@
                 key = { it }
             ) {
                 var counter by rememberSaveable { mutableIntStateOf(0) }
-                Button(onClick = { counter++ }, modifier = Modifier.animateItemPlacement()) {
+                Button(onClick = { counter++ }, modifier = Modifier.animateItem()) {
                     Text("$it has $counter")
                 }
             }
@@ -1071,7 +1071,7 @@
                 .weight(1f), reverseLayout = reverse) {
             items(items, key = { it }) { item ->
                 val selected = selectedIndexes.getOrDefault(item, false)
-                val modifier = Modifier.animateItemPlacement()
+                val modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
                 var height by remember { mutableStateOf(40.dp) }
                 Row(
                     modifier
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/PopularBooksDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/PopularBooksDemo.kt
index b8608fc..137b758 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/PopularBooksDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/PopularBooksDemo.kt
@@ -102,7 +102,7 @@
                 val sortedList = PopularBooksList.sortedWith(comparator)
                 items(sortedList, key = { it.title }) {
                     Row(
-                        Modifier.animateItemPlacement()
+                        Modifier.animateItem()
                             .height(IntrinsicSize.Max),
                         horizontalArrangement = Arrangement.spacedBy(8.dp)
                     ) {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ScrollingScreenshotDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ScrollingScreenshotDemo.kt
new file mode 100644
index 0000000..166cecc
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ScrollingScreenshotDemo.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos
+
+import android.content.Context
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.LinearLayout
+import android.widget.ScrollView
+import android.widget.TextView
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.integration.demos.common.ComposableDemo
+import androidx.compose.material.BottomAppBar
+import androidx.compose.material.Button
+import androidx.compose.material.Divider
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Switch
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.primarySurface
+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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.scrollcapture.ComposeFeatureFlag_LongScreenshotsEnabled
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.compose.ui.window.Dialog
+
+val ScrollingScreenshotsDemos = listOf(
+    ComposableDemo("Single, small, eager list") { SingleEagerListDemo() },
+    ComposableDemo("Single, small, lazy list") { SingleLazyListDemo() },
+    ComposableDemo("Single, full-screen list") { SingleFullScreenListDemo() },
+    ComposableDemo("Lazy list with content padding") { LazyListContentPaddingDemo() },
+    ComposableDemo("Big viewport nested in smaller outer viewport") { BigInLittleDemo() },
+    ComposableDemo("Scrollable in dialog") { InDialogDemo() },
+    ComposableDemo("Nested AndroidView") { AndroidViewDemo() },
+)
+
+@Composable
+private fun SingleEagerListDemo() {
+    var fullWidth by remember { mutableStateOf(false) }
+    var fullHeight by remember { mutableStateOf(false) }
+
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        FeatureFlagToggle()
+
+        Text(
+            "This is some scrollable content. When a screenshot is taken, it should let you " +
+                "capture the entire content, not just the part currently visible.",
+            style = MaterialTheme.typography.caption
+        )
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.SpaceBetween
+        ) {
+            Text("Full-width")
+            Switch(fullWidth, onCheckedChange = { fullWidth = it })
+        }
+        Row(
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.SpaceBetween
+        ) {
+            Text("Full-height")
+            Switch(fullHeight, onCheckedChange = { fullHeight = it })
+        }
+        Divider()
+
+        Column(
+            Modifier
+                .border(1.dp, Color.Black)
+                .fillMaxWidth(fraction = if (fullWidth) 1f else 0.75f)
+                .fillMaxHeight(fraction = if (fullHeight) 1f else 0.75f)
+                .verticalScroll(rememberScrollState())
+        ) {
+            repeat(50) { index ->
+                Button(
+                    onClick = {},
+                    Modifier
+                        .padding(8.dp)
+                        .fillMaxWidth()
+                ) {
+                    Text("Button $index")
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun SingleLazyListDemo() {
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        FeatureFlagToggle()
+
+        Text(
+            "This is some scrollable content. When a screenshot is taken, it should let you " +
+                "capture the entire content, not just the part currently visible.",
+            style = MaterialTheme.typography.caption
+        )
+
+        LazyColumn(
+            Modifier
+                .border(1.dp, Color.Black)
+                .fillMaxWidth(fraction = 0.75f)
+                .height(200.dp)
+        ) {
+            items(50) { index ->
+                Button(
+                    onClick = {},
+                    Modifier
+                        .padding(8.dp)
+                        .fillMaxWidth()
+                ) {
+                    Text("Button $index")
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun SingleFullScreenListDemo() {
+    Column {
+        FeatureFlagToggle()
+
+        LazyColumn(Modifier.fillMaxSize()) {
+            items(50) { index ->
+                Button(
+                    onClick = {},
+                    Modifier
+                        .padding(8.dp)
+                        .fillMaxWidth()
+                ) {
+                    Text("Button $index")
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun LazyListContentPaddingDemo() {
+    Column {
+        FeatureFlagToggle()
+
+        Scaffold(
+            modifier = Modifier
+                .padding(8.dp)
+                .border(1.dp, Color.Black),
+            topBar = {
+                TopAppBar(
+                    title = { Text("Top bar") },
+                    backgroundColor = MaterialTheme.colors.primarySurface.copy(alpha = 0.5f)
+                )
+            },
+            bottomBar = {
+                BottomAppBar(
+                    backgroundColor = MaterialTheme.colors.primarySurface.copy(alpha = 0.5f)
+                ) { Text("Bottom bar") }
+            }
+        ) { contentPadding ->
+            LazyColumn(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(Color.Red),
+                contentPadding = contentPadding
+            ) {
+                items(15) { index ->
+                    Button(
+                        onClick = {},
+                        Modifier
+                            .background(Color.LightGray)
+                            .padding(8.dp)
+                            .fillMaxWidth()
+                    ) {
+                        Text("Button $index")
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun BigInLittleDemo() {
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        FeatureFlagToggle()
+
+        Text(
+            "This is a small scroll container that has a much larger scroll container inside it. " +
+                "The inner scroll container should be captured.",
+            style = MaterialTheme.typography.caption
+        )
+
+        LazyColumn(
+            Modifier
+                .border(1.dp, Color.Black)
+                .weight(1f)
+                .fillMaxWidth(),
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            items(4) {
+                Text(
+                    "Header $it",
+                    Modifier
+                        .fillMaxWidth()
+                        .background(Color.LightGray)
+                        .padding(16.dp)
+                )
+                Box(
+                    Modifier
+                        .background(Color.Magenta)
+                        .fillParentMaxHeight(0.5f)
+//                        .height(400.dp)
+                        .padding(horizontal = 16.dp)
+                ) {
+                    SingleFullScreenListDemo()
+                }
+                Text(
+                    "Footer $it",
+                    Modifier
+                        .fillMaxWidth()
+                        .background(Color.LightGray)
+                        .padding(16.dp)
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun InDialogDemo() {
+    Column {
+        FeatureFlagToggle()
+
+        // Need a scrolling list in the below screen to check that the scrollable in the dialog is
+        // selected instead.
+        LazyColumn(Modifier.fillMaxSize()) {
+            items(50) { index ->
+                var showDialog by remember { mutableStateOf(false) }
+                Button(
+                    onClick = { showDialog = true },
+                    Modifier
+                        .padding(8.dp)
+                        .fillMaxWidth()
+                ) {
+                    Text("Open dialog ($index)")
+                }
+
+                if (showDialog) {
+                    Dialog(onDismissRequest = { showDialog = false }) {
+                        Box(
+                            Modifier
+                                .fillMaxSize(fraction = 0.5f)
+                                .background(Color.LightGray)
+                        ) {
+                            SingleFullScreenListDemo()
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun AndroidViewDemo() {
+    class DemoAndroidView(context: Context) : LinearLayout(context) {
+        init {
+            orientation = VERTICAL
+            addView(TextView(context).also { it.text = "AndroidView Header" })
+            addView(ScrollView(context).apply {
+                setBackgroundColor(android.graphics.Color.CYAN)
+                addView(LinearLayout(context).apply {
+                    orientation = VERTICAL
+                    repeat(20) {
+                        addView(TextView(context).apply {
+                            setPadding(20, 20, 20, 20)
+                            text = "Item $it"
+                        })
+                    }
+                })
+            }, LayoutParams(MATCH_PARENT, 0, 1f))
+            addView(TextView(context).also { it.text = "AndroidView Footer" })
+        }
+    }
+
+    Column {
+        FeatureFlagToggle()
+
+        LazyColumn(
+            modifier = Modifier.fillMaxSize(),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            items(10) {
+                Text("Compose item", Modifier.padding(16.dp))
+            }
+            item {
+                AndroidView(
+                    factory = ::DemoAndroidView,
+                    modifier = Modifier
+                        .background(Color.Magenta)
+                        .fillParentMaxHeight(0.5f)
+                        .padding(horizontal = 16.dp)
+                )
+            }
+            items(5) {
+                Text("Compose item", Modifier.padding(16.dp))
+            }
+        }
+    }
+}
+
+@Suppress("DEPRECATION")
+@Composable
+private fun FeatureFlagToggle() {
+    Column(Modifier.background(Color.Yellow.copy(alpha = 0.5f))) {
+        Row(
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(8.dp),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.SpaceBetween
+        ) {
+            Column(Modifier.weight(1f)) {
+                Text("Enable long screenshots")
+                Text(
+                    "The long screenshots feature is behind a feature flag while under " +
+                        "development. ",
+                    style = MaterialTheme.typography.caption
+                )
+            }
+            Switch(
+                checked = ComposeFeatureFlag_LongScreenshotsEnabled,
+                onCheckedChange = { ComposeFeatureFlag_LongScreenshotsEnabled = it },
+            )
+        }
+        Divider()
+    }
+}
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/contextmenu/ComposeContextMenu.kt
similarity index 72%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/contextmenu/ComposeContextMenu.kt
index 3e91597..a498dc6 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/contextmenu/ComposeContextMenu.kt
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.compose.foundation.demos.contextmenu
+
+import androidx.compose.integration.demos.common.ComposableDemo
+
+val ContextMenuDemos = listOf(
+    ComposableDemo(title = "Text Context Menus") { TextContextMenusDemo() },
+)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/contextmenu/ComposeTextContextMenu.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/contextmenu/ComposeTextContextMenu.kt
new file mode 100644
index 0000000..6fc8991
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/contextmenu/ComposeTextContextMenu.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
+
+package androidx.compose.foundation.demos.contextmenu
+
+import android.widget.EditText
+import android.widget.TextView
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicSecureTextField
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.input.rememberTextFieldState
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.LocalContentColor
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextFieldDefaults
+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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+
+@Composable
+internal fun TextContextMenusDemo() {
+    Box(
+        modifier = Modifier
+            .fillMaxSize()
+            .verticalScroll(rememberScrollState())
+    ) {
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .padding(start = 32.dp, end = 32.dp, bottom = 32.dp),
+            verticalArrangement = spacedBy(16.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Text("Try opening the context menu in any of the components below.")
+
+            LabeledItem("NOT Selectable Text (No Context Menu)") {
+                MyText("This single text is NOT selectable.")
+            }
+
+            LabeledItem("Selectable Text - Single") {
+                SelectionContainer {
+                    MyText("This single text is selectable.")
+                }
+            }
+
+            LabeledItem("Selectable Text - Multiple") {
+                SelectionContainer {
+                    Column(Modifier.outline()) {
+                        MyText("These four texts")
+                        MyText("in a column")
+                        MyText("are all")
+                        MyText("selectable together.")
+                    }
+                }
+            }
+
+            LabeledItem("BTF1 Fully Enabled") {
+                MyTextFieldOne(initialText = "Basic Text Field One")
+            }
+
+            LabeledItem("BTF1 Read-only") {
+                MyTextFieldOne(initialText = "Basic Text Field One", readOnly = true)
+            }
+
+            LabeledItem("BTF1 Disabled (No Context Menu)") {
+                MyTextFieldOne(initialText = "Basic Text Field One", enabled = false)
+            }
+
+            LabeledItem("BTF1 Password") {
+                MyTextFieldOne(
+                    initialText = "Basic Text Field One",
+                    visualTransformation = PasswordVisualTransformation()
+                )
+            }
+
+            LabeledItem("BTF2 Fully Enabled") {
+                MyTextFieldTwo(initialText = "Basic Text Field Two")
+            }
+
+            LabeledItem("BTF2 Read-only") {
+                MyTextFieldTwo(initialText = "Basic Text Field Two", readOnly = true)
+            }
+
+            LabeledItem("BTF2 Disabled (No Context Menu)") {
+                MyTextFieldTwo(initialText = "Basic Text Field Two", enabled = false)
+            }
+
+            LabeledItem("BTF2 Password") {
+                val tfs = rememberTextFieldState("Basic Text Field Two")
+                val interactionSource = remember { MutableInteractionSource() }
+                BasicSecureTextField(
+                    state = tfs,
+                    textStyle = MaterialTheme.typography.body1
+                        .copy(color = LocalContentColor.current),
+                    interactionSource = interactionSource,
+                    decorator = { innerTextField ->
+                        TextFieldDefaults.OutlinedTextFieldDecorationBox(
+                            value = tfs.text.toString(),
+                            innerTextField = innerTextField,
+                            enabled = true,
+                            singleLine = false,
+                            visualTransformation = VisualTransformation.None,
+                            interactionSource = interactionSource
+                        )
+                    },
+                )
+            }
+
+            val textColor = LocalContentColor.current.toArgb()
+            LabeledItem("Platform TextView - NOT Selectable\n(No Context Menu)") {
+                AndroidView(
+                    modifier = Modifier.outline(),
+                    factory = { ctx ->
+                        TextView(ctx).apply {
+                            text = "Platform NON-selectable text."
+                            setTextColor(textColor)
+                        }
+                    },
+                )
+            }
+
+            LabeledItem("Platform TextView - Selectable") {
+                AndroidView(
+                    modifier = Modifier.outline(),
+                    factory = { ctx ->
+                        TextView(ctx).apply {
+                            text = "Platform selectable text."
+                            setTextColor(textColor)
+                            setTextIsSelectable(true)
+                        }
+                    },
+                )
+            }
+
+            LabeledItem("Platform EditText") {
+                AndroidView(
+                    modifier = Modifier.outline(),
+                    factory = { ctx ->
+                        EditText(ctx).apply {
+                            setText("Platform editable text.")
+                            setTextIsSelectable(true)
+                            setTextColor(textColor)
+                        }
+                    },
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun MyText(text: String) {
+    Text(text = text, modifier = Modifier.outline())
+}
+
+@Composable
+private fun MyTextFieldOne(
+    initialText: String,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    textStyle: TextStyle? = null,
+    visualTransformation: VisualTransformation = VisualTransformation.None,
+) {
+    var tfv by remember { mutableStateOf(TextFieldValue(initialText)) }
+    val interactionSource = remember { MutableInteractionSource() }
+    BasicTextField(
+        value = tfv,
+        onValueChange = { tfv = it },
+        modifier = modifier,
+        enabled = enabled,
+        readOnly = readOnly,
+        textStyle = MaterialTheme.typography.body1
+            .copy(color = LocalContentColor.current)
+            .merge(textStyle),
+        visualTransformation = visualTransformation,
+        interactionSource = interactionSource,
+        decorationBox = { innerTextField ->
+            TextFieldDefaults.OutlinedTextFieldDecorationBox(
+                value = tfv.text,
+                innerTextField = innerTextField,
+                enabled = enabled,
+                singleLine = false,
+                visualTransformation = VisualTransformation.None,
+                interactionSource = interactionSource
+            )
+        },
+    )
+}
+
+@Composable
+private fun MyTextFieldTwo(
+    initialText: String,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    textStyle: TextStyle? = null,
+) {
+    val tfs = rememberTextFieldState(initialText)
+    val interactionSource = remember { MutableInteractionSource() }
+    BasicTextField(
+        state = tfs,
+        modifier = modifier,
+        enabled = enabled,
+        readOnly = readOnly,
+        textStyle = MaterialTheme.typography.body1
+            .copy(color = LocalContentColor.current)
+            .merge(textStyle),
+        interactionSource = interactionSource,
+        decorator = { innerTextField ->
+            TextFieldDefaults.OutlinedTextFieldDecorationBox(
+                value = tfs.text.toString(),
+                innerTextField = innerTextField,
+                enabled = enabled,
+                singleLine = false,
+                visualTransformation = VisualTransformation.None,
+                interactionSource = interactionSource
+            )
+        },
+    )
+}
+
+@Composable
+private fun LabeledItem(label: String, content: @Composable () -> Unit) {
+    Column(Modifier.fillMaxSize(), spacedBy(4.dp)) {
+        Text(label, color = LocalContentColor.current.copy(alpha = 0.5f))
+        content()
+    }
+}
+
+private fun Modifier.outline(color: Color = Color.LightGray): Modifier = this
+    .border(1.dp, color, RoundedCornerShape(4.dp))
+    .padding(2.dp)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerCarrouselDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerCarrouselDemos.kt
index 536b9355..c17b0b2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerCarrouselDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerCarrouselDemos.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation.demos.pager
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.Orientation
@@ -46,7 +45,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
-@OptIn(ExperimentalFoundationApi::class)
 val Carrousel = listOf(
     ComposableDemo("Horizontal") { HorizontalCarrouselDemo() },
     ComposableDemo("Vertical") { VerticalCarrouselDemo() },
@@ -56,7 +54,6 @@
     }
 )
 
-@OptIn(ExperimentalFoundationApi::class)
 val SnapPositionDemos = listOf(
     ComposableDemo("Snap Position - Start") { HorizontalCarrouselDemo(SnapPosition.Start) },
     ComposableDemo("Snap Position - Center") { HorizontalCarrouselDemo(SnapPosition.Center) },
@@ -64,7 +61,6 @@
     ComposableDemo("Snap Position - Custom") { HorizontalCarrouselDemoWithCustomSnapPosition() },
 )
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun HorizontalCarrouselDemoWithCustomSnapPosition() {
     val pagerState = rememberPagerState { PagesCount }
@@ -113,7 +109,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun HorizontalCarrouselDemo(snapPosition: SnapPosition = SnapPosition.Start) {
     val pagerState = rememberPagerState { PagesCount }
@@ -131,7 +126,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun VerticalCarrouselDemo() {
     val pagerState = rememberPagerState { PagesCount }
@@ -148,7 +142,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun HorizontalCustomPageSizeDemo() {
     val pagerState = rememberPagerState { PagesCount }
@@ -166,7 +159,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun HorizontalCustomPageSizeWithCustomMaxScrollDemo() {
     val pagerState = rememberPagerState { PagesCount }
@@ -207,7 +199,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 private val ThreePagesPerViewport = object : PageSize {
     override fun Density.calculateMainAxisPageSize(
         availableSpace: Int,
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerDemos.kt
index f56d4e1..5ba5ced 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerDemos.kt
@@ -32,7 +32,6 @@
 
 package androidx.compose.foundation.demos.pager
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
@@ -71,7 +70,6 @@
     DemoCategory("Snap Position", SnapPositionDemos),
 )
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun VerticalPagerDemo() {
     val pagerState = rememberPagerState { PagesCount }
@@ -83,7 +81,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 internal fun HorizontalPagerDemo() {
     val pagerState = rememberPagerState { PagesCount }
@@ -111,7 +108,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 internal fun PagerControls(modifier: Modifier = Modifier, pagerState: PagerState) {
     val animationScope = rememberCoroutineScope()
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerStateInteractionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerStateInteractionsDemos.kt
index 21e7b58..4fe87a5 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerStateInteractionsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/pager/PagerStateInteractionsDemos.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.foundation.demos.pager
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -43,7 +42,6 @@
     }
 )
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun StateDrivenPage() {
     val pagerState = rememberPagerState { PagesCount }
@@ -59,7 +57,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun StateDrivenPageWithMonitor() {
     val pagerState = rememberPagerState { PagesCount }
@@ -76,7 +73,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun StateMonitoringPager() {
     val pagerState = rememberPagerState { PagesCount }
@@ -91,7 +87,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun PageMonitor(modifier: Modifier, pagerState: PagerState) {
     Column(modifier.fillMaxWidth()) {
@@ -104,7 +99,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun StateMonitoringCustomPageSize() {
     val pagerState = rememberPagerState { PagesCount }
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 006e233..632fe29 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
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.demos.text2.BasicTextFieldOutputTransformationDemos
 import androidx.compose.foundation.demos.text2.BasicTextFieldValueCallbackDemo
 import androidx.compose.foundation.demos.text2.DecorationBoxDemos
+import androidx.compose.foundation.demos.text2.HandwritingDelegationDemo
 import androidx.compose.foundation.demos.text2.KeyboardActionsDemos
 import androidx.compose.foundation.demos.text2.KeyboardOptionsDemos
 import androidx.compose.foundation.demos.text2.NestedReceiveContentDemo
@@ -37,6 +38,7 @@
 import androidx.compose.foundation.samples.BasicTextFieldUndoSample
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.integration.demos.common.DemoCategory
+import androidx.compose.ui.text.samples.AnnotatedStringFromHtml
 
 val TextDemos = DemoCategory(
     "Text",
@@ -156,6 +158,7 @@
             "Text Input (BasicTextFieldv2)",
             listOf(
                 ComposableDemo("Basic text input") { BasicTextFieldDemos() },
+                ComposableDemo("Handwriting delegation") { HandwritingDelegationDemo() },
                 ComposableDemo("Value/callback overload") { BasicTextFieldValueCallbackDemo() },
                 ComposableDemo("Keyboard Options") { KeyboardOptionsDemos() },
                 ComposableDemo("Keyboard Actions") { KeyboardActionsDemos() },
@@ -213,5 +216,6 @@
             )
         ),
         ComposableDemo("Text Pointer Icon") { TextPointerIconDemo() },
+        ComposableDemo("Html") { AnnotatedStringFromHtml() }
     )
 )
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
index 1e28470..cbfdb0f 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
@@ -27,26 +27,23 @@
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.BasicSecureTextField
+import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.foundation.text.input.TextObfuscationMode
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
-import androidx.compose.material.Icon
-import androidx.compose.material.IconToggleButton
 import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Info
-import androidx.compose.material.icons.filled.Warning
+import androidx.compose.material.TextButton
 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.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.dp
 import androidx.core.text.isDigitsOnly
@@ -103,8 +100,10 @@
                 new.revertAllChanges()
             }
         },
-        keyboardType = KeyboardType.NumberPassword,
-        imeAction = ImeAction.Default,
+        keyboardOptions = KeyboardOptions(
+            autoCorrect = false,
+            keyboardType = KeyboardType.NumberPassword
+        ),
         modifier = demoTextFieldModifiers
     )
 }
@@ -114,7 +113,7 @@
 fun PasswordToggleVisibilityDemo() {
     val state = remember { TextFieldState() }
     var visible by remember { mutableStateOf(false) }
-    Row(Modifier.fillMaxWidth()) {
+    Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
         BasicSecureTextField(
             state = state,
             textObfuscationMode = if (visible) {
@@ -128,11 +127,13 @@
                 .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp))
                 .padding(6.dp)
         )
-        IconToggleButton(checked = visible, onCheckedChange = { visible = it }) {
-            if (visible) {
-                Icon(Icons.Default.Warning, "")
-            } else {
-                Icon(Icons.Default.Info, "")
+        if (visible) {
+            TextButton(onClick = { visible = false }) {
+                Text("Hide")
+            }
+        } else {
+            TextButton(onClick = { visible = true }) {
+                Text("Show")
             }
         }
     }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
index 2558c13..b3bcb53 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextFieldOutputTransformationDemos.kt
@@ -79,7 +79,7 @@
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun InsertReplaceDeleteDemo() {
-    val text = remember { TextFieldState("abc def ghi") }
+    val state = remember { TextFieldState("abc def ghi") }
     var prefixEnabled by remember { mutableStateOf(true) }
     var suffixEnabled by remember { mutableStateOf(true) }
     var middleWedge by remember { mutableStateOf(true) }
@@ -136,7 +136,7 @@
         }
         var isFirstFieldFocused by remember { mutableStateOf(false) }
         BasicTextField(
-            state = text,
+            state = state,
             onTextLayout = { textLayoutResultProvider = it },
             modifier = Modifier
                 .alignByBaseline()
@@ -149,7 +149,7 @@
                     // Only draw selection outline when not focused.
                     if (isFirstFieldFocused) return@drawWithContent
                     val textLayoutResult = textLayoutResultProvider() ?: return@drawWithContent
-                    val selection = text.text.selection
+                    val selection = state.selection
                     if (selection.collapsed) {
                         val cursorRect = textLayoutResult.getCursorRect(selection.start)
                         drawLine(
@@ -175,7 +175,7 @@
             modifier = Modifier.alignBy { (it.measuredHeight * 0.75f).toInt() }
         )
         BasicTextField(
-            state = text,
+            state = state,
             modifier = Modifier
                 .alignByBaseline()
                 .weight(0.5f)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
index 6962c67..6a235ec 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/DecorationBoxDemos.kt
@@ -78,14 +78,14 @@
 }
 
 @Composable
-fun OutlinedBasicTextField() {
+fun OutlinedBasicTextField(modifier: Modifier = Modifier) {
     val state = remember { TextFieldState() }
     val cursorColor by TextFieldDefaults
         .outlinedTextFieldColors()
         .cursorColor(isError = false)
     BasicTextField(
         state = state,
-        modifier = Modifier,
+        modifier = modifier,
         textStyle = LocalTextStyle.current.copy(color = LocalContentColor.current),
         cursorBrush = SolidColor(cursorColor),
         decorator = {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/HandwritingDelegationDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/HandwritingDelegationDemo.kt
new file mode 100644
index 0000000..a7b773c
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/HandwritingDelegationDemo.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
+
+package androidx.compose.foundation.demos.text2
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.handwriting.handwritingDelegate
+import androidx.compose.foundation.text.handwriting.handwritingDelegator
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Card
+import androidx.compose.material.ContentAlpha
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+
+@Composable
+fun HandwritingDelegationDemo() {
+    var openDialog by remember { mutableStateOf(false) }
+    val focusRequester = remember { FocusRequester() }
+
+    Column(
+        Modifier
+            .imePadding()
+            .requiredWidth(300.dp)
+            .verticalScroll(rememberScrollState()),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        Text(
+            "This is not an actual text field, but it is a handwriting delegator so you can use " +
+                "a stylus to write here."
+        )
+        Spacer(Modifier.size(16.dp))
+        Text("Fake text field",
+            Modifier
+                .fillMaxWidth()
+                .handwritingDelegator { openDialog = !openDialog }
+                .padding(4.dp)
+                .border(
+                    1.dp,
+                    MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
+                    RoundedCornerShape(4.dp)
+                )
+                .padding(16.dp),
+            color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium))
+    }
+
+    if (openDialog) {
+        Dialog(onDismissRequest = { openDialog = false }) {
+            Card(
+                modifier = Modifier.width(300.dp), shape = RoundedCornerShape(16.dp)
+            ) {
+                Column(
+                    modifier = Modifier.padding(24.dp),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text("This text field is a handwriting delegate.")
+                    Spacer(Modifier.size(16.dp))
+                    OutlinedBasicTextField(
+                        Modifier
+                            .fillMaxWidth()
+                            .focusRequester(focusRequester)
+                            .handwritingDelegate()
+                    )
+                }
+            }
+
+            val windowInfo = LocalWindowInfo.current
+            LaunchedEffect(windowInfo) {
+                snapshotFlow { windowInfo.isWindowFocused }.collect { isWindowFocused ->
+                    if (isWindowFocused) {
+                        focusRequester.requestFocus()
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
index 446e6bc..8f24e1c 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
@@ -100,7 +100,7 @@
             modifier = demoTextFieldModifiers.focusRequester(focusRequester),
             state = state,
             keyboardOptions = KeyboardOptions(
-                shouldShowKeyboardOnFocus = showKeyboardOnFocus
+                showKeyboardOnFocus = showKeyboardOnFocus
             )
         )
         Button(onClick = { focusRequester.requestFocus() }) {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
index d83f3ef..a9f990ca2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/ReceiveContentDemos.kt
@@ -29,9 +29,9 @@
 import androidx.compose.foundation.content.MediaType
 import androidx.compose.foundation.content.ReceiveContentListener
 import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.consumeEach
+import androidx.compose.foundation.content.consume
+import androidx.compose.foundation.content.contentReceiver
 import androidx.compose.foundation.content.hasMediaType
-import androidx.compose.foundation.content.receiveContent
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -111,7 +111,7 @@
             ): TransferableContent? {
                 val newImageUris = mutableListOf<Uri>()
                 return transferableContent
-                    .consumeEach { item ->
+                    .consume { item ->
                         // this happens in the ui thread, try not to load images here.
                         val isImageBitmap = item.uri?.isImageBitmap(context) ?: false
                         if (isImageBitmap) {
@@ -131,10 +131,7 @@
     Column(
         modifier = Modifier
             .fillMaxSize()
-            .receiveContent(
-                hintMediaTypes = setOf(MediaType.Image),
-                receiveContentListener = receiveContentListener
-            )
+            .contentReceiver(receiveContentListener)
             .padding(16.dp)
             .background(
                 color = when {
@@ -204,8 +201,8 @@
         )
         Spacer(Modifier.height(8.dp))
         ReceiveContentShowcase(
-            "Everything Consumer",
-            MediaType.All, {
+            title = "Everything Consumer",
+            onReceive = {
                 // consume everything here
                 null
             },
@@ -215,13 +212,12 @@
             var images by remember { mutableStateOf<List<ImageBitmap>>(emptyList()) }
             ReceiveContentShowcase(
                 title = "Image Consumer",
-                hintMediaType = MediaType.Image,
                 onReceive = { transferableContent ->
                     if (!transferableContent.hasMediaType(MediaType.Image)) {
                         transferableContent
                     } else {
                         var uri: Uri? = null
-                        transferableContent.consumeEach { item ->
+                        transferableContent.consume { item ->
                             // only consume this item if we can read
                             if (item.uri != null && uri == null) {
                                 uri = item.uri
@@ -244,9 +240,9 @@
                     }
                 }
                 ReceiveContentShowcase(
-                    "Text Consumer",
-                    MediaType.Text, {
-                        it.consumeEach { item ->
+                    title = "Text Consumer",
+                    onReceive = {
+                        it.consume { item ->
                             val text = item.coerceToText(context)
                             // only consume if it has text in it.
                             !text.isNullOrBlank() && item.uri == null
@@ -271,7 +267,6 @@
 @Composable
 private fun ReceiveContentShowcase(
     title: String,
-    hintMediaType: MediaType,
     onReceive: (TransferableContent) -> TransferableContent?,
     modifier: Modifier = Modifier,
     onClear: () -> Unit = {},
@@ -279,7 +274,7 @@
 ) {
     val transferableContentState = remember { mutableStateOf<TransferableContent?>(null) }
     val receiveContentState = remember {
-        ReceiveContentState(setOf(hintMediaType)) {
+        ReceiveContentState {
             transferableContentState.value = it
             onReceive(it)
         }
@@ -376,7 +371,6 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 class ReceiveContentState(
-    var hintMediaTypes: Set<MediaType>,
     private val onReceive: (TransferableContent) -> TransferableContent?
 ) {
     internal var hovering by mutableStateOf(false)
@@ -412,7 +406,7 @@
 fun Modifier.dropReceiveContent(
     state: ReceiveContentState
 ) = composed {
-    receiveContent(state.hintMediaTypes, state.listener)
+    contentReceiver(state.listener)
         .background(
             color = if (state.hovering) {
                 MaterialTheme.colors.secondary
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt
index 5885114..fbb1cb0 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateItemPlacementTest.kt
@@ -2322,6 +2322,45 @@
         }
     }
 
+    @Test
+    fun animationWhenTryingToStayInTheStart() {
+        var list by mutableStateOf(listOf(1, 2, 3, 4))
+        val listSize = itemSize * 2.5
+        val listSizeDp = itemSizeDp * 3f
+        rule.setContent {
+            LazyGrid(cells = 1, maxSize = listSizeDp) {
+                items(list, key = { it }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(0, 1, 2, 3)
+            runBlocking {
+                state.scrollToItem(0, 0)
+            }
+        }
+
+        onAnimationFrame { fraction ->
+            val expected = mutableListOf<Pair<Any, Offset>>().apply {
+                add(0 to AxisOffset(0f, 0f))
+                add(1 to AxisOffset(0f, itemSize * fraction))
+                add(2 to AxisOffset(0f, itemSize + itemSize * fraction))
+                val item3Offset = itemSize * 2 + itemSize * fraction
+                if (item3Offset < listSize) {
+                    add(3 to AxisOffset(0f, item3Offset))
+                } else {
+                    rule.onNodeWithTag("4").assertIsNotDisplayed()
+                }
+            }
+            assertPositions(
+                expected = expected.toTypedArray(),
+                fraction = fraction
+            )
+        }
+    }
+
     private fun AxisOffset(crossAxis: Float, mainAxis: Float) =
         if (isVertical) Offset(crossAxis, mainAxis) else Offset(mainAxis, crossAxis)
 
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
index 9350a50..d8c6a8c 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemAppearanceAnimationTest.kt
@@ -30,9 +30,9 @@
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyItemScope
 import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.foundation.lazy.animateItem
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.runtime.Composable
@@ -379,7 +379,7 @@
     }
 
     @Composable
-    private fun Item(
+    private fun LazyItemScope.Item(
         color: Color,
         size: Dp = itemSizeDp,
         crossAxisSize: Dp = crossAxisSizeDp,
@@ -387,7 +387,11 @@
     ) {
         Box(
             Modifier
-                .animateItem(appearanceSpec = animSpec, placementSpec = null)
+                .animateItem(
+                    fadeInSpec = animSpec,
+                    placementSpec = null,
+                    fadeOutSpec = null
+                )
                 .background(color)
                 .requiredHeight(size)
                 .requiredWidth(crossAxisSize)
@@ -400,4 +404,4 @@
 private val AnimSpec = tween<Float>(Duration.toInt(), easing = LinearEasing)
 private val ContainerTag = "container"
 
-private fun Float.isCloseTo(expected: Float) = abs(this - expected) < 0.01f
+internal fun Float.isCloseTo(expected: Float) = abs(this - expected) < 0.01f
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemDisappearanceAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemDisappearanceAnimationTest.kt
new file mode 100644
index 0000000..76fa5a7
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemDisappearanceAnimationTest.kt
@@ -0,0 +1,523 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy.list
+
+import android.os.Build
+import androidx.compose.animation.core.FiniteAnimationSpec
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyItemScope
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.getBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalFoundationApi::class)
+class LazyListItemDisappearanceAnimationTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    // the numbers should be divisible by 8 to avoid the rounding issues as we run 4 or 8 frames
+    // of the animation.
+    private val itemSize: Int = 4
+    private var itemSizeDp: Dp = Dp.Infinity
+    private val crossAxisSize: Int = 2
+    private var crossAxisSizeDp: Dp = Dp.Infinity
+    private val containerSize: Float = itemSize * 2f
+    private var containerSizeDp: Dp = Dp.Infinity
+    private lateinit var state: LazyListState
+
+    @Before
+    fun before() {
+        rule.mainClock.autoAdvance = false
+        with(rule.density) {
+            itemSizeDp = itemSize.toDp()
+            crossAxisSizeDp = crossAxisSize.toDp()
+            containerSizeDp = containerSize.toDp()
+        }
+    }
+
+    @Test
+    fun oneItemRemoved() {
+        var list by mutableStateOf(listOf(Color.Black))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = emptyList()
+        }
+
+        onAnimationFrame { fraction ->
+            assertPixels(mainAxisSize = itemSize) {
+                Color.Black.copy(alpha = 1f - fraction)
+            }
+        }
+    }
+
+    @Test
+    fun threeExistTwoRemoved() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red, Color.Green))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp * 3) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(Color.Black)
+        }
+
+        onAnimationFrame { fraction ->
+            assertPixels(itemSize * 3) { offset ->
+                when (offset) {
+                    in 0 until itemSize -> Color.Black
+                    in itemSize until itemSize * 2 -> Color.Red.copy(alpha = 1f - fraction)
+                    else -> Color.Green.copy(alpha = 1f - fraction)
+                }
+            }
+        }
+    }
+
+    // todo copy with content padding and horizontal
+    @Test
+    fun threeExistTwoRemoved_reverseLayout() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red, Color.Green))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp * 3, reverseLayout = true) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(Color.Black)
+        }
+
+        onAnimationFrame { fraction ->
+            assertPixels(itemSize * 3) { offset ->
+                when (offset) {
+                    in 0 until itemSize -> Color.Green.copy(alpha = 1f - fraction)
+                    in itemSize until itemSize * 2 -> Color.Red.copy(alpha = 1f - fraction)
+                    else -> Color.Black
+                }
+            }
+        }
+    }
+
+    @Test
+    fun oneRemoved_reverseLayout_contentPadding() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red))
+        rule.setContent {
+            LazyList(
+                containerSize = itemSizeDp * 3,
+                reverseLayout = true,
+                contentPadding = PaddingValues(bottom = itemSizeDp)
+            ) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(Color.Black)
+        }
+
+        onAnimationFrame { fraction ->
+            assertPixels(itemSize * 3) { offset ->
+                when (offset) {
+                    in 0 until itemSize -> Color.Red.copy(alpha = 1f - fraction)
+                    in itemSize until itemSize * 2 -> Color.Black
+                    else -> Color.Transparent
+                }
+            }
+        }
+    }
+
+    @Test
+    fun onlyItemWithSpecsIsAnimating() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp * 2) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it, disappearanceSpec = if (it == Color.Red) AnimSpec else null)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = emptyList()
+        }
+
+        onAnimationFrame { fraction ->
+            assertPixels(itemSize * 2) { offset ->
+                when (offset) {
+                    in 0 until itemSize -> Color.Transparent
+                    else -> Color.Red.copy(alpha = 1f - fraction)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun itemRemovedOutsideOfViewportIsNotAnimated() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red, Color.Blue, Color.Green))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp * 2) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            // Blue is removed before Green, both are outside the bounds
+            list = listOf(Color.Black, Color.Red, Color.Green)
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                // scroll 0.5 items so we now see half of Black, Red and half of Green
+                state.scrollBy(itemSize * 0.5f)
+            }
+        }
+
+        onAnimationFrame {
+            assertPixels(itemSize * 2) { offset ->
+                when (offset) {
+                    in 0 until itemSize / 2 -> Color.Black
+                    in itemSize / 2 until itemSize * 3 / 2 -> Color.Red
+                    else -> Color.Green
+                }
+            }
+        }
+    }
+
+    @Test
+    fun itemsBeingRemovedAreAffectingTheContainerSizeForTheDurationOfAnimation() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red))
+        rule.setContent {
+            LazyList(containerSize = null) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .assertHeightIsEqualTo(itemSizeDp * 2)
+
+        rule.runOnUiThread {
+            list = listOf(Color.Black)
+        }
+
+        onAnimationFrame { fraction ->
+            val heightDp = rule.onNodeWithTag(ContainerTag)
+                .getBoundsInRoot().height
+            val heightPx = with(rule.density) { heightDp.roundToPx() }
+            assertWithMessage("Height on fraction=$fraction")
+                .that(heightPx)
+                .isEqualTo(if (fraction < 1f) itemSize * 2 else itemSize)
+
+            if (fraction < 1f) {
+                assertPixels(itemSize * 2) { offset ->
+                    when (offset) {
+                        in 0 until itemSize -> Color.Black
+                        else -> Color.Red.copy(1f - fraction)
+                    }
+                }
+            } else {
+                assertPixels(itemSize) { Color.Black }
+            }
+        }
+    }
+
+    @Test
+    fun itemsBeingRemovedAreAffectingTheContainerSizeForTheDurationOfAnimation_reverseLayout() {
+        var list by mutableStateOf(listOf(Color.Black, Color.Red))
+        rule.setContent {
+            LazyList(containerSize = null, reverseLayout = true) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.onNodeWithTag(ContainerTag)
+            .assertHeightIsEqualTo(itemSizeDp * 2)
+
+        assertPixels(itemSize * 2) { offset ->
+            when (offset) {
+                in 0 until itemSize -> Color.Red
+                else -> Color.Black
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(Color.Black)
+        }
+
+        onAnimationFrame { fraction ->
+            val heightDp = rule.onNodeWithTag(ContainerTag)
+                .getBoundsInRoot().height
+            val heightPx = with(rule.density) { heightDp.roundToPx() }
+            assertWithMessage("Height on fraction=$fraction")
+                .that(heightPx)
+                .isEqualTo(if (fraction < 1f) itemSize * 2 else itemSize)
+
+            if (fraction < 1f) {
+                assertPixels(itemSize * 2) { offset ->
+                    when (offset) {
+                        in 0 until itemSize -> Color.Red.copy(1f - fraction)
+                        else -> Color.Black
+                    }
+                }
+            } else {
+                assertPixels(itemSize) { Color.Black }
+            }
+        }
+    }
+
+    @Test
+    fun reAddItemBeingAnimated_withoutAppearanceAnimation() {
+        var list by mutableStateOf(listOf(Color.Black))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = emptyList()
+        }
+
+        onAnimationFrame { fraction ->
+            if (fraction < 0.5f) {
+                assertPixels(itemSize) { Color.Black.copy(alpha = 1f - fraction) }
+            } else {
+                if (fraction.isCloseTo(0.5f)) {
+                    rule.runOnUiThread {
+                        list = listOf(Color.Black)
+                    }
+                }
+                assertPixels(itemSize) { Color.Black }
+            }
+        }
+    }
+
+    @Test
+    fun reAddItemBeingAnimated_withAppearanceAnimation() {
+        var list by mutableStateOf(listOf(Color.Black))
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it, appearanceSpec = HalfDurationAnimSpec)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = emptyList()
+        }
+
+        onAnimationFrame { fraction ->
+            if (fraction < 0.5f) {
+                assertPixels(itemSize) { Color.Black.copy(alpha = 1f - fraction) }
+            } else {
+                if (fraction.isCloseTo(0.5f)) {
+                    rule.runOnUiThread {
+                        list = listOf(Color.Black)
+                    }
+                }
+                assertPixels(itemSize) {
+                    Color.Black.copy(alpha = fraction)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun removeItemBeingAnimatedForAppearance() {
+        var list by mutableStateOf(emptyList<Color>())
+        rule.setContent {
+            LazyList(containerSize = itemSizeDp) {
+                items(list, key = { it.toArgb() }) {
+                    Item(it, appearanceSpec = AnimSpec, disappearanceSpec = HalfDurationAnimSpec)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(Color.Black)
+        }
+
+        onAnimationFrame { fraction ->
+            if (fraction < 0.5f) {
+                assertPixels(itemSize) { Color.Black.copy(alpha = fraction) }
+            } else {
+                if (fraction.isCloseTo(0.5f)) {
+                    rule.runOnUiThread {
+                        list = emptyList()
+                    }
+                }
+                assertPixels(itemSize) {
+                    Color.Black.copy(alpha = 1f - fraction)
+                }
+            }
+        }
+    }
+
+    private fun assertPixels(
+        mainAxisSize: Int,
+        crossAxisSize: Int = this.crossAxisSize,
+        expectedColorProvider: (offset: Int) -> Color?
+    ) {
+        rule.onNodeWithTag(ContainerTag)
+            .captureToImage()
+            .assertPixels(IntSize(crossAxisSize, mainAxisSize)) {
+                expectedColorProvider(it.y)?.compositeOver(Color.White)
+            }
+    }
+
+    private fun onAnimationFrame(duration: Long = Duration, onFrame: (fraction: Float) -> Unit) {
+        require(duration.mod(FrameDuration) == 0L)
+        rule.waitForIdle()
+        rule.mainClock.advanceTimeByFrame()
+        var expectedTime = rule.mainClock.currentTime
+        for (i in 0..duration step FrameDuration) {
+            val fraction = i / duration.toFloat()
+            onFrame(fraction)
+            if (i < duration) {
+                rule.mainClock.advanceTimeBy(FrameDuration)
+                expectedTime += FrameDuration
+                Truth.assertThat(expectedTime).isEqualTo(rule.mainClock.currentTime)
+            }
+        }
+    }
+
+    @Composable
+    private fun LazyList(
+        containerSize: Dp? = containerSizeDp,
+        startIndex: Int = 0,
+        crossAxisSize: Dp = crossAxisSizeDp,
+        reverseLayout: Boolean = false,
+        contentPadding: PaddingValues = PaddingValues(0.dp),
+        content: LazyListScope.() -> Unit
+    ) {
+        state = rememberLazyListState(startIndex)
+
+        LazyColumn(
+            state = state,
+            modifier = Modifier
+                .then(
+                    if (containerSize != null) {
+                        Modifier.requiredHeight(containerSize)
+                    } else {
+                        Modifier
+                    }
+                )
+                .background(Color.White)
+                .then(
+                    if (crossAxisSize != Dp.Unspecified) {
+                        Modifier.requiredWidth(crossAxisSize)
+                    } else {
+                        Modifier.fillMaxWidth()
+                    }
+                )
+                .testTag(ContainerTag),
+            contentPadding = contentPadding,
+            reverseLayout = reverseLayout,
+            content = content
+        )
+    }
+
+    @Composable
+    private fun LazyItemScope.Item(
+        color: Color,
+        size: Dp = itemSizeDp,
+        crossAxisSize: Dp = crossAxisSizeDp,
+        disappearanceSpec: FiniteAnimationSpec<Float>? = AnimSpec,
+        appearanceSpec: FiniteAnimationSpec<Float>? = null
+    ) {
+        Box(
+            Modifier
+                .animateItem(
+                    fadeInSpec = appearanceSpec,
+                    placementSpec = null,
+                    fadeOutSpec = disappearanceSpec
+                )
+                .background(color)
+                .requiredHeight(size)
+                .requiredWidth(crossAxisSize)
+        )
+    }
+}
+
+private val FrameDuration = 16L
+private val Duration = 64L // 4 frames, so we get 0f, 0.25f, 0.5f, 0.75f and 1f fractions
+private val AnimSpec = tween<Float>(Duration.toInt(), easing = LinearEasing)
+private val HalfDurationAnimSpec = tween<Float>(Duration.toInt() / 2, easing = LinearEasing)
+private val ContainerTag = "container"
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
index 6946c2c..f9a3ca6 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListItemPlacementAnimationTest.kt
@@ -1786,6 +1786,45 @@
         }
     }
 
+    @Test
+    fun animationWhenTryingToStayInTheStart() {
+        var list by mutableStateOf(listOf(1, 2, 3, 4))
+        val listSize = itemSize * 2.5
+        val listSizeDp = itemSizeDp * 3f
+        rule.setContent {
+            LazyList(maxSize = listSizeDp) {
+                items(list, key = { it }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(0, 1, 2, 3)
+            runBlocking {
+                state.scrollToItem(0, 0)
+            }
+        }
+
+        onAnimationFrame { fraction ->
+            val expected = mutableListOf<Pair<Any, Float>>().apply {
+                add(0 to 0f)
+                add(1 to itemSize * fraction)
+                add(2 to itemSize + itemSize * fraction)
+                val item3Offset = itemSize * 2 + itemSize * fraction
+                if (item3Offset < listSize) {
+                    add(3 to item3Offset)
+                } else {
+                    rule.onNodeWithTag("4").assertIsNotDisplayed()
+                }
+            }
+            assertPositions(
+                expected = expected.toTypedArray(),
+                fraction = fraction
+            )
+        }
+    }
+
     private val interruptionSpec = spring<IntOffset>(stiffness = Spring.StiffnessMediumLow)
 
     @Test
@@ -2011,7 +2050,11 @@
     ) {
         Box(
             if (animSpec != null) {
-                Modifier.animateItemPlacement(animSpec)
+                Modifier.animateItem(
+                    fadeInSpec = null,
+                    fadeOutSpec = null,
+                    placementSpec = animSpec
+                )
             } else {
                 Modifier
             }
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index 4d048df..c7498dc 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -2575,7 +2575,11 @@
                 items(list, key = { it }) { item ->
                     Box(
                         Modifier
-                            .animateItemPlacement(tween(160))
+                            .animateItem(
+                                fadeInSpec = null,
+                                fadeOutSpec = null,
+                                placementSpec = tween<IntOffset>(160)
+                            )
                             .trackPositions(
                                 lookaheadPosition,
                                 postLookaheadPosition,
@@ -2644,7 +2648,11 @@
                         items(4, key = { it }) {
                             Box(
                                 Modifier
-                                    .animateItemPlacement(tween(160, easing = LinearEasing))
+                                    .animateItem(
+                                        fadeInSpec = null,
+                                        fadeOutSpec = null,
+                                        placementSpec = tween<IntOffset>(160, easing = LinearEasing)
+                                    )
                                     .trackPositions(
                                         lookaheadPosition,
                                         postLookaheadPosition,
@@ -2827,7 +2835,14 @@
                             if (vertical) {
                                 Column(
                                     Modifier
-                                        .animateItemPlacement(tween(160, easing = LinearEasing))
+                                        .animateItem(
+                                            fadeInSpec = null,
+                                            fadeOutSpec = null,
+                                            placementSpec = tween<IntOffset>(
+                                                160,
+                                                easing = LinearEasing
+                                            )
+                                        )
                                         .trackPositions(
                                             lookaheadPosition,
                                             postLookaheadPosition,
@@ -2843,7 +2858,14 @@
                             } else {
                                 Row(
                                     Modifier
-                                        .animateItemPlacement(tween(160, easing = LinearEasing))
+                                        .animateItem(
+                                            fadeInSpec = null,
+                                            fadeOutSpec = null,
+                                            placementSpec = tween<IntOffset>(
+                                                160,
+                                                easing = LinearEasing
+                                            )
+                                        )
                                         .trackPositions(
                                             lookaheadPosition,
                                             postLookaheadPosition,
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
index 202b94e..9f2b765 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateItemPlacementTest.kt
@@ -2089,6 +2089,45 @@
         }
     }
 
+    @Test
+    fun animationWhenTryingToStayInTheStart() {
+        var list by mutableStateOf(listOf(1, 2, 3, 4))
+        val listSize = itemSize * 2.5
+        val listSizeDp = itemSizeDp * 3f
+        rule.setContent {
+            LazyStaggeredGrid(cells = 1, maxSize = listSizeDp) {
+                items(list, key = { it }) {
+                    Item(it)
+                }
+            }
+        }
+
+        rule.runOnUiThread {
+            list = listOf(0, 1, 2, 3)
+            runBlocking {
+                state.scrollToItem(0, 0)
+            }
+        }
+
+        onAnimationFrame { fraction ->
+            val expected = mutableListOf<Pair<Any, Offset>>().apply {
+                add(0 to AxisOffset(0f, 0f))
+                add(1 to AxisOffset(0f, itemSize * fraction))
+                add(2 to AxisOffset(0f, itemSize + itemSize * fraction))
+                val item3Offset = itemSize * 2 + itemSize * fraction
+                if (item3Offset < listSize) {
+                    add(3 to AxisOffset(0f, item3Offset))
+                } else {
+                    rule.onNodeWithTag("4").assertIsNotDisplayed()
+                }
+            }
+            assertPositions(
+                expected = expected.toTypedArray(),
+                fraction = fraction
+            )
+        }
+    }
+
     private fun AxisOffset(crossAxis: Float, mainAxis: Float) =
         if (isVertical) Offset(crossAxis, mainAxis) else Offset(mainAxis, crossAxis)
 
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
index baed998..4287125 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldSamples.kt
@@ -44,12 +44,11 @@
 import androidx.compose.foundation.text.input.delete
 import androidx.compose.foundation.text.input.forEachChange
 import androidx.compose.foundation.text.input.forEachChangeReversed
-import androidx.compose.foundation.text.input.forEachTextValue
 import androidx.compose.foundation.text.input.insert
 import androidx.compose.foundation.text.input.rememberTextFieldState
 import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
-import androidx.compose.foundation.text.input.textAsFlow
 import androidx.compose.foundation.text.input.then
+import androidx.compose.foundation.text.input.valueAsFlow
 import androidx.compose.material.Icon
 import androidx.compose.material.IconButton
 import androidx.compose.material.Text
@@ -224,7 +223,7 @@
 
         /** Called while the view model is active, e.g. from a LaunchedEffect. */
         suspend fun run() {
-            searchFieldState.forEachTextValue { queryText ->
+            searchFieldState.valueAsFlow().collectLatest { queryText ->
                 // Start a new search every time the user types something valid. If the previous
                 // search is still being processed when the text is changed, it will be cancelled
                 // and this code will run again with the latest query text.
@@ -314,6 +313,7 @@
 @Sampled
 @Composable
 fun BasicTextFieldCustomInputTransformationSample() {
+    // Demonstrates how to create a custom and relatively complex InputTransformation.
     val state = remember { TextFieldState() }
     BasicTextField(state, inputTransformation = { _, new ->
         // A filter that always places newly-input text at the start of the string, after a
@@ -426,41 +426,6 @@
     })
 }
 
-@Sampled
-fun BasicTextFieldForEachTextValueSample() {
-    class SearchViewModel {
-        val searchFieldState = TextFieldState()
-        var searchResults: List<String> by mutableStateOf(emptyList())
-            private set
-
-        /** Called while the view model is active, e.g. from a LaunchedEffect. */
-        suspend fun run() {
-            searchFieldState.forEachTextValue { queryText ->
-                // Start a new search every time the user types something. If the previous search
-                // is still being processed when the text is changed, it will be cancelled and this
-                // code will run again with the latest query text.
-                searchResults = performSearch(query = queryText)
-            }
-        }
-
-        private suspend fun performSearch(query: CharSequence): List<String> {
-            TODO()
-        }
-    }
-
-    @Composable
-    fun SearchScreen(viewModel: SearchViewModel) {
-        Column {
-            BasicTextField(viewModel.searchFieldState)
-            LazyColumn {
-                items(viewModel.searchResults) {
-                    TODO()
-                }
-            }
-        }
-    }
-}
-
 @OptIn(FlowPreview::class)
 @Suppress("RedundantSuspendModifier")
 @Sampled
@@ -472,7 +437,7 @@
 
         /** Called while the view model is active, e.g. from a LaunchedEffect. */
         suspend fun run() {
-            searchFieldState.textAsFlow()
+            searchFieldState.valueAsFlow()
                 // Let fast typers get multiple keystrokes in before kicking off a search.
                 .debounce(500)
                 // collectLatest cancels the previous search if it's still running when there's a
@@ -542,8 +507,8 @@
 
 @Sampled
 @Composable
-@OptIn(ExperimentalFoundationApi::class)
 fun BasicTextFieldDecoratorSample() {
+    // Demonstrates how to use the decorator API on BasicTextField
     val state = rememberTextFieldState("Hello, World!")
     BasicTextField(
         state = state,
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt
index 14a7720..ae20db1 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/BasicTextFieldValueSample.kt
@@ -19,7 +19,6 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text.input.TextFieldCharSequence
 import androidx.compose.foundation.text.input.TextFieldState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -40,13 +39,14 @@
 @Composable
 fun BasicTextFieldWithValueOnValueChangeSample() {
     var text by remember { mutableStateOf("") }
+    // A reference implementation that demonstrates how to create a TextField with the legacy
+    // state hoisting design around `BasicTextField(TextFieldState)`
     StringTextField(
         value = text,
         onValueChange = { text = it }
     )
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun StringTextField(
     value: String,
@@ -198,20 +198,19 @@
     }
 
     private fun observeTextState(fireOnValueChanged: Boolean = true) {
-        lateinit var text: TextFieldCharSequence
+        lateinit var value: TextFieldValue
         observeReads {
-            text = state.text
+            value = TextFieldValue(
+                state.text.toString(),
+                state.selection,
+                state.composition
+            )
         }
 
         // This code is outside of the observeReads lambda so we don't observe any state reads the
         // callback happens to do.
         if (fireOnValueChanged) {
-            val newValue = TextFieldValue(
-                text = text.toString(),
-                selection = text.selection,
-                composition = text.composition
-            )
-            onValueChanged(newValue)
+            onValueChanged(value)
         }
     }
 }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 7205812..3b003f7 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
@@ -41,7 +42,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.flow.collect
 
 @Sampled
 @Composable
@@ -106,19 +106,21 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
-fun ItemPlacementAnimationSample() {
+fun AnimateItemSample() {
     var list by remember { mutableStateOf(listOf("A", "B", "C")) }
-    LazyColumn {
-        item {
-            Button(onClick = { list = list.shuffled() }) {
-                Text("Shuffle")
-            }
+    Column {
+        Button(onClick = { list = list + "D" }) {
+            Text("Add new item")
         }
-        items(list, key = { it }) {
-            Text("Item $it", Modifier.animateItemPlacement())
+        Button(onClick = { list = list.shuffled() }) {
+            Text("Shuffle")
+        }
+        LazyColumn {
+            items(list, key = { it }) {
+                Text("Item $it", Modifier.animateItem())
+            }
         }
     }
 }
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
index b845444..58dadbf 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
@@ -60,7 +60,6 @@
 import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun SimpleHorizontalPagerSample() {
@@ -83,7 +82,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun SimpleVerticalPagerSample() {
@@ -106,7 +104,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun PagerWithStateSample() {
@@ -189,7 +186,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun CustomPageSizeSample() {
@@ -227,7 +223,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun ObservingStateChangesInPagerStateSample() {
@@ -260,7 +255,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun AnimateScrollPageSample() {
@@ -299,7 +293,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun ScrollToPageSample() {
@@ -334,7 +327,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun HorizontalPagerWithScrollableContent() {
@@ -403,7 +395,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun UsingPagerLayoutInfoForSideEffectSample() {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
index 8b01a4e..8027c60 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ReceiveContentSamples.kt
@@ -24,9 +24,9 @@
 import androidx.compose.foundation.content.MediaType
 import androidx.compose.foundation.content.ReceiveContentListener
 import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.consumeEach
+import androidx.compose.foundation.content.consume
+import androidx.compose.foundation.content.contentReceiver
 import androidx.compose.foundation.content.hasMediaType
-import androidx.compose.foundation.content.receiveContent
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.text.BasicTextField
@@ -55,12 +55,12 @@
         }
         BasicTextField(
             state = state,
-            modifier = Modifier.receiveContent(setOf(MediaType.Image)) { transferableContent ->
+            modifier = Modifier.contentReceiver { transferableContent ->
                 if (!transferableContent.hasMediaType(MediaType.Image)) {
-                    return@receiveContent transferableContent
+                    return@contentReceiver transferableContent
                 }
                 val newImages = mutableListOf<ImageBitmap>()
-                transferableContent.consumeEach { item ->
+                transferableContent.consume { item ->
                     // only consume this item if we can read an imageBitmap
                     item.readImageBitmap()?.let { newImages += it; true } ?: false
                 }.also {
@@ -95,8 +95,7 @@
                         else -> MaterialTheme.colors.background
                     }
                 )
-                .receiveContent(
-                    hintMediaTypes = setOf(MediaType.Image),
+                .contentReceiver(
                     receiveContentListener = object : ReceiveContentListener {
                         override fun onDragStart() {
                             dragging = true
@@ -123,7 +122,7 @@
                             }
                             val newImages = mutableListOf<ImageBitmap>()
                             return transferableContent
-                                .consumeEach { item ->
+                                .consume { item ->
                                     // only consume this item if we can read an imageBitmap
                                     item
                                         .readImageBitmap()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/IndicationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/IndicationTest.kt
index cba8964..a75ac01 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/IndicationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/IndicationTest.kt
@@ -121,23 +121,26 @@
             Box(Modifier.testTag(testTag).size(100.dp).indication(interactionSource, indication))
         }
 
-        rule.runOnIdle {
-            assertThat(drawCalls).isEqualTo(1)
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 1 }
+
+        rule.runOnUiThread {
             runBlocking {
                 interactionSource.emit(press)
             }
         }
 
-        rule.runOnIdle {
-            assertThat(drawCalls).isEqualTo(2)
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 2 }
+
+        rule.runOnUiThread {
             runBlocking {
                 interactionSource.emit(release)
             }
         }
 
-        rule.runOnIdle {
-            assertThat(drawCalls).isEqualTo(3)
-        }
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 3 }
     }
 
     @Test
@@ -160,23 +163,26 @@
             Box(Modifier.testTag(testTag).size(100.dp).indication(interactionSource, indication))
         }
 
-        rule.runOnIdle {
-            assertThat(drawCalls).isEqualTo(1)
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 1 }
+
+        rule.runOnUiThread {
             runBlocking {
                 interactionSource.emit(press)
             }
         }
 
-        rule.runOnIdle {
-            assertThat(drawCalls).isEqualTo(2)
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 2 }
+
+        rule.runOnUiThread {
             runBlocking {
                 interactionSource.emit(release)
             }
         }
 
-        rule.runOnIdle {
-            assertThat(drawCalls).isEqualTo(3)
-        }
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 3 }
     }
 
     @Test
@@ -229,6 +235,9 @@
             )
         }
 
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 1 }
+
         rule.runOnIdle {
             assertThat(createCalls).isEqualTo(1)
             assertThat(drawCalls).isEqualTo(1)
@@ -236,6 +245,9 @@
             interactionSource = MutableInteractionSource()
         }
 
+        // Due to b/302303969 there are no guarantees runOnIdle() will wait for drawing to happen
+        rule.waitUntil { drawCalls == 2 }
+
         rule.runOnIdle {
             // New instance should be created
             assertThat(createCalls).isEqualTo(2)
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollTest.kt
index a828f4b..3c4ec9c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollTest.kt
@@ -47,6 +47,7 @@
 import androidx.compose.testutils.assertPixels
 import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
@@ -102,6 +103,7 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.launch
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -522,6 +524,90 @@
     }
 
     @Test
+    fun scroller_semanticsScrollByOffset_isAnimated() {
+        rule.mainClock.autoAdvance = false
+        val scrollState = ScrollState(initial = 0)
+
+        createScrollableContent(scrollState = scrollState)
+
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(scrollState.maxValue).isGreaterThan(100) // If this fails, just add more items
+
+        val action = rule.onNodeWithTag(scrollerTag)
+            .fetchSemanticsNode().config[SemanticsActions.ScrollByOffset]
+        scope.launch(start = CoroutineStart.UNDISPATCHED) {
+            when (config.orientation) {
+                Vertical -> action(Offset(0f, 100f))
+                Horizontal -> action(Offset(100f, 0f))
+            }
+        }
+
+        // We haven't advanced time yet, make sure it's still zero
+        assertThat(scrollState.value).isEqualTo(0)
+
+        // Advance and make sure we're partway through
+        // Note that we need two frames for the animation to actually happen
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(scrollState.value).isGreaterThan(0)
+        assertThat(scrollState.value).isLessThan(100)
+
+        // Finish the scroll, make sure we're at the target
+        rule.mainClock.advanceTimeBy(5000)
+        assertThat(scrollState.value).isEqualTo(100)
+    }
+
+    @Test
+    fun scroller_semanticsScrollByOffset_returnsConsumedScroll() {
+        val scrollState = ScrollState(initial = 0)
+        var consumedScroll = Offset.Unspecified
+
+        createScrollableContent(scrollState = scrollState)
+
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(scrollState.maxValue).isGreaterThan(100) // If this fails, just add more items
+
+        val action = rule.onNodeWithTag(scrollerTag).fetchSemanticsNode()
+            .config[SemanticsActions.ScrollByOffset]
+
+        scope.launch {
+            consumedScroll = when (config.orientation) {
+                Vertical -> action(Offset(0f, 100f))
+                Horizontal -> action(Offset(100f, 0f))
+            }
+        }
+        rule.runOnIdle {
+            assertThat(consumedScroll).isEqualTo(
+                when (config.orientation) {
+                    Vertical -> Offset(0f, 100f)
+                    Horizontal -> Offset(100f, 0f)
+                }
+            )
+        }
+
+        // Try to scroll again, only consume part.
+        val expectedConsumed = scrollState.maxValue - scrollState.value
+        val impossibleScrollRequest = scrollState.maxValue + 10f
+        // b/330698760
+        scope.launch(DisableAnimationMotionDurationScale) {
+            consumedScroll = when (config.orientation) {
+                Vertical -> action(Offset(0f, impossibleScrollRequest))
+                Horizontal -> action(Offset(impossibleScrollRequest, 0f))
+            }
+        }
+        rule.runOnIdle {
+            assertThat(consumedScroll).isEqualTo(
+                when (config.orientation) {
+                    Vertical -> Offset(0f, expectedConsumed.toFloat())
+                    Horizontal -> Offset(expectedConsumed.toFloat(), 0f)
+                }
+            )
+        }
+    }
+
+    @Test
     fun scroller_touchInputEnabled_shouldHaveSemanticsInfo() {
         val scrollState = ScrollState(initial = 0)
         val scrollNode = rule.onNodeWithTag(scrollerTag)
@@ -841,9 +927,11 @@
             }
         }
         rule.setContent {
-            Box(Modifier.intrinsicMainAxisSize(IntrinsicSize.Min)
-                .scrollerWithOrientation()
-                .then(layoutModifier)
+            Box(
+                Modifier
+                    .intrinsicMainAxisSize(IntrinsicSize.Min)
+                    .scrollerWithOrientation()
+                    .then(layoutModifier)
             )
         }
         rule.waitForIdle()
@@ -882,9 +970,11 @@
             }
         }
         rule.setContent {
-            Box(Modifier.intrinsicCrossAxisSize(IntrinsicSize.Min)
-                .scrollerWithOrientation()
-                .then(layoutModifier)
+            Box(
+                Modifier
+                    .intrinsicCrossAxisSize(IntrinsicSize.Min)
+                    .scrollerWithOrientation()
+                    .then(layoutModifier)
             )
         }
         rule.waitForIdle()
@@ -923,9 +1013,11 @@
             }
         }
         rule.setContent {
-            Box(Modifier.intrinsicMainAxisSize(IntrinsicSize.Max)
-                .scrollerWithOrientation()
-                .then(layoutModifier)
+            Box(
+                Modifier
+                    .intrinsicMainAxisSize(IntrinsicSize.Max)
+                    .scrollerWithOrientation()
+                    .then(layoutModifier)
             )
         }
         rule.waitForIdle()
@@ -964,9 +1056,11 @@
             }
         }
         rule.setContent {
-            Box(Modifier.intrinsicCrossAxisSize(IntrinsicSize.Max)
-                .scrollerWithOrientation()
-                .then(layoutModifier)
+            Box(
+                Modifier
+                    .intrinsicCrossAxisSize(IntrinsicSize.Max)
+                    .scrollerWithOrientation()
+                    .then(layoutModifier)
             )
         }
         rule.waitForIdle()
@@ -1092,9 +1186,12 @@
 
         val content: @Composable () -> Unit = {
             repeat(25) {
-                Box(modifier = Modifier.size(100.dp)
-                    .padding(2.dp)
-                    .background(Color.Red))
+                Box(
+                    modifier = Modifier
+                        .size(100.dp)
+                        .padding(2.dp)
+                        .background(Color.Red)
+                )
             }
         }
 
@@ -1449,4 +1546,9 @@
             onRemeasure.invoke()
         }
     }
+
+    private object DisableAnimationMotionDurationScale : MotionDurationScale {
+        override val scaleFactor: Float
+            get() = 0f
+    }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 2d63199..523307e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -22,7 +22,6 @@
 import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.gestures.DefaultFlingBehavior
 import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ModifierLocalScrollableContainer
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableDefaults
@@ -80,8 +79,8 @@
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.VelocityTrackerAddPointsFix
 import androidx.compose.ui.materialize
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalReadScope
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalFocusManager
@@ -2068,15 +2067,8 @@
                         .testTag(scrollableBoxTag)
                         .size(100.dp)
                         .then(
-                            object : ModifierLocalConsumer {
-                                override fun onModifierLocalsUpdated(
-                                    scope: ModifierLocalReadScope
-                                ) {
-                                    with(scope) {
-                                        isOuterInScrollableContainer =
-                                            ModifierLocalScrollableContainer.current
-                                    }
-                                }
+                            ScrollableContainerReaderNodeElement {
+                                isOuterInScrollableContainer = it
                             }
                         )
                         .scrollable(
@@ -2084,15 +2076,8 @@
                             orientation = Orientation.Horizontal
                         )
                         .then(
-                            object : ModifierLocalConsumer {
-                                override fun onModifierLocalsUpdated(
-                                    scope: ModifierLocalReadScope
-                                ) {
-                                    with(scope) {
-                                        isInnerInScrollableContainer =
-                                            ModifierLocalScrollableContainer.current
-                                    }
-                                }
+                            ScrollableContainerReaderNodeElement {
+                                isInnerInScrollableContainer = it
                             }
                         )
                 )
@@ -2106,6 +2091,82 @@
     }
 
     @Test
+    fun scrollable_setsModifierLocalScrollableContainer_scrollDisabled() {
+        val controller = ScrollableState { it }
+
+        var isOuterInScrollableContainer: Boolean? = null
+        var isInnerInScrollableContainer: Boolean? = null
+        rule.setContent {
+            Box {
+                Box(
+                    modifier = Modifier
+                        .testTag(scrollableBoxTag)
+                        .size(100.dp)
+                        .then(
+                            ScrollableContainerReaderNodeElement {
+                                isOuterInScrollableContainer = it
+                            }
+                        )
+                        .scrollable(
+                            state = controller,
+                            orientation = Orientation.Horizontal,
+                            enabled = false
+                        )
+                        .then(
+                            ScrollableContainerReaderNodeElement {
+                                isInnerInScrollableContainer = it
+                            }
+                        )
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(isOuterInScrollableContainer).isFalse()
+            assertThat(isInnerInScrollableContainer).isFalse()
+        }
+    }
+
+    @Test
+    fun scrollable_setsModifierLocalScrollableContainer_scrollUpdates() {
+        val controller = ScrollableState { it }
+
+        var isInnerInScrollableContainer: Boolean? = null
+        val enabled = mutableStateOf(true)
+        rule.setContent {
+            Box {
+                Box(
+                    modifier = Modifier
+                        .testTag(scrollableBoxTag)
+                        .size(100.dp)
+                        .scrollable(
+                            state = controller,
+                            orientation = Orientation.Horizontal,
+                            enabled = enabled.value
+                        )
+                        .then(
+                            ScrollableContainerReaderNodeElement {
+                                isInnerInScrollableContainer = it
+                            }
+                        )
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(isInnerInScrollableContainer).isTrue()
+        }
+
+        rule.runOnIdle {
+            enabled.value = false
+        }
+
+        rule.runOnIdle {
+            assertThat(isInnerInScrollableContainer).isFalse()
+        }
+    }
+
+    @Test
     fun scrollable_scrollByWorksWithRepeatableAnimations() {
         rule.mainClock.autoAdvance = false
 
@@ -2455,9 +2516,11 @@
                 item {
                     LazyRow(Modifier.testTag("list")) {
                         items(100) {
-                            Box(modifier = Modifier
-                                .size(20.dp)
-                                .background(Color.Blue))
+                            Box(
+                                modifier = Modifier
+                                    .size(20.dp)
+                                    .background(Color.Blue)
+                            )
                         }
                     }
                 }
@@ -2585,7 +2648,7 @@
                 "reverseDirection",
                 "flingBehavior",
                 "interactionSource",
-                "scrollableBringIntoViewConfig",
+                "bringIntoViewSpec",
             )
         }
     }
@@ -3209,3 +3272,45 @@
 }
 
 internal class TestScrollMotionDurationScale(override val scaleFactor: Float) : MotionDurationScale
+
+private class ScrollableContainerReaderNodeElement(val hasScrollableBlock: (Boolean) -> Unit) :
+    ModifierNodeElement<ScrollableContainerReaderNode>() {
+    override fun create(): ScrollableContainerReaderNode {
+        return ScrollableContainerReaderNode(hasScrollableBlock)
+    }
+
+    override fun update(node: ScrollableContainerReaderNode) {
+        node.hasScrollableBlock = hasScrollableBlock
+        node.onUpdate()
+    }
+
+    override fun hashCode(): Int = hasScrollableBlock.hashCode()
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other === null) return false
+        if (this::class != other::class) return false
+
+        other as ScrollableContainerReaderNodeElement
+
+        if (hasScrollableBlock != other.hasScrollableBlock) return false
+
+        return true
+    }
+}
+
+private class ScrollableContainerReaderNode(var hasScrollableBlock: (Boolean) -> Unit) :
+    Modifier.Node(),
+    TraversableNode {
+    override val traverseKey: Any = TraverseKey
+
+    override fun onAttach() {
+        hasScrollableBlock.invoke(hasScrollableContainer())
+    }
+
+    fun onUpdate() {
+        hasScrollableBlock.invoke(hasScrollableContainer())
+    }
+
+    companion object TraverseKey
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
index a5c32eb..dc9ae1e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableGestureTest.kt
@@ -870,7 +870,7 @@
         val velocityThreshold = 100.dp
         val state = AnchoredDraggableState(
             initialValue = A,
-            velocityThreshold = { 0f },
+            velocityThreshold = { with(rule.density) { velocityThreshold.toPx() } },
             positionalThreshold = { Float.POSITIVE_INFINITY },
             snapAnimationSpec = tween(),
             decayAnimationSpec = DefaultDecayAnimationSpec
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt
index 8cb9c88..5cbdb10 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableOverscrollTest.kt
@@ -37,6 +37,7 @@
 import androidx.compose.foundation.overscroll
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.testutils.WithTouchSlop
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
@@ -293,9 +294,10 @@
         // assert that applyToFling was called but there is no remaining velocity for overscroll
         // because flinging was not towards the min/max anchors
         assertThat(overscrollEffect.applyToFlingCalledCount).isEqualTo(1)
-        assertThat(abs(overscrollEffect.flingOverscrollVelocity.x)).isEqualTo(0f)
+        assertThat(abs(overscrollEffect.flingOverscrollVelocity.x)).isWithin(1f).of(0f)
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun anchoredDraggable_swipeWithVelocity_notEnoughVelocityForOverscroll() {
         val overscrollEffect = TestOverscrollEffect()
@@ -365,7 +367,7 @@
         // assert that applyToFling was called but there is no remaining velocity for overscroll
         // because velocity is small
         assertThat(overscrollEffect.applyToFlingCalledCount).isEqualTo(1)
-        assertThat(abs(overscrollEffect.flingOverscrollVelocity.x)).isEqualTo(0f)
+        assertThat(abs(overscrollEffect.flingOverscrollVelocity.x)).isWithin(1f).of(0f)
     }
 
     private val DefaultPositionalThreshold: (totalDistance: Float) -> Float = {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
index 84885e5..970617f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/anchoredDraggable/AnchoredDraggableStateTest.kt
@@ -40,6 +40,7 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.anchoredDraggable
 import androidx.compose.foundation.gestures.animateTo
+import androidx.compose.foundation.gestures.animateToWithDecay
 import androidx.compose.foundation.gestures.snapTo
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -897,7 +898,7 @@
     }
 
     @Test
-    fun anchoredDraggable_customDrag_snapsToClosestAnchor() = runBlocking {
+    fun anchoredDraggable_customDrag_doesNotSnapToClosestAnchor() = runBlocking {
         val state = AnchoredDraggableState(
             initialValue = A,
             positionalThreshold = defaultPositionalThreshold,
@@ -916,13 +917,13 @@
         }
 
         assertThat(state.currentValue).isEqualTo(B)
-        assertThat(state.requireOffset()).isEqualTo(200f)
+        assertThat(state.requireOffset()).isEqualTo(150f)
 
         state.anchoredDrag {
             dragTo(260f)
         }
         assertThat(state.currentValue).isEqualTo(C)
-        assertThat(state.requireOffset()).isEqualTo(300f)
+        assertThat(state.requireOffset()).isEqualTo(260f)
     }
 
     @Test
@@ -1513,10 +1514,58 @@
             assertThat(state.offset).isEqualTo(positionB)
 
             // since offset == positionB, decay animation is used
-            assertThat(inspectDecayAnimationSpec.animationWasExecutions).isEqualTo(1)
+            assertThat(inspectDecayAnimationSpec.animationWasExecutions).isEqualTo(0)
             assertThat(tweenAnimationSpec.animationWasExecutions).isEqualTo(0)
         }
 
+    @Test
+    fun anchoredDraggable_animateTo_alreadyAtTarget_noOps() {
+        val state = AnchoredDraggableState(
+            initialValue = B,
+            positionalThreshold = defaultPositionalThreshold,
+            velocityThreshold = defaultVelocityThreshold,
+            snapAnimationSpec = defaultAnimationSpec,
+            decayAnimationSpec = defaultDecayAnimationSpec,
+            anchors = DraggableAnchors {
+                A at 0f
+                B at 200f
+                C at 300f
+            }
+        )
+        val clock = HandPumpTestFrameClock()
+        val scope = CoroutineScope(clock)
+
+        assertThat(state.offset).isEqualTo(200f)
+        scope.launch { state.animateTo(B) }
+        runBlocking { clock.advanceByFrame() } // Advance only one frame, we should be done
+        assertThat(state.offset).isEqualTo(200f)
+    }
+
+    @Test
+    fun anchoredDraggable_animateToWithDecay_alreadyAtTarget_noOps() {
+        val state = AnchoredDraggableState(
+            initialValue = B,
+            positionalThreshold = defaultPositionalThreshold,
+            velocityThreshold = defaultVelocityThreshold,
+            snapAnimationSpec = defaultAnimationSpec,
+            decayAnimationSpec = defaultDecayAnimationSpec,
+            anchors = DraggableAnchors {
+                A at 0f
+                B at 200f
+                C at 300f
+            }
+        )
+        val clock = HandPumpTestFrameClock()
+        val scope = CoroutineScope(clock)
+
+        assertThat(state.offset).isEqualTo(200f)
+        scope.launch {
+            state.animateToWithDecay(B, velocity = 100f)
+        }
+        runBlocking { clock.advanceByFrame() } // Advance only one frame, we should be done
+        assertThat(state.offset).isEqualTo(200f)
+    }
+
     private suspend fun suspendIndefinitely() = suspendCancellableCoroutine<Unit> { }
 
     private class HandPumpTestFrameClock : MonotonicFrameClock {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/ReceiveContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/ReceiveContentTest.kt
index 91d6be5..bc469a1 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/ReceiveContentTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/ReceiveContentTest.kt
@@ -58,13 +58,13 @@
 
     @Test
     fun receiveContentConfiguration_isMergedBottomToTop() {
-        var calculatedReceiveContent: ReceiveContentConfiguration? = null
+        var calculatedReceiveContent: ReceiveContentConfiguration?
         val listenerCalls = mutableListOf<Int>()
         rule.setContent {
             Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Video)) { listenerCalls += 3; it }
-                .receiveContent(setOf(MediaType.Audio)) { listenerCalls += 2; it }
-                .receiveContent(setOf(MediaType.Text)) { listenerCalls += 1; it }
+                .contentReceiver { listenerCalls += 3; it }
+                .contentReceiver { listenerCalls += 2; it }
+                .contentReceiver { listenerCalls += 1; it }
                 .then(TestElement {
                     calculatedReceiveContent = it.getReceiveContentConfiguration()
                     calculatedReceiveContent
@@ -75,10 +75,6 @@
         }
 
         rule.runOnIdle {
-            assertThat(calculatedReceiveContent?.hintMediaTypes).isNotNull()
-            assertThat(calculatedReceiveContent?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Audio, MediaType.Text)
-            )
             assertThat(listenerCalls).isEqualTo(listOf(1, 2, 3))
         }
     }
@@ -90,28 +86,25 @@
         var textReceived: TransferableContent? = null
         rule.setContent {
             Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Video)) {
-                    videoReceived = it
-                    val t = it.consumeEach {
+                .contentReceiver { transferable ->
+                    videoReceived = transferable
+                    transferable.consume {
                         it.uri
                             ?.toString()
                             ?.contains("video") ?: false
                     }
-                    t
                 }
-                .receiveContent(setOf(MediaType.Audio)) {
-                    audioReceived = it
-                    val t = it.consumeEach {
+                .contentReceiver { transferable ->
+                    audioReceived = transferable
+                    transferable.consume {
                         it.uri
                             ?.toString()
                             ?.contains("audio") ?: false
                     }
-                    t
                 }
-                .receiveContent(setOf(MediaType.Text)) {
-                    textReceived = it
-                    val t = it.consumeEach { it.text != null }
-                    t
+                .contentReceiver { transferable ->
+                    textReceived = transferable
+                    transferable.consume { it.text != null }
                 }
                 .then(TestElement {
                     it.getReceiveContentConfiguration()
@@ -144,7 +137,6 @@
     @Test
     fun receiveContentConfiguration_returnsNullIfNotDefined() {
         var calculatedReceiveContent: ReceiveContentConfiguration? = ReceiveContentConfiguration(
-            emptySet(),
             ReceiveContentListener { null }
         )
         rule.setContent {
@@ -163,7 +155,6 @@
     @Test
     fun receiveContentConfiguration_returnsNullIfDefined_atSiblingNode() {
         var calculatedReceiveContent: ReceiveContentConfiguration? = ReceiveContentConfiguration(
-            emptySet(),
             ReceiveContentListener { null }
         )
         rule.setContent {
@@ -171,7 +162,7 @@
                 Box(modifier = Modifier.then(TestElement {
                     calculatedReceiveContent = it.getReceiveContentConfiguration()
                 }))
-                Box(modifier = Modifier.receiveContent(setOf(MediaType.Text)) { it })
+                Box(modifier = Modifier.contentReceiver { it })
             }
         }
 
@@ -183,7 +174,6 @@
     @Test
     fun receiveContentConfiguration_returnsNullIfDefined_atChildNode() {
         var calculatedReceiveContent: ReceiveContentConfiguration? = ReceiveContentConfiguration(
-            emptySet(),
             ReceiveContentListener { null }
         )
         rule.setContent {
@@ -192,7 +182,7 @@
                     calculatedReceiveContent = it.getReceiveContentConfiguration()
                 })
             ) {
-                Box(modifier = Modifier.receiveContent(setOf(MediaType.Text)) { it })
+                Box(modifier = Modifier.contentReceiver { it })
             }
         }
 
@@ -202,38 +192,19 @@
     }
 
     @Test
-    fun receiveContentConfiguration_emptyMediaTypeSet_returnsMediaTypeAll() {
-        var calculatedReceiveContent: ReceiveContentConfiguration? = ReceiveContentConfiguration(
-            emptySet(),
-            ReceiveContentListener { null }
-        )
-        rule.setContent {
-            Box(modifier = Modifier.receiveContent(emptySet()) { it }) {
-                Box(modifier = Modifier.then(TestElement {
-                    calculatedReceiveContent = it.getReceiveContentConfiguration()
-                }))
-            }
-        }
-
-        rule.runOnIdle {
-            assertThat(calculatedReceiveContent).isNotNull()
-            assertThat(calculatedReceiveContent?.hintMediaTypes).isEqualTo(setOf(MediaType.All))
-        }
-    }
-
-    @Test
     fun detachedReceiveContent_disappearsFromMergedConfiguration() {
         var getReceiveContentConfiguration: (() -> ReceiveContentConfiguration?)? = null
         var attached by mutableStateOf(true)
+        val called = mutableListOf<Int>()
         rule.setContent {
             Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Video)) { it }
+                .contentReceiver { called += 1; it }
                 .then(if (attached) {
-                    Modifier.receiveContent(setOf(MediaType.Audio)) { it }
+                    Modifier.contentReceiver { called += 2; it }
                 } else {
                     Modifier
                 })
-                .receiveContent(setOf(MediaType.Text)) { it }
+                .contentReceiver { called += 3; it }
                 .then(TestElement {
                     getReceiveContentConfiguration = {
                         it.getReceiveContentConfiguration()
@@ -245,19 +216,22 @@
         rule.runOnIdle {
             val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
             assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Audio, MediaType.Text)
+            receiveContentConfiguration!!.receiveContentListener.onReceive(
+                TransferableContent(createClipData())
             )
+            assertThat(called).isEqualTo(listOf(3, 2, 1))
         }
 
+        called.clear()
         attached = false
 
         rule.runOnIdle {
             val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
             assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Text)
+            receiveContentConfiguration!!.receiveContentListener.onReceive(
+                TransferableContent(createClipData())
             )
+            assertThat(called).isEqualTo(listOf(3, 1))
         }
     }
 
@@ -265,15 +239,17 @@
     fun laterAttachedReceiveContent_appearsInMergedConfiguration() {
         var getReceiveContentConfiguration: (() -> ReceiveContentConfiguration?)? = null
         var attached by mutableStateOf(false)
+        val called = mutableListOf<Int>()
+
         rule.setContent {
             Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Video)) { it }
+                .contentReceiver { called += 1; it }
                 .then(if (attached) {
-                    Modifier.receiveContent(setOf(MediaType.Audio)) { it }
+                    Modifier.contentReceiver { called += 2; it }
                 } else {
                     Modifier
                 })
-                .receiveContent(setOf(MediaType.Text)) { it }
+                .contentReceiver { called += 3; it }
                 .then(TestElement {
                     getReceiveContentConfiguration = {
                         it.getReceiveContentConfiguration()
@@ -285,89 +261,22 @@
         rule.runOnIdle {
             val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
             assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Text)
+            receiveContentConfiguration!!.receiveContentListener.onReceive(
+                TransferableContent(createClipData())
             )
+            assertThat(called).isEqualTo(listOf(3, 1))
         }
 
+        called.clear()
         attached = true
 
         rule.runOnIdle {
             val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
             assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Audio, MediaType.Text)
+            receiveContentConfiguration!!.receiveContentListener.onReceive(
+                TransferableContent(createClipData())
             )
-        }
-    }
-
-    @Test
-    fun changingParentReceiveContent_appearsInMergedConfiguration() {
-        var getReceiveContentConfiguration: (() -> ReceiveContentConfiguration?)? = null
-        var topMediaTypes by mutableStateOf(setOf(MediaType.Video))
-        rule.setContent {
-            Box(modifier = Modifier
-                .receiveContent(topMediaTypes) { it }
-                .receiveContent(setOf(MediaType.Text)) { it }
-                .then(TestElement {
-                    getReceiveContentConfiguration = {
-                        it.getReceiveContentConfiguration()
-                    }
-                })
-            )
-        }
-
-        rule.runOnIdle {
-            val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
-            assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Text)
-            )
-        }
-
-        topMediaTypes = setOf(MediaType.Audio, MediaType.Video)
-
-        rule.runOnIdle {
-            val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
-            assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Audio, MediaType.Text)
-            )
-        }
-    }
-
-    @Test
-    fun changingCurrentReceiveContent_appearsInMergedConfiguration() {
-        var getReceiveContentConfiguration: (() -> ReceiveContentConfiguration?)? = null
-        var currentMediaTypes by mutableStateOf(setOf(MediaType.Video))
-        rule.setContent {
-            Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Image)) { it }
-                .receiveContent(currentMediaTypes) { it }
-                .then(TestElement {
-                    getReceiveContentConfiguration = {
-                        it.getReceiveContentConfiguration()
-                    }
-                })
-            )
-        }
-
-        rule.runOnIdle {
-            val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
-            assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Image)
-            )
-        }
-
-        currentMediaTypes = setOf(MediaType.Text, MediaType.Video)
-
-        rule.runOnIdle {
-            val receiveContentConfiguration = getReceiveContentConfiguration?.invoke()
-            assertThat(receiveContentConfiguration).isNotNull()
-            assertThat(receiveContentConfiguration?.hintMediaTypes).containsExactlyElementsIn(
-                setOf(MediaType.Video, MediaType.Image, MediaType.Text)
-            )
+            assertThat(called).isEqualTo(listOf(3, 2, 1))
         }
     }
 
@@ -379,11 +288,11 @@
             view = LocalView.current
             Box(modifier = Modifier
                 .size(200.dp)
-                .receiveContent(setOf(MediaType.Video)) { it }
+                .contentReceiver { it }
                 .size(100.dp)
-                .receiveContent(setOf(MediaType.Audio)) { it }
+                .contentReceiver { it }
                 .size(50.dp)
-                .receiveContent(setOf(MediaType.Text)) { it }
+                .contentReceiver { it }
             )
         }
 
@@ -411,7 +320,7 @@
             view = LocalView.current
             Box(modifier = Modifier
                 .size(100.dp)
-                .receiveContent(setOf(MediaType.Image)) {
+                .contentReceiver {
                     transferableContent = it
                     null // consume all
                 })
@@ -443,7 +352,7 @@
             view = LocalView.current
             Box(modifier = Modifier
                 .size(100.dp)
-                .receiveContent(setOf(MediaType.Audio)) {
+                .contentReceiver {
                     transferableContent = it
                     null // consume all
                 })
@@ -476,14 +385,14 @@
             view = LocalView.current
             Box(modifier = Modifier
                 .size(200.dp)
-                .receiveContent(setOf(MediaType.All)) {
+                .contentReceiver {
                     parentTransferableContent = it
                     null
                 }) {
                 Box(modifier = Modifier
                     .align(Alignment.Center)
                     .size(100.dp)
-                    .receiveContent(setOf(MediaType.Image)) {
+                    .contentReceiver {
                         childTransferableContent = it
                         it // don't consume anything
                     })
@@ -524,21 +433,21 @@
             view = LocalView.current
             Box(modifier = Modifier
                 .size(200.dp)
-                .receiveContent(setOf(MediaType.All)) {
+                .contentReceiver {
                     grandParentTransferableContent = it
                     null
                 }) {
                 Box(modifier = Modifier
                     .align(Alignment.Center)
                     .size(100.dp)
-                    .receiveContent(setOf(MediaType.Image)) {
+                    .contentReceiver {
                         parentTransferableContent = it
                         it // don't consume anything
                     }) {
                     Box(modifier = Modifier
                         .align(Alignment.Center)
                         .size(50.dp)
-                        .receiveContent(setOf(MediaType.Text)) {
+                        .contentReceiver {
                             childTransferableContent = it
                             it // don't consume anything
                         })
@@ -571,7 +480,7 @@
             Box(
                 modifier = Modifier
                     .size(100.dp)
-                    .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                    .contentReceiver(object : ReceiveContentListener {
                         override fun onDragEnter() {
                             calls += "enter"
                         }
@@ -618,7 +527,7 @@
             Box(
                 modifier = Modifier
                     .size(200.dp)
-                    .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                    .contentReceiver(object : ReceiveContentListener {
                         override fun onDragEnter() {
                             calls += "enter-1"
                         }
@@ -636,7 +545,7 @@
                     modifier = Modifier
                         .align(Alignment.Center)
                         .size(100.dp)
-                        .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                        .contentReceiver(object : ReceiveContentListener {
                             override fun onDragEnter() {
                                 calls += "enter-2"
                             }
@@ -654,7 +563,7 @@
                         modifier = Modifier
                             .align(Alignment.Center)
                             .size(50.dp)
-                            .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                            .contentReceiver(object : ReceiveContentListener {
                                 override fun onDragEnter() {
                                     calls += "enter-3"
                                 }
@@ -710,7 +619,7 @@
             Box(
                 modifier = Modifier
                     .size(100.dp)
-                    .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                    .contentReceiver(object : ReceiveContentListener {
                         override fun onDragStart() {
                             calls += "start"
                         }
@@ -757,7 +666,7 @@
             Box(
                 modifier = Modifier
                     .size(200.dp)
-                    .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                    .contentReceiver(object : ReceiveContentListener {
                         override fun onDragStart() {
                             calls += "start-1"
                         }
@@ -775,7 +684,7 @@
                     modifier = Modifier
                         .align(Alignment.Center)
                         .size(100.dp)
-                        .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                        .contentReceiver(object : ReceiveContentListener {
                             override fun onDragStart() {
                                 calls += "start-2"
                             }
@@ -793,7 +702,7 @@
                         modifier = Modifier
                             .align(Alignment.Center)
                             .size(50.dp)
-                            .receiveContent(setOf(MediaType.All), object : ReceiveContentListener {
+                            .contentReceiver(object : ReceiveContentListener {
                                 override fun onDragStart() {
                                     calls += "start-3"
                                 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TransferableContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TransferableContentTest.kt
index edcb824..d97f989 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TransferableContentTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/content/TransferableContentTest.kt
@@ -90,14 +90,14 @@
     @Test
     fun consumeEach_returnsNull_ifEverythingIsConsumed() {
         val transferableContent = TransferableContent(createClipData())
-        val remaining = transferableContent.consumeEach { true }
+        val remaining = transferableContent.consume { true }
         assertThat(remaining).isNull()
     }
 
     @Test
     fun consumeEach_returnsSameObject_ifNothingIsConsumed() {
         val transferableContent = TransferableContent(createClipData())
-        val remaining = transferableContent.consumeEach { false }
+        val remaining = transferableContent.consume { false }
         assertThat(remaining).isSameInstanceAs(transferableContent)
     }
 
@@ -109,7 +109,7 @@
             addUri(mimeType = "image/gif")
         })
         // only text would remain
-        val remaining = transferableContent.consumeEach { it.uri != null }
+        val remaining = transferableContent.consume { it.uri != null }
         assertThat(remaining?.clipEntry?.clipData?.itemCount).isEqualTo(1)
         assertThat(remaining?.clipEntry?.clipData?.getItemAt(0)?.uri).isNull()
         assertThat(remaining?.hasMediaType(MediaType("video/mp4"))).isTrue()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/draganddrop/DragDropTargetTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/draganddrop/DragDropTargetTest.kt
new file mode 100644
index 0000000..d5f51f1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/draganddrop/DragDropTargetTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.draganddrop
+
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.content.testDragAndDrop
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class DragDropTargetTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun dragAndDropTarget_changingTarget_updatesModifier() {
+        var targetKey by mutableIntStateOf(0)
+        val dropEvents = mutableListOf<Int>()
+        lateinit var view: View
+        rule.setContent {
+            view = LocalView.current
+            Box(
+                Modifier
+                    .size(60.dp)
+                    .background(Color.Blue)
+                    .testTag("target")
+                    .dragAndDropTarget(
+                        shouldStartDragAndDrop = { true },
+                        target = remember(targetKey) {
+                            DragAndDropTarget {
+                                dropEvents.add(targetKey)
+                                return@DragAndDropTarget true
+                            }
+                        }
+                    )
+            )
+        }
+
+        assertThat(dropEvents).isEmpty()
+        val targetPosition = rule.onNodeWithTag("target").fetchSemanticsNode().boundsInRoot.center
+
+        testDragAndDrop(view, rule.density) {
+            drag(targetPosition, "FAKE EVENT")
+            drop()
+            cancelDrag()
+        }
+        rule.waitForIdle()
+
+        assertThat(dropEvents[0]).isEqualTo(0)
+
+        targetKey++
+        rule.waitForIdle()
+
+        testDragAndDrop(view, rule.density) {
+            drag(targetPosition, "FAKE EVENT")
+            drop()
+            cancelDrag()
+        }
+
+        assertThat(dropEvents[1]).isEqualTo(1)
+
+        targetKey--
+        rule.waitForIdle()
+
+        testDragAndDrop(view, rule.density) {
+            drag(targetPosition, "FAKE EVENT")
+            drop()
+            cancelDrag()
+        }
+
+        assertThat(dropEvents[2]).isEqualTo(0)
+    }
+
+    // todo(jossiwolf) b/330481832 Investigate behavior and update
+    @Test
+    fun dragAndDropTarget_changingTarget_whileDragging_doesntReceiveEvent() {
+        var targetKey by mutableIntStateOf(0)
+        val dropEvents = mutableListOf<Int>()
+        lateinit var view: View
+        rule.setContent {
+            view = LocalView.current
+            Box(
+                Modifier
+                    .size(60.dp)
+                    .background(Color.Blue)
+                    .testTag("target")
+                    .dragAndDropTarget(
+                        shouldStartDragAndDrop = { true },
+                        target = remember(targetKey) {
+                            DragAndDropTarget {
+                                dropEvents.add(targetKey)
+                                return@DragAndDropTarget true
+                            }
+                        }
+                    )
+            )
+        }
+
+        assertThat(dropEvents).isEmpty()
+        val targetPosition = rule.onNodeWithTag("target").fetchSemanticsNode().boundsInRoot.center
+
+        testDragAndDrop(view, rule.density) {
+            drag(targetPosition, "FAKE EVENT")
+            targetKey++
+            rule.waitForIdle()
+            drop()
+            cancelDrag()
+        }
+        rule.waitForIdle()
+        assertThat(dropEvents).isEmpty()
+    }
+
+    @Test
+    fun dragAndDropTarget_shouldStartDragAndDrop_referencesLatestInstance() {
+        var shouldStartDragAndDropKey by mutableIntStateOf(0)
+        val shouldStartDragAndDropInvocations = mutableListOf<Int>()
+        lateinit var view: View
+        rule.setContent {
+            view = LocalView.current
+            Box(
+                Modifier
+                    .size(60.dp)
+                    .background(Color.Blue)
+                    .testTag("target")
+                    .dragAndDropTarget(
+                        shouldStartDragAndDrop = remember(shouldStartDragAndDropKey) {
+                            {
+                                shouldStartDragAndDropInvocations.add(shouldStartDragAndDropKey)
+                                true
+                            }
+                        },
+                        target = remember { DragAndDropTarget { return@DragAndDropTarget true } }
+                    )
+            )
+        }
+
+        val targetPosition = rule.onNodeWithTag("target").fetchSemanticsNode().boundsInRoot.center
+
+        testDragAndDrop(view, rule.density) {
+            drag(targetPosition, "FAKE EVENT")
+            drop()
+            cancelDrag()
+        }
+        rule.waitForIdle()
+
+        assertThat(shouldStartDragAndDropInvocations[0]).isEqualTo(0)
+
+        shouldStartDragAndDropKey++
+        rule.waitForIdle()
+        testDragAndDrop(view, rule.density) {
+            drag(targetPosition, "FAKE EVENT")
+            drop()
+            cancelDrag()
+        }
+        rule.waitForIdle()
+
+        assertThat(shouldStartDragAndDropInvocations[1]).isEqualTo(1)
+    }
+
+    private fun DragAndDropTarget(
+        onEvent: (Boolean) -> Unit = {},
+        onDrop: (DragAndDropEvent) -> Boolean
+    ) = object : DragAndDropTarget {
+        override fun onEntered(event: DragAndDropEvent) = onEvent(true)
+        override fun onExited(event: DragAndDropEvent) = onEvent(false)
+        override fun onEnded(event: DragAndDropEvent) = onEvent(false)
+        override fun onDrop(event: DragAndDropEvent) = onDrop(event)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
index 882810d..f06e0b8 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
@@ -158,14 +158,25 @@
                 pageCount = { DefaultPageCount },
                 modifier = Modifier.size(pageSizeDp * 1.5f),
                 pageSize = PageSize.Fixed(pageSizeDp),
-                outOfBoundsPageCount = 2,
+                outOfBoundsPageCount = it.outOfBoundsPageCount,
                 orientation = it.orientation,
-                pageSpacing = it.pageSpacing,
-                contentPadding = it.mainAxisContentPadding
+                pageSpacing = it.pageSpacing
             )
         }
 
-        forEachParameter(ParamsToTest) { param ->
+        val Params = mutableListOf<SingleParamConfig>().apply {
+            for (orientation in TestOrientation) {
+                for (pageSpacing in TestPageSpacing) {
+                    add(SingleParamConfig(
+                        orientation = orientation,
+                        pageSpacing = pageSpacing,
+                        outOfBoundsPageCount = 2
+                    ))
+                }
+            }
+        }
+
+        forEachParameter(Params) { param ->
             val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
             // Assert
             rule.runOnIdle {
@@ -184,6 +195,8 @@
                     Truth.assertThat(pagerState.layoutWithMeasurement)
                         .isEqualTo(previousNumberOfRemeasurementPasses)
                 }
+
+                // verify that out of bounds pages are correctly placed
                 param.confirmPageIsInCorrectPosition(
                     pagerState.currentPage,
                     lastVisible + 1,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt
new file mode 100644
index 0000000..2f008d6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldHandwritingTest.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import android.view.inputmethod.CursorAnchorInfo
+import android.view.inputmethod.ExtractedText
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.setFocusableContent
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
+import androidx.compose.foundation.text.input.InputMethodInterceptor
+import androidx.compose.foundation.text.input.internal.InputMethodManager
+import androidx.compose.foundation.text.input.internal.inputMethodManagerFactory
+import androidx.compose.foundation.text.matchers.isZero
+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.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class CoreTextFieldHandwritingTest {
+    @get:Rule
+    val rule = createComposeRule()
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
+    private val Tag = "CoreTextField"
+
+    private val fakeImm = object : InputMethodManager {
+        private var stylusHandwritingStartCount = 0
+
+        fun expectStylusHandwriting(started: Boolean) {
+            if (started) {
+                assertThat(stylusHandwritingStartCount).isEqualTo(1)
+                stylusHandwritingStartCount = 0
+            } else {
+                assertThat(stylusHandwritingStartCount).isZero()
+            }
+        }
+
+        override fun isActive(): Boolean = true
+
+        override fun restartInput() {}
+
+        override fun showSoftInput() {}
+
+        override fun hideSoftInput() {}
+
+        override fun updateExtractedText(token: Int, extractedText: ExtractedText) {}
+
+        override fun updateSelection(
+            selectionStart: Int,
+            selectionEnd: Int,
+            compositionStart: Int,
+            compositionEnd: Int
+        ) {}
+
+        override fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo) {}
+
+        override fun startStylusHandwriting() {
+            ++stylusHandwritingStartCount
+        }
+    }
+
+    @Before
+    fun setup() {
+        // Test is only meaningful when stylusHandwriting is supported.
+        assumeTrue(isStylusHandwritingSupported)
+    }
+
+    @Test
+    fun coreTextField_startHandwriting_unfocused() {
+        testStylusHandwriting(stylusHandwritingStarted = true) {
+            performStylusHandwriting()
+        }
+    }
+
+    @Test
+    fun coreTextField_startStylusHandwriting_unfocused() {
+        testStylusHandwriting(stylusHandwritingStarted = true) {
+            performStylusHandwriting()
+        }
+    }
+
+    @Test
+    fun coreTextField_startStylusHandwriting_focused() {
+        testStylusHandwriting(stylusHandwritingStarted = true) {
+            requestFocus()
+            performStylusHandwriting()
+        }
+    }
+
+    @Test
+    fun coreTextField_click_notStartStylusHandwriting() {
+        testStylusHandwriting(stylusHandwritingStarted = false) {
+            performStylusClick()
+        }
+    }
+
+    @Test
+    fun coreTextField_longClick_notStartStylusHandwriting() {
+        testStylusHandwriting(stylusHandwritingStarted = false) {
+            performStylusLongClick()
+        }
+    }
+
+    @Test
+    fun coreTextField_longPressAndDrag_notStartStylusHandwriting() {
+        testStylusHandwriting(stylusHandwritingStarted = false) {
+            performStylusLongPressAndDrag()
+        }
+    }
+
+    @Test
+    fun coreTextField_toggleEnabled_startStylusHandwriting() {
+        inputMethodManagerFactory = { fakeImm }
+
+        var enabled by mutableStateOf(true)
+
+        setContent {
+            val value = remember { TextFieldValue() }
+            CoreTextField(
+                value = value,
+                onValueChange = { },
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                enabled = enabled
+            )
+        }
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+
+        // Toggle enabled to false, shouldn't start handwriting
+        enabled = false
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = false)
+
+        // Toggle to true again, should be able to start handwriting
+        enabled = true
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+    }
+
+    @Test
+    fun coreTextField_toggleReadOnly_startStylusHandwriting() {
+        inputMethodManagerFactory = { fakeImm }
+
+        var readOnly by mutableStateOf(false)
+
+        setContent {
+            val value = remember { TextFieldValue() }
+            CoreTextField(
+                value = value,
+                onValueChange = { },
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                readOnly = readOnly
+            )
+        }
+
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+
+        // Toggle enabled to true, shouldn't start handwriting
+        readOnly = true
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = false)
+
+        // Toggle to true again, should be able to start handwriting
+        readOnly = false
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+    }
+
+    private fun testStylusHandwriting(
+        stylusHandwritingStarted: Boolean,
+        interaction: SemanticsNodeInteraction.() -> Unit
+    ) {
+        inputMethodManagerFactory = { fakeImm }
+
+        setContent {
+            val value = remember { TextFieldValue() }
+            CoreTextField(
+                value = value,
+                onValueChange = { },
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        interaction.invoke(rule.onNodeWithTag(Tag))
+        rule.waitForIdle()
+        fakeImm.expectStylusHandwriting(stylusHandwritingStarted)
+    }
+
+    private fun setContent(
+        extraItemForInitialFocus: Boolean = true,
+        content: @Composable () -> Unit
+    ) {
+        rule.setFocusableContent(extraItemForInitialFocus) {
+            inputMethodInterceptor.Content {
+                content()
+            }
+        }
+    }
+
+    private fun performHandwritingAndExpect(stylusHandwritingStarted: Boolean) {
+        rule.onNodeWithTag(Tag).performStylusHandwriting()
+        rule.waitForIdle()
+        fakeImm.expectStylusHandwriting(stylusHandwritingStarted)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
index cc4fccf..e3f3e01 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/CoreTextFieldInputServiceIntegrationTest.kt
@@ -471,6 +471,7 @@
                 cursorAnchorInfos += cursorAnchorInfo
             }
 
+            override fun startStylusHandwriting() {}
             override fun isActive(): Boolean = true
             override fun restartInput() {}
             override fun showSoftInput() {}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
index 601de6e..ca78f4d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/DefaultKeyboardActionsTest.kt
@@ -18,11 +18,15 @@
 
 import android.os.Build
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.text.input.InputMethodInterceptor
+import androidx.compose.foundation.text.input.TestSoftwareKeyboardController
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -51,6 +55,8 @@
     @get:Rule
     val rule = createComposeRule()
 
+    private val inputMethodInterceptor = InputMethodInterceptor(rule)
+
     // We need to wrap the inline class parameter in another class because Java can't instantiate
     // the inline class.
     class Param(val imeAction: ImeAction) {
@@ -76,42 +82,45 @@
         val (value1, value2, value3) = List(3) { TextFieldValue("Placeholder Text") }
         val (textField1, textField2, textField3) = FocusRequester.createRefs()
         var (focusState1, focusState2, focusState3) = List(3) { false }
-        val keyboardHelper = KeyboardHelper(rule)
+        val keyboardController = TestSoftwareKeyboardController(rule)
 
-        rule.setContent {
-            keyboardHelper.initialize()
-            Column {
-                CoreTextField(
-                    value = value1,
-                    onValueChange = {},
-                    modifier = Modifier
-                        .focusRequester(textField1)
-                        .onFocusChanged { focusState1 = it.isFocused }
-                )
-                CoreTextField(
-                    value = value2,
-                    onValueChange = {},
-                    modifier = Modifier
-                        .testTag(initialTextField)
-                        .focusRequester(textField2)
-                        .focusProperties { previous = textField1; next = textField3 }
-                        .onFocusChanged { focusState2 = it.isFocused },
-                    imeOptions = ImeOptions(imeAction = imeAction)
-                )
-                CoreTextField(
-                    value = value3,
-                    onValueChange = {},
-                    modifier = Modifier
-                        .focusRequester(textField3)
-                        .onFocusChanged { focusState3 = it.isFocused }
-                )
+        inputMethodInterceptor.setContent {
+            CompositionLocalProvider(
+                LocalSoftwareKeyboardController provides keyboardController
+            ) {
+                Column {
+                    CoreTextField(
+                        value = value1,
+                        onValueChange = {},
+                        modifier = Modifier
+                            .focusRequester(textField1)
+                            .onFocusChanged { focusState1 = it.isFocused }
+                    )
+                    CoreTextField(
+                        value = value2,
+                        onValueChange = {},
+                        modifier = Modifier
+                            .testTag(initialTextField)
+                            .focusRequester(textField2)
+                            .focusProperties { previous = textField1; next = textField3 }
+                            .onFocusChanged { focusState2 = it.isFocused },
+                        imeOptions = ImeOptions(imeAction = imeAction)
+                    )
+                    CoreTextField(
+                        value = value3,
+                        onValueChange = {},
+                        modifier = Modifier
+                            .focusRequester(textField3)
+                            .onFocusChanged { focusState3 = it.isFocused }
+                    )
+                }
             }
         }
 
         // Show keyboard.
         rule.onNodeWithTag(initialTextField).performClick()
-        keyboardHelper.waitForKeyboardVisibility(visible = true)
-        assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
+        inputMethodInterceptor.assertSessionActive()
+        keyboardController.show()
 
         // Act.
         rule.onNodeWithTag(initialTextField).performImeAction()
@@ -137,8 +146,7 @@
                 assertThat(focusState3).isFalse()
 
                 // Software keyboard is hidden.
-                keyboardHelper.waitForKeyboardVisibility(false)
-                assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
+                keyboardController.assertHidden()
             }
             else -> {
                 // No change to focus state.
@@ -157,51 +165,53 @@
         val (value1, value2, value3) = List(3) { TextFieldValue("Placeholder Text") }
         val (textField1, textField2, textField3) = FocusRequester.createRefs()
         var (focusState1, focusState2, focusState3) = List(3) { false }
-        val keyboardHelper = KeyboardHelper(rule)
+        val keyboardController = TestSoftwareKeyboardController(rule)
 
-        rule.setContent {
-            keyboardHelper.initialize()
-            Column {
-                CoreTextField(
-                    value = value1,
-                    onValueChange = {},
-                    modifier = Modifier
-                        .focusRequester(textField1)
-                        .onFocusChanged { focusState1 = it.isFocused }
-                )
-                CoreTextField(
-                    value = value2,
-                    onValueChange = {},
-                    modifier = Modifier
-                        .testTag(initialTextField)
-                        .focusRequester(textField2)
-                        .focusProperties { previous = textField1; next = textField3 }
-                        .onFocusChanged { focusState2 = it.isFocused },
-                    imeOptions = ImeOptions(imeAction = imeAction),
-                    keyboardActions = KeyboardActions(
-                        onDone = { defaultKeyboardAction(Done) },
-                        onGo = { defaultKeyboardAction(Go) },
-                        onNext = { defaultKeyboardAction(Next) },
-                        onPrevious = { defaultKeyboardAction(Previous) },
-                        onSearch = { defaultKeyboardAction(Search) },
-                        onSend = { defaultKeyboardAction(Send) },
+        inputMethodInterceptor.setContent {
+            CompositionLocalProvider(
+                LocalSoftwareKeyboardController provides keyboardController
+            ) {
+                Column {
+                    CoreTextField(
+                        value = value1,
+                        onValueChange = {},
+                        modifier = Modifier
+                            .focusRequester(textField1)
+                            .onFocusChanged { focusState1 = it.isFocused }
                     )
-                )
-                CoreTextField(
-                    value = value3,
-                    onValueChange = {},
-                    modifier = Modifier
-                        .focusRequester(textField3)
-                        .onFocusChanged { focusState3 = it.isFocused }
-                )
+                    CoreTextField(
+                        value = value2,
+                        onValueChange = {},
+                        modifier = Modifier
+                            .testTag(initialTextField)
+                            .focusRequester(textField2)
+                            .focusProperties { previous = textField1; next = textField3 }
+                            .onFocusChanged { focusState2 = it.isFocused },
+                        imeOptions = ImeOptions(imeAction = imeAction),
+                        keyboardActions = KeyboardActions(
+                            onDone = { defaultKeyboardAction(Done) },
+                            onGo = { defaultKeyboardAction(Go) },
+                            onNext = { defaultKeyboardAction(Next) },
+                            onPrevious = { defaultKeyboardAction(Previous) },
+                            onSearch = { defaultKeyboardAction(Search) },
+                            onSend = { defaultKeyboardAction(Send) },
+                        )
+                    )
+                    CoreTextField(
+                        value = value3,
+                        onValueChange = {},
+                        modifier = Modifier
+                            .focusRequester(textField3)
+                            .onFocusChanged { focusState3 = it.isFocused }
+                    )
+                }
             }
         }
 
         // Show keyboard.
         rule.onNodeWithTag(initialTextField).performClick()
-
-        keyboardHelper.waitForKeyboardVisibility(visible = true)
-        assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
+        inputMethodInterceptor.assertSessionActive()
+        keyboardController.show()
 
         // Act.
         rule.onNodeWithTag(initialTextField).performImeAction()
@@ -227,8 +237,7 @@
                 assertThat(focusState3).isFalse()
 
                 // Software keyboard is hidden.
-                keyboardHelper.waitForKeyboardVisibility(false)
-                assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
+                keyboardController.assertHidden()
             }
             else -> {
                 // No change to focus state.
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt
new file mode 100644
index 0000000..966c702
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/HandwritingTestUtils.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import android.view.KeyEvent
+import android.view.MotionEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.invokeGlobalAssertions
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.center
+import androidx.compose.ui.unit.toOffset
+import androidx.core.view.InputDeviceCompat
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.math.roundToInt
+
+// We don't have StylusInjectionScope at the moment. This is a simplified implementation for
+// the basic use cases in this test. It only supports single stylus pointer, and the pointerId
+// is totally ignored.
+internal class HandwritingTestStylusInjectScope(
+    semanticsNode: SemanticsNode
+) : TouchInjectionScope, Density by semanticsNode.layoutInfo.density {
+    private val root = semanticsNode.root as ViewRootForTest
+    private val downTime: Long = System.currentTimeMillis()
+
+    private var lastPosition: Offset = Offset.Unspecified
+    private var currentTime: Long = System.currentTimeMillis()
+    private val boundsInRoot = semanticsNode.boundsInRoot
+
+    override val visibleSize: IntSize =
+        IntSize(boundsInRoot.width.roundToInt(), boundsInRoot.height.roundToInt())
+
+    override val viewConfiguration: ViewConfiguration =
+        semanticsNode.layoutInfo.viewConfiguration
+
+    private fun localToRoot(position: Offset): Offset {
+        return position + boundsInRoot.topLeft
+    }
+
+    override fun advanceEventTime(durationMillis: Long) {
+        require(durationMillis >= 0) {
+            "duration of a delay can only be positive, not $durationMillis"
+        }
+        currentTime += durationMillis
+    }
+
+    override fun currentPosition(pointerId: Int): Offset? {
+        return lastPosition
+    }
+
+    override fun down(pointerId: Int, position: Offset) {
+        val rootPosition = localToRoot(position)
+        lastPosition = rootPosition
+        sendTouchEvent(KeyEvent.ACTION_DOWN)
+    }
+
+    override fun updatePointerTo(pointerId: Int, position: Offset) {
+        lastPosition = localToRoot(position)
+    }
+
+    override fun move(delayMillis: Long) {
+        advanceEventTime(delayMillis)
+        sendTouchEvent(MotionEvent.ACTION_MOVE)
+    }
+
+    @ExperimentalTestApi
+    override fun moveWithHistoryMultiPointer(
+        relativeHistoricalTimes: List<Long>,
+        historicalCoordinates: List<List<Offset>>,
+        delayMillis: Long
+    ) {
+        // Not needed for this test because Android only support one stylus pointer.
+    }
+
+    override fun up(pointerId: Int) {
+        sendTouchEvent(MotionEvent.ACTION_UP)
+    }
+
+    override fun cancel(delayMillis: Long) {
+        sendTouchEvent(MotionEvent.ACTION_CANCEL)
+    }
+
+    private fun sendTouchEvent(action: Int) {
+        val positionInScreen = run {
+            val array = intArrayOf(0, 0)
+            root.view.getLocationOnScreen(array)
+            Offset(array[0].toFloat(), array[1].toFloat())
+        }
+        val motionEvent = MotionEvent.obtain(
+            /* downTime = */ downTime,
+            /* eventTime = */ currentTime,
+            /* action = */ action,
+            /* pointerCount = */ 1,
+            /* pointerProperties = */ arrayOf(
+                MotionEvent.PointerProperties().apply {
+                    id = 0
+                    toolType = MotionEvent.TOOL_TYPE_STYLUS
+                }
+            ),
+            /* pointerCoords = */ arrayOf(
+                MotionEvent.PointerCoords().apply {
+                    val startOffset = lastPosition
+
+                    // Allows for non-valid numbers/Offsets to be passed along to Compose to
+                    // test if it handles them properly (versus breaking here and we not knowing
+                    // if Compose properly handles these values).
+                    x = if (startOffset.isValid()) {
+                        positionInScreen.x + startOffset.x
+                    } else {
+                        Float.NaN
+                    }
+
+                    y = if (startOffset.isValid()) {
+                        positionInScreen.y + startOffset.y
+                    } else {
+                        Float.NaN
+                    }
+                }
+            ),
+            /* metaState = */ 0,
+            /* buttonState = */ 0,
+            /* xPrecision = */ 1f,
+            /* yPrecision = */ 1f,
+            /* deviceId = */ 0,
+            /* edgeFlags = */ 0,
+            /* source = */ InputDeviceCompat.SOURCE_TOUCHSCREEN,
+            /* flags = */ 0
+        )
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            root.view.dispatchTouchEvent(motionEvent)
+        }
+    }
+}
+
+/** Start stylus handwriting on the target element. */
+internal fun SemanticsNodeInteraction.performStylusHandwriting() {
+    performStylusInput {
+        val startPosition = visibleSize.center.toOffset()
+        down(startPosition)
+        moveTo(startPosition + Offset(viewConfiguration.handwritingSlop * 2, 0f))
+        up()
+    }
+}
+
+internal fun SemanticsNodeInteraction.performStylusClick() {
+    performStylusInput {
+        down(visibleSize.center.toOffset())
+        move()
+        up()
+    }
+}
+
+internal fun SemanticsNodeInteraction.performStylusLongClick() {
+    performStylusInput {
+        down(visibleSize.center.toOffset())
+        move(viewConfiguration.longPressTimeoutMillis + 1)
+        up()
+    }
+}
+
+internal fun SemanticsNodeInteraction.performStylusLongPressAndDrag() {
+    performStylusInput {
+        val startPosition = visibleSize.center.toOffset()
+        down(visibleSize.center.toOffset())
+        val position = startPosition + Offset(viewConfiguration.handwritingSlop * 2, 0f)
+        moveTo(
+            position = position,
+            delayMillis = viewConfiguration.longPressTimeoutMillis + 1
+        )
+        up()
+    }
+}
+
+private fun SemanticsNodeInteraction.performStylusInput(
+    block: TouchInjectionScope.() -> Unit
+): SemanticsNodeInteraction {
+    @OptIn(ExperimentalTestApi::class)
+    invokeGlobalAssertions()
+    val node = fetchSemanticsNode("Failed to inject stylus input.")
+    val stylusInjectionScope = HandwritingTestStylusInjectScope(node)
+    block.invoke(stylusInjectionScope)
+    return this
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt
index 0053b63..0e6f520 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldHandwritingTest.kt
@@ -16,41 +16,33 @@
 
 package androidx.compose.foundation.text.input
 
-import android.view.KeyEvent.ACTION_DOWN
-import android.view.MotionEvent
-import android.view.MotionEvent.ACTION_CANCEL
-import android.view.MotionEvent.ACTION_MOVE
-import android.view.MotionEvent.ACTION_UP
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
+import androidx.compose.foundation.text.performStylusClick
+import androidx.compose.foundation.text.performStylusHandwriting
+import androidx.compose.foundation.text.performStylusLongClick
+import androidx.compose.foundation.text.performStylusLongPressAndDrag
+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.geometry.Offset
-import androidx.compose.ui.platform.ViewConfiguration
-import androidx.compose.ui.platform.ViewRootForTest
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.SemanticsNode
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.TouchInjectionScope
-import androidx.compose.ui.test.invokeGlobalAssertions
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.requestFocus
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.center
-import androidx.compose.ui.unit.toOffset
-import androidx.core.view.InputDeviceCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import kotlin.math.roundToInt
+import org.junit.Assume.assumeTrue
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@OptIn(ExperimentalFoundationApi::class)
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 internal class BasicTextFieldHandwritingTest {
@@ -66,6 +58,12 @@
 
     private val imm = FakeInputMethodManager()
 
+    @Before
+    fun setup() {
+        // Test is only meaningful when stylus handwriting is supported.
+        assumeTrue(isStylusHandwritingSupported)
+    }
+
     @Test
     fun textField_startStylusHandwriting_unfocused() {
         testStylusHandwriting(stylusHandwritingStarted = true) {
@@ -84,38 +82,21 @@
     @Test
     fun textField_click_notStartStylusHandwriting() {
         testStylusHandwriting(stylusHandwritingStarted = false) {
-            performStylusInput {
-                down(visibleSize.center.toOffset())
-                move()
-                up()
-            }
+            performStylusClick()
         }
     }
 
     @Test
     fun textField_longClick_notStartStylusHandwriting() {
         testStylusHandwriting(stylusHandwritingStarted = false) {
-            performStylusInput {
-                down(visibleSize.center.toOffset())
-                move(viewConfiguration.longPressTimeoutMillis + 1)
-                up()
-            }
+            performStylusLongClick()
         }
     }
 
     @Test
     fun textField_longPressAndDrag_notStartStylusHandwriting() {
         testStylusHandwriting(stylusHandwritingStarted = false) {
-            performStylusInput {
-                val startPosition = visibleSize.center.toOffset()
-                down(visibleSize.center.toOffset())
-                val position = startPosition + Offset(viewConfiguration.handwritingSlop * 2, 0f)
-                moveTo(
-                    position = position,
-                    delayMillis = viewConfiguration.longPressTimeoutMillis + 1
-                )
-                up()
-            }
+            performStylusLongPressAndDrag()
         }
     }
 
@@ -157,6 +138,58 @@
         }
     }
 
+    @Test
+    fun textField_toggleEnabled_startStylusHandwriting() {
+        immRule.setFactory { imm }
+        var enabled by mutableStateOf(true)
+        inputMethodInterceptor.setTextFieldTestContent {
+            val state = remember { TextFieldState() }
+            BasicTextField(
+                state = state,
+                modifier = Modifier.fillMaxSize().testTag(Tag),
+                enabled = enabled
+            )
+        }
+
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+
+        // Toggle enabled to false, shouldn't start handwriting
+        enabled = false
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = false)
+
+        // Toggle to true again, should be able to start handwriting
+        enabled = true
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+    }
+
+    @Test
+    fun textField_toggleReadOnly_startStylusHandwriting() {
+        immRule.setFactory { imm }
+        var readOnly by mutableStateOf(false)
+        inputMethodInterceptor.setTextFieldTestContent {
+            val state = remember { TextFieldState() }
+            BasicTextField(
+                state = state,
+                modifier = Modifier.fillMaxSize().testTag(Tag),
+                readOnly = readOnly
+            )
+        }
+
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+
+        // Toggle enabled to true, shouldn't start handwriting
+        readOnly = true
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = false)
+
+        // Toggle to true again, should be able to start handwriting
+        readOnly = false
+        rule.waitForIdle()
+        performHandwritingAndExpect(stylusHandwritingStarted = true)
+    }
+
     private fun testStylusHandwriting(
         stylusHandwritingStarted: Boolean,
         interaction: SemanticsNodeInteraction.() -> Unit
@@ -180,142 +213,14 @@
         }
     }
 
-    /** Start stylus handwriting on the target element. */
-    private fun SemanticsNodeInteraction.performStylusHandwriting() {
-        performStylusInput {
-            val startPosition = visibleSize.center.toOffset()
-            down(startPosition)
-            moveTo(startPosition + Offset(viewConfiguration.handwritingSlop * 2, 0f))
-            up()
-        }
-    }
-
-    private fun SemanticsNodeInteraction.performStylusInput(
-        block: TouchInjectionScope.() -> Unit
-    ): SemanticsNodeInteraction {
-        @OptIn(ExperimentalTestApi::class)
-        invokeGlobalAssertions()
-        val node = fetchSemanticsNode("Failed to inject stylus input.")
-        val stylusInjectionScope = StylusInjectionScope(node)
-        block.invoke(stylusInjectionScope)
-        return this
-    }
-
-    // We don't have StylusInjectionScope at the moment. This is a simplified implementation for
-    // the basic use cases in this test. It only supports single stylus pointer, and the pointerId
-    // is totally ignored.
-    private inner class StylusInjectionScope(
-        semanticsNode: SemanticsNode
-    ) : TouchInjectionScope, Density by semanticsNode.layoutInfo.density {
-        private val root = semanticsNode.root as ViewRootForTest
-        private val downTime: Long = System.currentTimeMillis()
-
-        private var lastPosition: Offset = Offset.Unspecified
-        private var currentTime: Long = System.currentTimeMillis()
-        private val boundsInRoot = semanticsNode.boundsInRoot
-
-        override val visibleSize: IntSize =
-            IntSize(boundsInRoot.width.roundToInt(), boundsInRoot.height.roundToInt())
-
-        override val viewConfiguration: ViewConfiguration =
-            semanticsNode.layoutInfo.viewConfiguration
-
-        private fun localToRoot(position: Offset): Offset {
-            return position + boundsInRoot.topLeft
-        }
-
-        override fun advanceEventTime(durationMillis: Long) {
-            require(durationMillis >= 0) {
-                "duration of a delay can only be positive, not $durationMillis"
-            }
-            currentTime += durationMillis
-        }
-
-        override fun currentPosition(pointerId: Int): Offset? {
-            return lastPosition
-        }
-
-        override fun down(pointerId: Int, position: Offset) {
-            val rootPosition = localToRoot(position)
-            lastPosition = rootPosition
-            sendTouchEvent(ACTION_DOWN)
-        }
-
-        override fun updatePointerTo(pointerId: Int, position: Offset) {
-            lastPosition = localToRoot(position)
-        }
-
-        override fun move(delayMillis: Long) {
-            advanceEventTime(delayMillis)
-            sendTouchEvent(ACTION_MOVE)
-        }
-
-        @ExperimentalTestApi
-        override fun moveWithHistoryMultiPointer(
-            relativeHistoricalTimes: List<Long>,
-            historicalCoordinates: List<List<Offset>>,
-            delayMillis: Long
-        ) {
-            // Not needed for this test because Android only support one stylus pointer.
-        }
-
-        override fun up(pointerId: Int) {
-            sendTouchEvent(ACTION_UP)
-        }
-
-        override fun cancel(delayMillis: Long) {
-            sendTouchEvent(ACTION_CANCEL)
-        }
-
-        private fun sendTouchEvent(action: Int) {
-            val positionInScreen = run {
-                val array = intArrayOf(0, 0)
-                root.view.getLocationOnScreen(array)
-                Offset(array[0].toFloat(), array[1].toFloat())
-            }
-            val motionEvent = MotionEvent.obtain(
-                /* downTime = */ downTime,
-                /* eventTime = */ currentTime,
-                /* action = */ action,
-                /* pointerCount = */ 1,
-                /* pointerProperties = */ arrayOf(
-                    MotionEvent.PointerProperties().apply {
-                        id = 0
-                        toolType = MotionEvent.TOOL_TYPE_STYLUS
-                    }
-                ),
-                /* pointerCoords = */ arrayOf(
-                    MotionEvent.PointerCoords().apply {
-                        val startOffset = lastPosition
-
-                        // Allows for non-valid numbers/Offsets to be passed along to Compose to
-                        // test if it handles them properly (versus breaking here and we not knowing
-                        // if Compose properly handles these values).
-                        x = if (startOffset.isValid()) {
-                            positionInScreen.x + startOffset.x
-                        } else {
-                            Float.NaN
-                        }
-
-                        y = if (startOffset.isValid()) {
-                            positionInScreen.y + startOffset.y
-                        } else {
-                            Float.NaN
-                        }
-                    }
-                ),
-                /* metaState = */ 0,
-                /* buttonState = */ 0,
-                /* xPrecision = */ 1f,
-                /* yPrecision = */ 1f,
-                /* deviceId = */ 0,
-                /* edgeFlags = */ 0,
-                /* source = */ InputDeviceCompat.SOURCE_TOUCHSCREEN,
-                /* flags = */ 0
-            )
-
-            rule.runOnUiThread {
-                root.view.dispatchTouchEvent(motionEvent)
+    private fun performHandwritingAndExpect(stylusHandwritingStarted: Boolean) {
+        rule.onNodeWithTag(Tag).performStylusHandwriting()
+        rule.waitForIdle()
+        rule.runOnIdle {
+            if (stylusHandwritingStarted) {
+                imm.expectCall("startStylusHandwriting")
+            } else {
+                imm.expectNoMoreCalls()
             }
         }
     }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt
index 3a7e1f3..32b32fc 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldImeSelectionChangesTest.kt
@@ -93,6 +93,22 @@
     }
 
     @Test
+    fun perform_setComposingText() {
+        val state = TextFieldState("Hello")
+        inputMethodInterceptor.setTextFieldTestContent {
+            BasicTextField(state = state, modifier = Modifier.testTag(Tag))
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+
+        inputMethodInterceptor.withInputConnection {
+            setComposingRegion(0, 5)
+            setComposingText("World", 1)
+        }
+
+        imm.expectCall("updateSelection(5, 5, 0, 5)")
+    }
+
+    @Test
     fun perform_sendKeyEvent() {
         val state = TextFieldState("Hello")
         lateinit var view: View
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt
index 3ad3c19..4a2d653 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldSemanticsTest.kt
@@ -385,7 +385,7 @@
         rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(2, 3))
+            assertThat(state.selection).isEqualTo(TextRange(2, 3))
         }
     }
 
@@ -406,7 +406,7 @@
         rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(2))
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
         }
     }
 
@@ -517,7 +517,7 @@
         rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.PasteText)
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
             assertThat(state.text.toString()).isEqualTo("Hello World!")
         }
     }
@@ -548,7 +548,7 @@
         rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.PasteText)
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(9))
+            assertThat(state.selection).isEqualTo(TextRange(9))
             assertThat(state.text.toString()).isEqualTo("Heo Word!")
         }
     }
@@ -611,7 +611,7 @@
         rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.CopyText)
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
         }
     }
 
@@ -633,7 +633,7 @@
 
         rule.runOnIdle {
             assertThat(state.text.toString()).isEqualTo(" World!")
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
             assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
         }
     }
@@ -659,7 +659,7 @@
 
         rule.runOnIdle {
             assertThat(state.text.toString()).isEqualTo("Hello World!")
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
             assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
         }
     }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
index f5952d3..9622281 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/BasicTextFieldTest.kt
@@ -372,7 +372,7 @@
         inputMethodInterceptor.setTextFieldTestContent {
             BasicTextField(
                 state = state,
-                keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
+                keyboardOptions = KeyboardOptions(showKeyboardOnFocus = false),
                 modifier = Modifier
                     .fillMaxSize()
                     .testTag(Tag)
@@ -395,7 +395,7 @@
         inputMethodInterceptor.setTextFieldTestContent {
             BasicTextField(
                 state = state,
-                keyboardOptions = KeyboardOptions(shouldShowKeyboardOnFocus = false),
+                keyboardOptions = KeyboardOptions(showKeyboardOnFocus = false),
                 modifier = Modifier
                     .fillMaxSize()
                     .testTag(Tag)
@@ -1134,7 +1134,7 @@
         rule.waitForIdle()
 
         assertThat(tfs.text.toString()).isEqualTo(longText)
-        assertThat(tfs.text.selection).isEqualTo(TextRange(longText.length))
+        assertThat(tfs.selection).isEqualTo(TextRange(longText.length))
     }
 
     @Test
@@ -1155,7 +1155,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
             assertThat(imm.expectCall("updateSelection(0, 5, -1, -1)"))
         }
     }
@@ -1230,7 +1230,7 @@
 
         rule.runOnIdle {
             assertThat(state.text.toString()).isEqualTo("Worldo")
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
index 4944195..96cb15d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/DecorationBoxTest.kt
@@ -273,7 +273,7 @@
 
         // assertThat selection happened
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
         }
     }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt
index 58713f7..989f8c9 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt
@@ -74,4 +74,12 @@
     override fun startStylusHandwriting() {
         calls += "startStylusHandwriting"
     }
+
+    override fun prepareStylusHandwritingDelegation() {
+        calls += "prepareStylusHandwritingDelegation"
+    }
+
+    override fun acceptStylusHandwritingDelegation() {
+        calls += "acceptStylusHandwritingDelegation"
+    }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDelegateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDelegateTest.kt
new file mode 100644
index 0000000..a40a175
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDelegateTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.handwriting.handwritingDelegate
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class HandwritingDelegateTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val immRule = ComposeInputMethodManagerTestRule()
+
+    @Before
+    fun setup() {
+        // Test is only meaningful when stylus handwriting is supported.
+        Assume.assumeTrue(isStylusHandwritingSupported)
+    }
+
+    @Test
+    fun delegate_gainFocus_acceptsDelegation() {
+        val imm = FakeInputMethodManager()
+        immRule.setFactory { imm }
+
+        val tag = "delegate"
+        InputMethodInterceptor(rule).setTextFieldTestContent {
+            val state = remember { TextFieldState() }
+            BasicTextField(
+                state = state,
+                modifier = Modifier.fillMaxSize().handwritingDelegate().testTag(tag)
+            )
+        }
+
+        rule.onNodeWithTag(tag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+
+        rule.runOnIdle {
+            imm.expectCall("acceptStylusHandwritingDelegation")
+            imm.expectNoMoreCalls()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDelegatorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDelegatorTest.kt
new file mode 100644
index 0000000..b8a7814
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDelegatorTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.input
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.text.handwriting.handwritingDelegator
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
+import androidx.compose.foundation.text.performStylusClick
+import androidx.compose.foundation.text.performStylusHandwriting
+import androidx.compose.foundation.text.performStylusLongClick
+import androidx.compose.foundation.text.performStylusLongPressAndDrag
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+internal class HandwritingDelegatorTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val immRule = ComposeInputMethodManagerTestRule()
+
+    private val imm = FakeInputMethodManager()
+
+    private val tag = "delegator"
+
+    private var callbackCount = 0;
+
+    @Before
+    fun setup() {
+        // Test is only meaningful when stylus handwriting is supported.
+        Assume.assumeTrue(isStylusHandwritingSupported)
+
+        immRule.setFactory { imm }
+
+        callbackCount = 0
+
+        rule.setContent {
+            Spacer(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .handwritingDelegator { callbackCount++ }
+                    .testTag(tag)
+            )
+        }
+    }
+
+    @Test
+    fun delegator_handwriting_preparesDelegation() {
+        rule.onNodeWithTag(tag).performStylusHandwriting()
+
+        assertHandwritingDelegationPrepared()
+    }
+
+    @Test
+    fun delegator_click_notPreparesDelegation() {
+        rule.onNodeWithTag(tag).performStylusClick()
+
+        assertHandwritingDelegationNotPrepared()
+    }
+
+    @Test
+    fun delegator_longClick_notPreparesDelegation() {
+        rule.onNodeWithTag(tag).performStylusLongClick()
+
+        assertHandwritingDelegationNotPrepared()
+    }
+
+    @Test
+    fun delegator_longPressAndDrag_notPreparesDelegation() {
+        rule.onNodeWithTag(tag).performStylusLongPressAndDrag()
+
+        assertHandwritingDelegationNotPrepared()
+    }
+
+    private fun assertHandwritingDelegationPrepared() {
+        rule.runOnIdle {
+            assertThat(callbackCount).isEqualTo(1)
+            imm.expectCall("prepareStylusHandwritingDelegation")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    private fun assertHandwritingDelegationNotPrepared() {
+        rule.runOnIdle {
+            assertThat(callbackCount).isEqualTo(0)
+            imm.expectNoMoreCalls()
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt
index bcadfb2..2fe532a 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/RememberTextFieldStateTest.kt
@@ -50,7 +50,7 @@
 
         rule.runOnIdle {
             assertThat(state.text.toString()).isEqualTo("hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(TextRange(2))
         }
     }
 
@@ -78,7 +78,7 @@
 
         rule.runOnIdle {
             assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-            assertThat(restoredState.text.selection).isEqualTo(TextRange(0, 12))
+            assertThat(restoredState.selection).isEqualTo(TextRange(0, 12))
         }
     }
 
@@ -109,7 +109,7 @@
 
         rule.runOnIdle {
             assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-            assertThat(restoredState.text.selection).isEqualTo(TextRange(0, 12))
+            assertThat(restoredState.selection).isEqualTo(TextRange(0, 12))
         }
     }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
index 36aa45b..bc7f1d17 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCodepointTransformationTest.kt
@@ -437,7 +437,7 @@
         listOf(0, 1, 3, 4).forEachIndexed { i, expectedCursor ->
             rule.runOnIdle {
                 assertWithMessage("After pressing right arrow $i times")
-                    .that(state.text.selection).isEqualTo(TextRange(expectedCursor))
+                    .that(state.selection).isEqualTo(TextRange(expectedCursor))
             }
             rule.onNodeWithTag(Tag).performKeyInput {
                 pressKey(Key.DirectionRight)
@@ -477,7 +477,7 @@
         ).forEachIndexed { i, expectedSelection ->
             rule.runOnIdle {
                 assertWithMessage("After pressing shift+right arrow $i times")
-                    .that(state.text.selection).isEqualTo(expectedSelection)
+                    .that(state.selection).isEqualTo(expectedSelection)
             }
             rule.onNodeWithTag(Tag).performKeyInput {
                 withKeyDown(Key.ShiftLeft) {
@@ -519,7 +519,7 @@
         ).forEachIndexed { i, expectedSelection ->
             rule.runOnIdle {
                 assertWithMessage("After pressing shift+left arrow $i times")
-                    .that(state.text.selection).isEqualTo(expectedSelection)
+                    .that(state.selection).isEqualTo(expectedSelection)
             }
             rule.onNodeWithTag(Tag).performKeyInput {
                 withKeyDown(Key.ShiftLeft) {
@@ -815,7 +815,7 @@
                     .that(performSelectionOnVisualText(write)).isTrue()
                 rule.runOnIdle {
                     assertWithMessage("Visual selection $write to mapped")
-                        .that(text.selection).isEqualTo(expected)
+                        .that(selection).isEqualTo(expected)
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
index e062e6e..a982889 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldCursorTest.kt
@@ -125,7 +125,7 @@
     private var textLayoutResult: (() -> TextLayoutResult?)? = null
     private val cursorRect: Rect
         // assume selection is collapsed
-        get() = textLayoutResult?.invoke()?.getCursorRect(state.text.selection.start)
+        get() = textLayoutResult?.invoke()?.getCursorRect(state.selection.start)
             ?: Rect.Zero
 
     private val cursorSize: DpSize by lazy {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
index 3c3f61d..544d882 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldDragAndDropTest.kt
@@ -21,12 +21,11 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.TestActivity
 import androidx.compose.foundation.content.DragAndDropScope
-import androidx.compose.foundation.content.MediaType
 import androidx.compose.foundation.content.ReceiveContentListener
 import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.consumeEach
+import androidx.compose.foundation.content.consume
+import androidx.compose.foundation.content.contentReceiver
 import androidx.compose.foundation.content.createClipData
-import androidx.compose.foundation.content.receiveContent
 import androidx.compose.foundation.content.testDragAndDrop
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.collectIsHoveredAsState
@@ -78,22 +77,9 @@
     @Test
     fun nonTextContent_isNotAccepted() {
         rule.setContentAndTestDragAndDrop {
-            val startSelection = state.text.selection
+            val startSelection = state.selection
             drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
-            assertThat(state.text.selection).isEqualTo(startSelection)
-        }
-    }
-
-    @Test
-    fun nonTextContent_isAcceptedIfReceiveContentDefined() {
-        rule.setContentAndTestDragAndDrop(
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
-                null
-            }
-        ) {
-            val accepted = drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
-            assertThat(accepted).isTrue()
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(startSelection)
         }
     }
 
@@ -101,7 +87,7 @@
     fun textContent_isAccepted() {
         rule.setContentAndTestDragAndDrop {
             drag(Offset(fontSize.toPx() * 2, 10f), "hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(TextRange(2))
         }
     }
 
@@ -109,27 +95,27 @@
     fun draggingText_updatesSelection() {
         rule.setContentAndTestDragAndDrop {
             drag(Offset(fontSize.toPx() * 1, 10f), "hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(1))
+            assertThat(state.selection).isEqualTo(TextRange(1))
             drag(Offset(fontSize.toPx() * 2, 10f), "hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(TextRange(2))
             drag(Offset(fontSize.toPx() * 3, 10f), "hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(3))
+            assertThat(state.selection).isEqualTo(TextRange(3))
         }
     }
 
     @Test
     fun draggingNonText_updatesSelection_withReceiveContent() {
         rule.setContentAndTestDragAndDrop(
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+            modifier = Modifier.contentReceiver {
                 null
             }
         ) {
             drag(Offset(fontSize.toPx() * 1, 10f), defaultUri)
-            assertThat(state.text.selection).isEqualTo(TextRange(1))
+            assertThat(state.selection).isEqualTo(TextRange(1))
             drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(TextRange(2))
             drag(Offset(fontSize.toPx() * 3, 10f), defaultUri)
-            assertThat(state.text.selection).isEqualTo(TextRange(3))
+            assertThat(state.selection).isEqualTo(TextRange(3))
         }
     }
 
@@ -140,9 +126,9 @@
             modifier = Modifier.width(300.dp)
         ) {
             drag(Offset.Zero, "hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
             drag(Offset(295.dp.toPx(), 10f), "hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(4))
+            assertThat(state.selection).isEqualTo(TextRange(4))
         }
     }
 
@@ -225,7 +211,7 @@
                 Box(
                     modifier = Modifier
                         .size(200.dp)
-                        .receiveContent(emptySet(), object : ReceiveContentListener {
+                        .contentReceiver(object : ReceiveContentListener {
                             override fun onDragStart() {
                                 calls += "start"
                             }
@@ -298,7 +284,7 @@
                 Box(
                     modifier = Modifier
                         .size(200.dp)
-                        .receiveContent(emptySet(), object : ReceiveContentListener {
+                        .contentReceiver(object : ReceiveContentListener {
                             override fun onDragStart() {
                                 calls += "start"
                             }
@@ -354,7 +340,7 @@
                 " Awesome"
             )
             drop()
-            assertThat(state.text.selection).isEqualTo(TextRange("Hello Awesome".length))
+            assertThat(state.selection).isEqualTo(TextRange("Hello Awesome".length))
             assertThat(state.text.toString()).isEqualTo("Hello Awesome World!")
         }
     }
@@ -364,9 +350,9 @@
         lateinit var receivedContent: TransferableContent
         rule.setContentAndTestDragAndDrop(
             "Hello World!",
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+            modifier = Modifier.contentReceiver {
                 receivedContent = it
-                receivedContent.consumeEach {
+                receivedContent.consume {
                     // do not consume text
                     it.uri != null
                 }
@@ -378,7 +364,7 @@
             }
             drag(Offset(fontSize.toPx() * 5, 10f), clipData)
             drop()
-            assertThat(state.text.selection).isEqualTo(TextRange("Hello Awesome".length))
+            assertThat(state.selection).isEqualTo(TextRange("Hello Awesome".length))
             assertThat(state.text.toString()).isEqualTo("Hello Awesome World!")
             assertThat(receivedContent.clipEntry.clipData.itemCount).isEqualTo(2)
             assertThat(receivedContent.clipEntry.firstUriOrNull()).isEqualTo(defaultUri)
@@ -390,7 +376,7 @@
         lateinit var receivedContent: TransferableContent
         rule.setContentAndTestDragAndDrop(
             "Hello World!",
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+            modifier = Modifier.contentReceiver {
                 receivedContent = it
                 // consume everything
                 null
@@ -402,7 +388,7 @@
             }
             drag(Offset(fontSize.toPx() * 5, 10f), clipData)
             drop()
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
             assertThat(state.text.toString()).isEqualTo("Hello World!")
             assertThat(receivedContent.clipEntry.clipData.itemCount).isEqualTo(2)
             assertThat(receivedContent.clipEntry.firstUriOrNull()).isEqualTo(defaultUri)
@@ -414,7 +400,7 @@
         lateinit var receivedContent: TransferableContent
         rule.setContentAndTestDragAndDrop(
             "Hello World!",
-            modifier = Modifier.receiveContent(setOf(MediaType("video/*"))) {
+            modifier = Modifier.contentReceiver {
                 receivedContent = it
                 val uri = receivedContent.clipEntry.firstUriOrNull()
                 // replace the content
@@ -436,7 +422,7 @@
     fun droppedItem_requestsPermission_ifReceiveContent() {
         rule.setContentAndTestDragAndDrop(
             "Hello World!",
-            modifier = Modifier.receiveContent(emptySet()) { null }
+            modifier = Modifier.contentReceiver { null }
         ) {
             drag(Offset(fontSize.toPx() * 5, 10f), defaultUri)
             drop()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
index bafdbfa..2e16280 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldKeyEventTest.kt
@@ -689,7 +689,7 @@
 
         fun expectedSelection(selection: TextRange) {
             rule.runOnIdle {
-                assertThat(state.text.selection).isEqualTo(selection)
+                assertThat(state.selection).isEqualTo(selection)
             }
         }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
index 599b9bf..7787cf8 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationGesturesIntegrationTest.kt
@@ -77,7 +77,7 @@
             click(center + Offset(1f, 0f))
         }
         rule.runOnIdle {
-            assertThat(text.text.selection).isEqualTo(TextRange(2))
+            assertThat(text.selection).isEqualTo(TextRange(2))
         }
 
         rule.onNodeWithTag(Tag).performTouchInput {
@@ -87,7 +87,7 @@
             click(center + Offset(-1f, 0f))
         }
         rule.runOnIdle {
-            assertThat(text.text.selection).isEqualTo(TextRange(1))
+            assertThat(text.selection).isEqualTo(TextRange(1))
         }
     }
 
@@ -124,7 +124,7 @@
             click(topRight)
         }
         rule.runOnIdle {
-            assertThat(text.text.selection).isEqualTo(TextRange(indexOfA))
+            assertThat(text.selection).isEqualTo(TextRange(indexOfA))
         }
         assertCursor(indexOfA)
 
@@ -134,7 +134,7 @@
             click(bottomLeft)
         }
         rule.runOnIdle {
-            assertThat(text.text.selection)
+            assertThat(text.selection)
                 .isEqualTo(TextRange(indexOfA + 1))
         }
         assertCursor(indexOfA + replacement.length)
@@ -164,7 +164,7 @@
             click(center + Offset(1f, 0f))
         }
         rule.runOnIdle {
-            assertThat(text.text.selection).isEqualTo(TextRange(1))
+            assertThat(text.selection).isEqualTo(TextRange(1))
         }
         assertCursor(5)
 
@@ -175,7 +175,7 @@
             click(center + Offset(-1f, 0f))
         }
         rule.runOnIdle {
-            assertThat(text.text.selection).isEqualTo(TextRange(1))
+            assertThat(text.selection).isEqualTo(TextRange(1))
         }
         assertCursor(1)
     }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
index 768fd70..c10b343 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldOutputTransformationHardwareKeysIntegrationTest.kt
@@ -78,13 +78,13 @@
         }
         rule.onNodeWithTag(Tag).requestFocus()
 
-        assertThat(text.text.selection).isEqualTo(TextRange(0))
+        assertThat(text.selection).isEqualTo(TextRange(0))
         pressKey(Key.DirectionRight)
-        assertThat(text.text.selection).isEqualTo(TextRange(1))
+        assertThat(text.selection).isEqualTo(TextRange(1))
         pressKey(Key.DirectionRight)
-        assertThat(text.text.selection).isEqualTo(TextRange(3))
+        assertThat(text.selection).isEqualTo(TextRange(3))
         pressKey(Key.DirectionRight)
-        assertThat(text.text.selection).isEqualTo(TextRange(4))
+        assertThat(text.selection).isEqualTo(TextRange(4))
     }
 
     @Test
@@ -100,13 +100,13 @@
         }
         rule.onNodeWithTag(Tag).requestFocus()
 
-        assertThat(text.text.selection).isEqualTo(TextRange(4))
+        assertThat(text.selection).isEqualTo(TextRange(4))
         pressKey(Key.DirectionLeft)
-        assertThat(text.text.selection).isEqualTo(TextRange(3))
+        assertThat(text.selection).isEqualTo(TextRange(3))
         pressKey(Key.DirectionLeft)
-        assertThat(text.text.selection).isEqualTo(TextRange(1))
+        assertThat(text.selection).isEqualTo(TextRange(1))
         pressKey(Key.DirectionLeft)
-        assertThat(text.text.selection).isEqualTo(TextRange(0))
+        assertThat(text.selection).isEqualTo(TextRange(0))
     }
 
     @Test
@@ -181,13 +181,13 @@
         }
         rule.onNodeWithTag(Tag).requestFocus()
 
-        assertThat(text.text.selection).isEqualTo(TextRange(0))
+        assertThat(text.selection).isEqualTo(TextRange(0))
         pressKey(Key.DirectionRight)
-        assertThat(text.text.selection).isEqualTo(TextRange(1))
+        assertThat(text.selection).isEqualTo(TextRange(1))
         pressKey(Key.DirectionRight)
-        assertThat(text.text.selection).isEqualTo(TextRange(1))
+        assertThat(text.selection).isEqualTo(TextRange(1))
         pressKey(Key.DirectionRight)
-        assertThat(text.text.selection).isEqualTo(TextRange(2))
+        assertThat(text.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -203,13 +203,13 @@
         }
         rule.onNodeWithTag(Tag).requestFocus()
 
-        assertThat(text.text.selection).isEqualTo(TextRange(2))
+        assertThat(text.selection).isEqualTo(TextRange(2))
         pressKey(Key.DirectionLeft)
-        assertThat(text.text.selection).isEqualTo(TextRange(1))
+        assertThat(text.selection).isEqualTo(TextRange(1))
         pressKey(Key.DirectionLeft)
-        assertThat(text.text.selection).isEqualTo(TextRange(1))
+        assertThat(text.selection).isEqualTo(TextRange(1))
         pressKey(Key.DirectionLeft)
-        assertThat(text.text.selection).isEqualTo(TextRange(0))
+        assertThat(text.selection).isEqualTo(TextRange(0))
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
index 695108a..4ffd505 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldReceiveContentTest.kt
@@ -23,21 +23,16 @@
 import android.view.inputmethod.InputConnection
 import android.view.inputmethod.InputContentInfo
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
 import androidx.compose.foundation.content.TransferableContent
 import androidx.compose.foundation.content.assertClipData
-import androidx.compose.foundation.content.consumeEach
+import androidx.compose.foundation.content.consume
+import androidx.compose.foundation.content.contentReceiver
 import androidx.compose.foundation.content.createClipData
-import androidx.compose.foundation.content.receiveContent
-import androidx.compose.foundation.draganddrop.dragAndDropTarget
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.input.internal.selection.FakeClipboardManager
 import androidx.compose.foundation.text.selection.FakeTextToolbar
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draganddrop.DragAndDropEvent
-import androidx.compose.ui.draganddrop.DragAndDropTarget
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalTextToolbar
 import androidx.compose.ui.platform.testTag
@@ -124,124 +119,13 @@
                 state = rememberTextFieldState(),
                 modifier = Modifier
                     .testTag(tag)
-                    .receiveContent(setOf(MediaType.Image)) { null }
+                    .contentReceiver { null }
             )
         }
         rule.onNodeWithTag(tag).requestFocus()
         inputMethodInterceptor.withEditorInfo {
             val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(arrayOf(MediaType.Image.representation))
-        }
-    }
-
-    @Test
-    fun singleReceiveContent_duplicateMediaTypes_appliedUniquely() {
-        inputMethodInterceptor.setContent {
-            BasicTextField(
-                state = rememberTextFieldState(),
-                modifier = Modifier
-                    .testTag(tag)
-                    .receiveContent(
-                        setOf(
-                            MediaType.Image,
-                            MediaType.PlainText,
-                            MediaType.Image,
-                            MediaType.HtmlText
-                        )
-                    ) { null }
-            )
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.PlainText.representation,
-                    MediaType.HtmlText.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_mergesMediaTypes() {
-        inputMethodInterceptor.setContent {
-            Box(modifier = Modifier.receiveContent(setOf(MediaType.Text)) { null }) {
-                BasicTextField(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) { null }
-                )
-            }
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.Text.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_mergesMediaTypes_uniquely() {
-        inputMethodInterceptor.setContent {
-            Box(modifier = Modifier.receiveContent(
-                setOf(MediaType.Text, MediaType.Image)
-            ) { null }) {
-                BasicTextField(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) { null }
-                )
-            }
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.Text.representation
-                )
-            )
-        }
-    }
-
-    @Test
-    fun multiReceiveContent_mergesMediaTypes_includingAnotherTraversableNode() {
-        inputMethodInterceptor.setContent {
-            Box(modifier = Modifier
-                .receiveContent(setOf(MediaType.Text)) { null }
-                .dragAndDropTarget({ true }, object : DragAndDropTarget {
-                    override fun onDrop(event: DragAndDropEvent): Boolean {
-                        return false
-                    }
-                })
-            ) {
-                BasicTextField(
-                    state = rememberTextFieldState(),
-                    modifier = Modifier
-                        .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) { null }
-                )
-            }
-        }
-        rule.onNodeWithTag(tag).requestFocus()
-        inputMethodInterceptor.withEditorInfo {
-            val contentMimeTypes = EditorInfoCompat.getContentMimeTypes(this)
-            assertThat(contentMimeTypes).isEqualTo(
-                arrayOf(
-                    MediaType.Image.representation,
-                    MediaType.Text.representation
-                )
-            )
+            assertThat(contentMimeTypes).isEqualTo(arrayOf("*/*"))
         }
     }
 
@@ -253,7 +137,7 @@
                 state = rememberTextFieldState(),
                 modifier = Modifier
                     .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
+                    .contentReceiver {
                         transferableContent = it
                         null
                     }
@@ -299,7 +183,7 @@
                 state = rememberTextFieldState(),
                 modifier = Modifier
                     .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
+                    .contentReceiver {
                         transferableContent = it
                         null
                     }
@@ -338,11 +222,11 @@
                 state = rememberTextFieldState(),
                 modifier = Modifier
                     .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
+                    .contentReceiver {
                         parentTransferableContent = it
                         null
                     }
-                    .receiveContent(setOf(MediaType.All)) {
+                    .contentReceiver {
                         childTransferableContent = it
                         it
                     }
@@ -383,11 +267,11 @@
                 state = rememberTextFieldState(),
                 modifier = Modifier
                     .testTag(tag)
-                    .receiveContent(setOf(MediaType.All)) {
+                    .contentReceiver {
                         parentTransferableContent = it
                         null
                     }
-                    .receiveContent(setOf(MediaType.All)) {
+                    .contentReceiver {
                         childTransferableContent = it
                         null
                     }
@@ -422,7 +306,7 @@
                     state = rememberTextFieldState(),
                     modifier = Modifier
                         .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) {
+                        .contentReceiver {
                             transferableContent = it
                             null
                         }
@@ -455,8 +339,8 @@
                     state = state,
                     modifier = Modifier
                         .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image, MediaType.Text)) {
-                            it.consumeEach { item ->
+                        .contentReceiver {
+                            it.consume { item ->
                                 // only consume if there's no text
                                 item.text == null
                             }
@@ -494,21 +378,21 @@
                     state = state,
                     modifier = Modifier
                         .testTag(tag)
-                        .receiveContent(setOf(MediaType.Text)) {
+                        .contentReceiver {
                             transferableContent1 = it
-                            it.consumeEach {
+                            it.consume {
                                 it.text.contains("a")
                             }
                         }
-                        .receiveContent(setOf(MediaType.Text)) {
+                        .contentReceiver {
                             transferableContent2 = it
-                            it.consumeEach {
+                            it.consume {
                                 it.text.contains("b")
                             }
                         }
-                        .receiveContent(setOf(MediaType.Text)) {
+                        .contentReceiver {
                             transferableContent3 = it
-                            it.consumeEach {
+                            it.consume {
                                 it.text.contains("c")
                             }
                         }
@@ -548,7 +432,7 @@
                     state = rememberTextFieldState(),
                     modifier = Modifier
                         .testTag(tag)
-                        .receiveContent(setOf(MediaType.Image)) {
+                        .contentReceiver {
                             transferableContent = it
                             null
                         }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
index 0443ce1..6a566ee 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
@@ -458,7 +458,7 @@
 
         rule.runOnIdle {
             assertThat(scrollState.value).isEqualTo(scrollState.maxValue)
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
         }
     }
 
@@ -591,9 +591,9 @@
         rule.onNodeWithTag("field").assertTextEquals("aaaaaaaaaa")
         rule.waitUntil(
             "scrollState.value (${scrollState.value}) == 0 && " +
-                "state.text.selection (${state.text.selection}) == TextRange(0)"
+                "state.selection (${state.selection}) == TextRange(0)"
         ) {
-            scrollState.value == 0 && state.text.selection == TextRange(0)
+            scrollState.value == 0 && state.selection == TextRange(0)
         }
     }
 
@@ -755,6 +755,32 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun textFieldDoesNotCrash_inVerticallyScrollableContainer_whenFieldShrinks() {
+        // Start as a single line, then enter '\n' to grow to 2 lines.
+        val state = TextFieldState("\n\n\n\n\n\n\n\n\n")
+        rule.setContent {
+            BasicTextField(
+                state,
+                // The field should never scroll internally.
+                lineLimits = MultiLine(maxHeightInLines = Int.MAX_VALUE),
+                modifier = Modifier
+                    .testTag("field")
+                    .border(1.dp, Color.Blue)
+            )
+        }
+        rule.onNodeWithTag("field").requestFocus()
+
+        // remove lines in quick succession and expect to not crash
+        repeat(state.text.length) {
+            rule.onNodeWithTag("field").performKeyInput { pressKey(Key.Backspace) }
+        }
+
+        rule.waitForIdle()
+        assertThat(state.text.toString()).isEmpty()
+    }
+
     private fun ComposeContentTestRule.setupHorizontallyScrollableContent(
         state: TextFieldState,
         scrollState: ScrollState,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
index 73e32c2..5b2a4c6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
@@ -94,6 +94,8 @@
 
         override fun sendKeyEvent(event: KeyEvent) {}
         override fun startStylusHandwriting() {}
+        override fun prepareStylusHandwritingDelegation() {}
+        override fun acceptStylusHandwritingDelegation() {}
     }
 
     private lateinit var inputConnection: InputConnection
@@ -125,8 +127,8 @@
         // Immediate update.
         val expectedInfo = builder.build(
             text = textFieldState.text,
-            selection = textFieldState.text.selection,
-            composition = textFieldState.text.composition,
+            selection = textFieldState.selection,
+            composition = textFieldState.composition,
             textLayoutResult = layoutState.layoutResult!!,
             matrix = getAndroidMatrix(windowOffset),
             innerTextFieldBounds = Rect.Zero,
@@ -186,8 +188,8 @@
         // Monitoring update.
         val expectedInfo = builder.build(
             text = textFieldState.text,
-            selection = textFieldState.text.selection,
-            composition = textFieldState.text.composition,
+            selection = textFieldState.selection,
+            composition = textFieldState.composition,
             textLayoutResult = layoutState.layoutResult!!,
             matrix = getAndroidMatrix(Offset(67f, 89f)),
             innerTextFieldBounds = Rect.Zero,
@@ -208,8 +210,8 @@
         // Immediate update.
         val expectedInfo = builder.build(
             text = textFieldState.text,
-            selection = textFieldState.text.selection,
-            composition = textFieldState.text.composition,
+            selection = textFieldState.selection,
+            composition = textFieldState.composition,
             textLayoutResult = layoutState.layoutResult!!,
             matrix = getAndroidMatrix(windowOffset),
             innerTextFieldBounds = Rect.Zero,
@@ -225,8 +227,8 @@
         // Monitoring update.
         val expectedInfo2 = builder.build(
             text = textFieldState.text,
-            selection = textFieldState.text.selection,
-            composition = textFieldState.text.composition,
+            selection = textFieldState.selection,
+            composition = textFieldState.composition,
             textLayoutResult = layoutState.layoutResult!!,
             matrix = getAndroidMatrix(Offset(67f, 89f)),
             innerTextFieldBounds = Rect.Zero,
@@ -247,8 +249,8 @@
         // Immediate update.
         val expectedInfo = builder.build(
             text = textFieldState.text,
-            selection = textFieldState.text.selection,
-            composition = textFieldState.text.composition,
+            selection = textFieldState.selection,
+            composition = textFieldState.composition,
             textLayoutResult = layoutState.layoutResult!!,
             matrix = getAndroidMatrix(windowOffset),
             innerTextFieldBounds = Rect.Zero,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
index d848cac..687eb4c 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldClickToMoveCursorTest.kt
@@ -82,15 +82,15 @@
 
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(center) }
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
             performTouchInput { click(Offset(left + 1f, top + 1f)) } // topLeft
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
             performTouchInput { click(Offset(right - 1f, top + 1f)) } // topRight
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
             performTouchInput { click(Offset(left + 1f, bottom - 1f)) } // bottomLeft
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
             performTouchInput { click(Offset(right - 1f, bottom - 1f)) } // bottomRight
-            assertThat(state.text.selection).isEqualTo(TextRange(0))
+            assertThat(state.selection).isEqualTo(TextRange(0))
         }
     }
 
@@ -111,7 +111,7 @@
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(Offset((fontSize * 2).toPx(), height / 2f)) }
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -133,7 +133,7 @@
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(Offset(right - (fontSize * 2).toPx(), height / 2f)) }
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -155,7 +155,7 @@
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(Offset(right - (fontSize * 2).toPx(), height / 2f)) }
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
     }
 
     @Test
@@ -175,7 +175,7 @@
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(Offset((fontSize * 2).toPx(), height / 2f)) }
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
     }
 
     @Test
@@ -195,7 +195,7 @@
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(Offset((fontSize * 4).toPx(), height / 2f)) }
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
     }
 
     @Test
@@ -217,7 +217,7 @@
         with(rule.onNodeWithTag(TAG)) {
             performTouchInput { click(Offset(fontSize.toPx(), height / 2f)) }
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
     }
 
     @Test
@@ -242,7 +242,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(6))
+            assertThat(state.selection).isEqualTo(TextRange(6))
             assertThat(scrollState.value).isGreaterThan(0)
         }
     }
@@ -268,7 +268,7 @@
             performTouchInput { click(Offset(fontSize.toPx(), bottom - 1f)) }
         }
         rule.waitForIdle()
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
         assertThat(scrollState.value).isGreaterThan(0)
     }
 
@@ -300,6 +300,6 @@
             performTouchInput { click(Offset(2 * fontSize.toPx(), centerY)) }
         }
         rule.waitForIdle()
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
index 728e321..8b210f4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldCursorHandleTest.kt
@@ -102,7 +102,7 @@
             click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
 
         rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
             (2 * fontSize.value).dp + cursorWidth / 2,
@@ -155,7 +155,7 @@
             click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4))
+        assertThat(state.selection).isEqualTo(TextRange(4))
     }
 
     @Test
@@ -177,7 +177,7 @@
             click(Offset(fontSize.toPx() * 8, fontSize.toPx() / 2))
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
 
         rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
             5 * fontSizeDp + cursorWidth / 2,
@@ -206,7 +206,7 @@
             click(Offset(fontSize.toPx() * 2, fontSize.toPx() / 2))
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
 
         rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
             (2 * fontSize.value).dp + cursorWidth / 2,
@@ -233,7 +233,7 @@
             click(Offset(fontSize.toPx() * 8, fontSize.toPx() / 2))
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
 
         rule.onNode(isSelectionHandle(Handle.Cursor)).assertHandlePositionMatches(
             (5 * fontSize.value).dp + cursorWidth / 2,
@@ -604,7 +604,7 @@
         swipeToRight(fontSizePx * 5)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange.Zero)
+        assertThat(state.selection).isEqualTo(TextRange.Zero)
     }
 
     // region ltr drag tests
@@ -629,7 +629,7 @@
         swipeToRight(fontSizePx)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
     }
 
     @Test
@@ -653,7 +653,7 @@
         swipeToLeft(fontSizePx)
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(TextRange(2))
         }
     }
 
@@ -678,7 +678,7 @@
         swipeToRight(getTextFieldWidth() * 2)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
     }
 
     @Test
@@ -702,7 +702,7 @@
         swipeToLeft(getTextFieldWidth() * 2)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange.Zero)
+        assertThat(state.selection).isEqualTo(TextRange.Zero)
     }
 
     @Test
@@ -730,7 +730,7 @@
         swipeToRight(getTextFieldWidth() * 3)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(state.text.length))
+        assertThat(state.selection).isEqualTo(TextRange(state.text.length))
     }
 
     @Test
@@ -757,12 +757,12 @@
 
         swipeToRight(fontSizePx * 12, durationMillis = 1)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(12))
+            assertThat(state.selection).isEqualTo(TextRange(12))
         }
 
         swipeToRight(fontSizePx * 2, durationMillis = 1)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(14))
+            assertThat(state.selection).isEqualTo(TextRange(14))
         }
     }
 
@@ -792,7 +792,7 @@
         swipeToRight(fontSizePx)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -818,7 +818,7 @@
         swipeToLeft(fontSizePx)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
     }
 
     @Test
@@ -844,7 +844,7 @@
         swipeToRight(getTextFieldWidth() * 2)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange.Zero)
+        assertThat(state.selection).isEqualTo(TextRange.Zero)
     }
 
     @Test
@@ -870,7 +870,7 @@
         swipeToLeft(getTextFieldWidth() * 2)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(state.text.length))
+        assertThat(state.selection).isEqualTo(TextRange(state.text.length))
     }
 
     @Test
@@ -902,7 +902,7 @@
         swipeToLeft(getTextFieldWidth() * 3)
         rule.waitForIdle()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(state.text.length))
+        assertThat(state.selection).isEqualTo(TextRange(state.text.length))
     }
 
     @Test
@@ -936,12 +936,12 @@
 
         swipeToLeft(fontSizePx * 12, durationMillis = 1)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(12))
+            assertThat(state.selection).isEqualTo(TextRange(12))
         }
 
         swipeToLeft(fontSizePx * 2, durationMillis = 1)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(14))
+            assertThat(state.selection).isEqualTo(TextRange(14))
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
index 5d0f3ce..509edf6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldLongPressTest.kt
@@ -129,7 +129,7 @@
         }
 
         rule.onNode(isSelectionHandle(Handle.Cursor)).assertIsDisplayed()
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
     }
 
     @Test
@@ -183,7 +183,7 @@
 
         rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
         rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 7))
+        assertThat(state.selection).isEqualTo(TextRange(4, 7))
     }
 
     @Test
@@ -203,8 +203,8 @@
 
         rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
         rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selection).isNotEqualTo(TextRange(7, 8))
-        assertThat(state.text.selection.collapsed).isFalse()
+        assertThat(state.selection).isNotEqualTo(TextRange(7, 8))
+        assertThat(state.selection.collapsed).isFalse()
     }
 
     @Test
@@ -232,7 +232,7 @@
 
         rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
         rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selection).isEqualTo(TextRange(20, 23))
+        assertThat(state.selection).isEqualTo(TextRange(20, 23))
     }
 
     @Test
@@ -262,7 +262,7 @@
 
         rule.onNode(isSelectionHandle(Handle.SelectionStart)).assertIsDisplayed()
         rule.onNode(isSelectionHandle(Handle.SelectionEnd)).assertIsDisplayed()
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 7))
+        assertThat(state.selection).isEqualTo(TextRange(4, 7))
     }
 
     @Test
@@ -282,7 +282,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 11))
+        assertThat(state.selection).isEqualTo(TextRange(4, 11))
     }
 
     @Test
@@ -302,7 +302,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 7))
+        assertThat(state.selection).isEqualTo(TextRange(0, 7))
     }
 
     @Test
@@ -322,7 +322,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 15))
+        assertThat(state.selection).isEqualTo(TextRange(4, 15))
     }
 
     @Test
@@ -342,7 +342,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 15))
+        assertThat(state.selection).isEqualTo(TextRange(4, 15))
     }
 
     @Test
@@ -364,7 +364,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 7))
+        assertThat(state.selection).isEqualTo(TextRange(4, 7))
     }
 
     //region RTL
@@ -386,7 +386,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 7))
+        assertThat(state.selection).isEqualTo(TextRange(0, 7))
     }
 
     @Test
@@ -406,7 +406,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 11))
+        assertThat(state.selection).isEqualTo(TextRange(4, 11))
     }
 
     @Test
@@ -426,7 +426,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 11))
+        assertThat(state.selection).isEqualTo(TextRange(0, 11))
     }
 
     @Test
@@ -446,7 +446,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 11))
+        assertThat(state.selection).isEqualTo(TextRange(0, 11))
     }
 
     @Test
@@ -470,7 +470,7 @@
             up()
         }
 
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 7))
+        assertThat(state.selection).isEqualTo(TextRange(4, 7))
     }
 
     @Test
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
index 43f45e8..466b0a6 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionHandlesTest.kt
@@ -335,13 +335,13 @@
         rule.onNodeWithTag(TAG).performTouchInput { swipeLeft() }
         assertHandlesNotExist()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
 
         rule.onNodeWithTag(TAG).performTouchInput { swipeRight() }
         assertHandlesDisplayed()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
     }
 
@@ -368,7 +368,7 @@
         }
         assertHandlesNotExist()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
 
         rule.onNodeWithTag(TAG).performTouchInput {
@@ -376,7 +376,7 @@
         }
         assertHandlesDisplayed()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
     }
 
@@ -412,7 +412,7 @@
         }
         assertHandlesNotExist()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
 
         rule.onNodeWithTag(containerTag).performTouchInput {
@@ -420,7 +420,7 @@
         }
         assertHandlesDisplayed()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
     }
 
@@ -456,7 +456,7 @@
         }
         assertHandlesNotExist()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
 
         rule.onNodeWithTag(containerTag).performTouchInput {
@@ -464,7 +464,7 @@
         }
         assertHandlesDisplayed()
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(1, 2))
+            assertThat(state.selection).isEqualTo(TextRange(1, 2))
         }
     }
 
@@ -485,7 +485,7 @@
 
         swipeToLeft(Handle.SelectionStart, fontSizePx * 4)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 7))
+            assertThat(state.selection).isEqualTo(TextRange(0, 7))
         }
     }
 
@@ -506,7 +506,7 @@
 
         swipeToRight(Handle.SelectionEnd, fontSizePx * 4)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(4, 11))
+            assertThat(state.selection).isEqualTo(TextRange(4, 11))
         }
     }
 
@@ -529,7 +529,7 @@
             doubleClick(Offset(fontSizePx * 5, fontSizePx / 2)) // middle word
         }
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(4, 7))
+            assertThat(state.selection).isEqualTo(TextRange(4, 7))
         }
     }
 
@@ -554,8 +554,8 @@
             doubleClick(Offset(fontSizePx * 3.5f, fontSizePx / 2))
         }
         rule.runOnIdle {
-            assertThat(state.text.selection).isNotEqualTo(TextRange(3, 4))
-            assertThat(state.text.selection.collapsed).isFalse()
+            assertThat(state.selection).isNotEqualTo(TextRange(3, 4))
+            assertThat(state.selection.collapsed).isFalse()
         }
     }
 
@@ -585,7 +585,7 @@
             swipeToLeft(Handle.SelectionStart, fontSizePx)
         }
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 80))
+            assertThat(state.selection).isEqualTo(TextRange(0, 80))
         }
     }
 
@@ -615,7 +615,7 @@
         // make sure that we also swipe to start on the first line
         swipeToLeft(Handle.SelectionStart, fontSizePx * 10)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 80))
+            assertThat(state.selection).isEqualTo(TextRange(0, 80))
         }
     }
 
@@ -640,7 +640,7 @@
             swipeToRight(Handle.SelectionEnd, fontSizePx)
         }
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 80))
+            assertThat(state.selection).isEqualTo(TextRange(0, 80))
         }
     }
 
@@ -669,7 +669,7 @@
             swipeToRight(Handle.SelectionEnd, layoutResult.size.width.toFloat())
         }
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 80))
+            assertThat(state.selection).isEqualTo(TextRange(0, 80))
         }
     }
 
@@ -691,7 +691,7 @@
         swipeToLeft(Handle.SelectionStart, fontSizePx * 2) // only move by 2 characters
         rule.runOnIdle {
             // selection extends by a word
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 7))
+            assertThat(state.selection).isEqualTo(TextRange(0, 7))
         }
     }
 
@@ -713,7 +713,7 @@
         swipeToRight(Handle.SelectionEnd, fontSizePx * 2) // only move by 2 characters
         rule.runOnIdle {
             // selection extends by a word
-            assertThat(state.text.selection).isEqualTo(TextRange(4, 11))
+            assertThat(state.selection).isEqualTo(TextRange(4, 11))
         }
     }
 
@@ -735,7 +735,7 @@
         swipeToRight(Handle.SelectionStart, fontSizePx) // only move by a single character
         rule.runOnIdle {
             // selection shrinks by a character
-            assertThat(state.text.selection).isEqualTo(TextRange(5, 7))
+            assertThat(state.selection).isEqualTo(TextRange(5, 7))
         }
     }
 
@@ -757,7 +757,7 @@
         swipeToLeft(Handle.SelectionEnd, fontSizePx) // only move by a single character
         rule.runOnIdle {
             // selection shrinks by a character
-            assertThat(state.text.selection).isEqualTo(TextRange(4, 6))
+            assertThat(state.selection).isEqualTo(TextRange(4, 6))
         }
     }
 
@@ -778,7 +778,7 @@
 
         swipeToRight(Handle.SelectionStart, fontSizePx * 7)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(11, 7))
+            assertThat(state.selection).isEqualTo(TextRange(11, 7))
         }
     }
 
@@ -799,7 +799,7 @@
 
         swipeToLeft(Handle.SelectionEnd, fontSizePx * 7)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(4, 0))
+            assertThat(state.selection).isEqualTo(TextRange(4, 0))
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
index 25801cc..c7d8baa 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionOnBackTest.kt
@@ -78,7 +78,7 @@
         textNode.performKeyInput { pressKey(Key.Back) }
         val expected = TextRange(3, 3)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(expected)
+            assertThat(state.selection).isEqualTo(expected)
         }
     }
 
@@ -106,7 +106,7 @@
         textNode.performKeyInput { pressKey(Key.Back) }
         val expected = TextRange(3, 3)
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(expected)
+            assertThat(state.selection).isEqualTo(expected)
             assertThat(backPressed).isEqualTo(0)
         }
     }
@@ -129,7 +129,7 @@
         // should have no effect
         textNode.performKeyInput { keyDown(Key.Back) }
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(expected)
+            assertThat(state.selection).isEqualTo(expected)
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
index 1e4b8ec..80dacad 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldTextToolbarTest.kt
@@ -17,9 +17,8 @@
 package androidx.compose.foundation.text.input.internal.selection
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
+import androidx.compose.foundation.content.contentReceiver
 import androidx.compose.foundation.content.createClipData
-import androidx.compose.foundation.content.receiveContent
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -48,7 +47,6 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.ClipEntry
-import androidx.compose.ui.platform.ClipMetadata
 import androidx.compose.ui.platform.ClipboardManager
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalTextToolbar
@@ -56,7 +54,6 @@
 import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.platform.toClipEntry
-import androidx.compose.ui.platform.toClipMetadata
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -193,7 +190,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(5, 2))
+            assertThat(state.selection).isEqualTo(TextRange(5, 2))
             assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
         }
 
@@ -206,7 +203,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
             assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
         }
     }
@@ -226,7 +223,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
             assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
         }
     }
@@ -243,7 +240,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
             assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Hidden)
         }
     }
@@ -262,7 +259,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+            assertThat(state.selection).isEqualTo(TextRange(0, 5))
             assertThat(textToolbar.status).isEqualTo(TextToolbarStatus.Shown)
         }
     }
@@ -494,7 +491,7 @@
 
         selectAllOption?.invoke()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(state.selection).isEqualTo(TextRange(0, 5))
         rule.runOnIdle {
             assertThat(selectAllOption).isNull()
         }
@@ -582,7 +579,7 @@
             toolbar = textToolbar,
             singleLine = true,
             clipboardManager = clipboardManager,
-            modifier = Modifier.receiveContent(setOf(MediaType.Image)) { null }
+            modifier = Modifier.contentReceiver { null }
         )
 
         rule.onNodeWithTag(TAG).performTouchInput { click() }
@@ -615,7 +612,7 @@
 
         rule.runOnIdle {
             assertThat(state.text.toString()).isEqualTo("Heworldllo")
-            assertThat(state.text.selection).isEqualTo(TextRange(7))
+            assertThat(state.selection).isEqualTo(TextRange(7))
         }
     }
 
@@ -688,7 +685,7 @@
 
         rule.runOnIdle {
             assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
         }
     }
 
@@ -715,7 +712,7 @@
         rule.runOnIdle {
             assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
             assertThat(state.text.toString()).isEqualTo("H World!")
-            assertThat(state.text.selection).isEqualTo(TextRange(1))
+            assertThat(state.selection).isEqualTo(TextRange(1))
         }
     }
 
@@ -747,7 +744,7 @@
         rule.runOnIdle {
             assertThat(clipboardManager.getText()?.toString()).isEqualTo("ello")
             assertThat(state.text.toString()).isEqualTo("Hello World!")
-            assertThat(state.text.selection).isEqualTo(TextRange(1))
+            assertThat(state.selection).isEqualTo(TextRange(1))
         }
     }
 
@@ -970,23 +967,7 @@
         }
     }
 
-    override fun getClipMetadata(): ClipMetadata? {
-        if (supportsClipEntry) {
-            return currentClipEntry?.clipData?.description?.toClipMetadata()
-        } else {
-            throw NotImplementedError("This clipboard does not support clip entries")
-        }
-    }
-
-    override fun hasClip(): Boolean {
-        if (supportsClipEntry) {
-            return currentClipEntry != null
-        } else {
-            throw NotImplementedError("This clipboard does not support clip entries")
-        }
-    }
-
-    override fun setClip(clipEntry: ClipEntry) {
+    override fun setClip(clipEntry: ClipEntry?) {
         if (supportsClipEntry) {
             currentClipEntry = clipEntry
         } else {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
index cca04f5..7ac3f35 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/selection/gesture/TextFieldScrolledSelectionGestureTest.kt
@@ -143,7 +143,7 @@
 
         fun assertSelectionEquals(selectionRange: Pair<Int, Int>) {
             val (start, end) = selectionRange
-            assertThat(textFieldState.text.selection).isEqualTo(TextRange(start, end))
+            assertThat(textFieldState.selection).isEqualTo(TextRange(start, end))
         }
 
         fun assertNoMagnifierExists() {
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt
index e0051fd..901345d 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/undo/BasicTextFieldUndoTest.kt
@@ -212,7 +212,7 @@
         state.undoState.undo()
 
         rule.runOnIdle {
-            assertThat(state.text.selection).isNotEqualTo(TextRange(7))
+            assertThat(state.selection).isNotEqualTo(TextRange(7))
         }
 
         state.undoState.redo()
@@ -368,7 +368,7 @@
     private fun TextFieldState.assertTextAndSelection(text: String, selection: TextRange) {
         rule.runOnIdle {
             assertThat(this.text.toString()).isEqualTo(text)
-            assertThat(this.text.selection).isEqualTo(selection)
+            assertThat(this.selection).isEqualTo(selection)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/TransferableContent.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/TransferableContent.android.kt
index 7c0f682..8ac71ce5 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/TransferableContent.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/content/TransferableContent.android.kt
@@ -28,8 +28,8 @@
 /**
  * Android specific parts of [TransferableContent].
  *
- * @param linkUri Only supplied by InputConnection#commitContent.
- * @param extras Extras bundle that's passed by InputConnection#commitContent.
+ * @property linkUri Only supplied by InputConnection#commitContent.
+ * @property extras Extras bundle that's passed by InputConnection#commitContent.
  */
 @ExperimentalFoundationApi
 actual class PlatformTransferableContent internal constructor(
@@ -61,16 +61,18 @@
 
 /**
  * Helper function to consume parts of [TransferableContent] in Android by splitting it to
- * [ClipData.Item] parts. Use this function in [receiveContent] modifier's `onReceive` callback to
+ * [ClipData.Item] parts. Use this function in [contentReceiver] modifier's `onReceive` callback to
  * easily separate remaining parts from incoming [TransferableContent].
  *
+ * @sample androidx.compose.foundation.samples.ReceiveContentBasicSample
+ *
  * @param predicate Decides whether to consume or leave the given item out. Return true to indicate
  * that this particular item was processed here, it shouldn't be passed further down the content
  * receiver chain. Return false to keep it in the returned [TransferableContent].
  * @return Remaining parts of this [TransferableContent].
  */
 @ExperimentalFoundationApi
-fun TransferableContent.consumeEach(predicate: (ClipData.Item) -> Boolean): TransferableContent? {
+fun TransferableContent.consume(predicate: (ClipData.Item) -> Boolean): TransferableContent? {
     val clipData = clipEntry.clipData
     return if (clipData.itemCount == 1) {
         // return this if the single item inside ClipData is not consumed, or null if it's consumed
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegate.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegate.android.kt
new file mode 100644
index 0000000..7534743
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegate.android.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.handwriting
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.text.input.internal.ComposeInputMethodManager
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.requireView
+import androidx.compose.ui.platform.InspectorInfo
+
+/**
+ * Configures an element to act as a stylus handwriting delegate which can handle text input from a
+ * handwriting session which was triggered by stylus handwriting on a handwriting delegator.
+ *
+ * When this element gains focus, if there is an ongoing stylus handwriting delegation which was
+ * triggered by stylus handwriting on a handwriting delegator, this element will receive text input
+ * from the handwriting session via its input connection.
+ *
+ * A common use case is a component which looks like a text input field but does not actually
+ * support text input itself, and clicking on this fake text input field causes a real text input
+ * field to be shown. To support handwriting initiation in this case, a [handwritingDelegator]
+ * modifier can be applied to the fake text input field to configure it as a delegator, and this
+ * modifier can be applied to the real text input field. The `callback` implementation for the fake
+ * text field's [handwritingDelegator] modifier is typically the same as the `onClick`
+ * implementation its [clickable] modifier, which shows and focuses the real text input field.
+ *
+ * This function returns a no-op modifier on API levels below Android U (34) as stylus handwriting
+ * is not supported.
+ */
+fun Modifier.handwritingDelegate(): Modifier =
+    if (isStylusHandwritingSupported) then(HandwritingDelegateElement()) else this
+
+private class HandwritingDelegateElement : ModifierNodeElement<HandwritingDelegateNode>() {
+    override fun create() = HandwritingDelegateNode()
+
+    override fun update(node: HandwritingDelegateNode) {}
+
+    override fun hashCode() = 0
+
+    override fun equals(other: Any?) = other is HandwritingDelegateElement
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "handwritingDelegate"
+    }
+}
+
+private class HandwritingDelegateNode : FocusEventModifierNode, Modifier.Node() {
+    private var focusState: FocusState? = null
+    private val composeImm by lazy(LazyThreadSafetyMode.NONE) {
+        ComposeInputMethodManager(requireView())
+    }
+
+    override fun onFocusEvent(focusState: FocusState) {
+        if (this.focusState != focusState) {
+            this.focusState = focusState
+            if (focusState.hasFocus) {
+                composeImm.acceptStylusHandwritingDelegation()
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt
new file mode 100644
index 0000000..b4a18c5
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDelegator.android.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.handwriting
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.text.input.internal.ComposeInputMethodManager
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.requireView
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * Configures an element to act as a handwriting delegator which detects stylus handwriting and
+ * delegates handling of the recognised text to a handwriting delegate.
+ *
+ * Stylus movement on the element will start a handwriting session, and trigger the [callback]. The
+ * [callback] implementation is expected to show and focus a handwriting delegate element which can
+ * handle the recognized text from the handwriting session.
+ *
+ * A common use case is a component which looks like a text input field but does not actually
+ * support text input itself, and clicking on this fake text input field causes a real text input
+ * field to be shown. To support handwriting initiation in this case, this modifier can be applied
+ * to the fake text input field to configure it as a delegator, and a [handwritingDelegate] modifier
+ * can be applied to the real text input field. The [callback] implementation is typically the same
+ * as the `onClick` implementation for the fake text field's [clickable] modifier, which shows and
+ * focuses the real text input field.
+ *
+ * This function returns a no-op modifier on API levels below Android U (34) as stylus handwriting
+ * is not supported.
+ *
+ * @param callback a callback which will be triggered when stylus handwriting is detected
+ */
+fun Modifier.handwritingDelegator(callback: () -> Unit) =
+    if (isStylusHandwritingSupported) then(HandwritingDelegatorElement(callback)) else this
+
+private class HandwritingDelegatorElement(
+    private val callback: () -> Unit
+) : ModifierNodeElement<HandwritingDelegatorNode>() {
+    override fun create() = HandwritingDelegatorNode(callback)
+
+    override fun update(node: HandwritingDelegatorNode) {
+        node.callback = callback
+    }
+
+    override fun hashCode() = 31 * callback.hashCode()
+
+    override fun equals(other: Any?) =
+        (this === other) or ((other is HandwritingDelegatorElement) && callback == other.callback)
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "handwritingDelegator"
+        properties["callback"] = callback
+    }
+}
+
+private class HandwritingDelegatorNode(var callback: () -> Unit) : DelegatingNode(),
+    PointerInputModifierNode {
+    private val composeImm by lazy(LazyThreadSafetyMode.NONE) {
+        ComposeInputMethodManager(requireView())
+    }
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize
+    ) {
+        pointerInputNode.onPointerEvent(pointerEvent, pass, bounds)
+    }
+
+    override fun onCancelPointerInput() {
+        pointerInputNode.onCancelPointerInput()
+    }
+
+    val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
+        detectStylusHandwriting {
+            callback()
+            composeImm.prepareStylusHandwritingDelegation()
+            return@detectStylusHandwriting true
+        }
+    })
+}
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.android.kt
similarity index 74%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.android.kt
index 3e91597..3ba3d83 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.android.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.compose.foundation.text.handwriting
+
+import android.os.Build
+
+internal actual val isStylusHandwritingSupported: Boolean =
+    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
index d90df9e..2e85ce2 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
@@ -80,8 +80,8 @@
                     composeImm.updateSelection(
                         selectionStart = newSelection.min,
                         selectionEnd = newSelection.max,
-                        compositionStart = oldComposition?.min ?: -1,
-                        compositionEnd = oldComposition?.max ?: -1
+                        compositionStart = newComposition?.min ?: -1,
+                        compositionEnd = newComposition?.max ?: -1
                     )
                 }
 
@@ -141,29 +141,20 @@
                 }
             }
 
-            val hintMediaTypes = receiveContentConfiguration?.hintMediaTypes
-            val contentMimeTypes: Array<String>? =
-                if (!hintMediaTypes.isNullOrEmpty()) {
-                    val arr = Array(hintMediaTypes.size) { "" }
-                    hintMediaTypes.forEachIndexed { i, mediaType ->
-                        arr[i] = mediaType.representation
-                    }
-                    arr
-                } else {
-                    null
-                }
-
             outAttrs.update(
                 text = state.visualText,
                 selection = state.visualText.selection,
                 imeOptions = imeOptions,
-                contentMimeTypes = contentMimeTypes
+                // only pass AllMimeTypes if we have a ReceiveContentConfiguration.
+                contentMimeTypes = receiveContentConfiguration?.let { ALL_MIME_TYPES }
             )
             StatelessInputConnection(textInputSession, outAttrs)
         }
     }
 }
 
+private val ALL_MIME_TYPES = arrayOf("*/*")
+
 private fun logDebug(tag: String = TIA_TAG, content: () -> String) {
     if (TIA_DEBUG) {
         Log.d(tag, content())
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt
index 4e4eee14..f6b7bb6 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/ComposeInputMethodManager.android.kt
@@ -69,6 +69,10 @@
      * Signal the IME to start stylus handwriting.
      */
     fun startStylusHandwriting()
+
+    fun prepareStylusHandwritingDelegation()
+
+    fun acceptStylusHandwritingDelegation()
 }
 
 /**
@@ -156,6 +160,14 @@
         // stylus handwriting is only supported after Android U.
     }
 
+    override fun prepareStylusHandwritingDelegation() {
+        // stylus handwriting is only supported after Android U.
+    }
+
+    override fun acceptStylusHandwritingDelegation() {
+        // stylus handwriting is only supported after Android U.
+    }
+
     protected fun requireImm(): InputMethodManager = imm ?: createImm().also { imm = it }
 
     private fun createImm() =
@@ -193,4 +205,12 @@
     override fun startStylusHandwriting() {
         requireImm().startStylusHandwriting(view)
     }
+
+    override fun prepareStylusHandwritingDelegation() {
+        requireImm().prepareStylusHandwritingDelegation(view)
+    }
+
+    override fun acceptStylusHandwritingDelegation() {
+        requireImm().acceptStylusHandwritingDelegation(view)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputMethodManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputMethodManager.android.kt
index e5298d9..9aaa89a 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputMethodManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/InputMethodManager.android.kt
@@ -17,10 +17,13 @@
 package androidx.compose.foundation.text.input.internal
 
 import android.content.Context
+import android.os.Build
 import android.util.Log
 import android.view.View
 import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.ExtractedText
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
 import androidx.core.view.SoftwareKeyboardControllerCompat
 
 internal interface InputMethodManager {
@@ -45,6 +48,8 @@
     )
 
     fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo)
+
+    fun startStylusHandwriting()
 }
 
 /**
@@ -98,4 +103,18 @@
     override fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo) {
         imm.updateCursorAnchorInfo(view, cursorAnchorInfo)
     }
+
+    override fun startStylusHandwriting() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            Api34StartStylusHandwriting.startStylusHandwriting(imm, view)
+        }
+    }
+}
+
+@RequiresApi(34)
+internal object Api34StartStylusHandwriting {
+    @DoNotInline
+    fun startStylusHandwriting(imm: android.view.inputmethod.InputMethodManager, view: View) {
+        imm.startStylusHandwriting(view)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt
index 8cd284c..067d170 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.android.kt
@@ -23,6 +23,7 @@
 import android.view.inputmethod.BaseInputConnection
 import android.view.inputmethod.EditorInfo
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.platform.PlatformTextInputMethodRequest
@@ -36,7 +37,14 @@
 import androidx.emoji2.text.EmojiCompat
 import java.lang.ref.WeakReference
 import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
 
 private const val DEBUG_CLASS = "AndroidLegacyPlatformTextInputServiceAdapter"
 
@@ -55,6 +63,21 @@
 
     private var job: Job? = null
     private var currentRequest: LegacyTextInputMethodRequest? = null
+    private var backingStylusHandwritingTrigger: MutableSharedFlow<Unit>? = null
+    private var stylusHandwritingTrigger: MutableSharedFlow<Unit>? = null
+        get() {
+            val finalStylusHandwritingTrigger = backingStylusHandwritingTrigger
+            if (finalStylusHandwritingTrigger != null) {
+                return finalStylusHandwritingTrigger
+            }
+            if (!isStylusHandwritingSupported) {
+                return null
+            }
+            return MutableSharedFlow<Unit>(
+                replay = 1,
+                onBufferOverflow = BufferOverflow.DROP_LATEST
+            ).also { backingStylusHandwritingTrigger = it }
+        }
 
     override fun startInput(
         value: TextFieldValue,
@@ -89,27 +112,40 @@
         // No need to cancel any previous job, the text input system ensures the previous session
         // will be cancelled.
         job = node.launchTextInputSession {
-            val request = LegacyTextInputMethodRequest(
-                view = view,
-                localToScreen = ::localToScreen,
-                inputMethodManager = inputMethodManagerFactory(view)
-            )
-            initializeRequest?.invoke(request)
-            currentRequest = request
-            try {
-                startInputMethod(request)
-            } finally {
-                currentRequest = null
+            coroutineScope {
+                val inputMethodManager = inputMethodManagerFactory(view)
+                val request = LegacyTextInputMethodRequest(
+                    view = view,
+                    localToScreen = ::localToScreen,
+                    inputMethodManager = inputMethodManager
+                )
+
+                if (isStylusHandwritingSupported) {
+                    launch(start = CoroutineStart.UNDISPATCHED) {
+                        stylusHandwritingTrigger?.collect {
+                            inputMethodManager.startStylusHandwriting()
+                        }
+                    }
+                }
+                initializeRequest?.invoke(request)
+                currentRequest = request
+                try {
+                    startInputMethod(request)
+                } finally {
+                    currentRequest = null
+                }
             }
         }
     }
 
+    @OptIn(ExperimentalCoroutinesApi::class)
     override fun stopInput() {
         if (DEBUG) {
             Log.d(TAG, "$DEBUG_CLASS.stopInput")
         }
         job?.cancel()
         job = null
+        stylusHandwritingTrigger?.resetReplayCache()
     }
 
     override fun updateState(oldValue: TextFieldValue?, newValue: TextFieldValue) {
@@ -136,6 +172,14 @@
             decorationBoxBounds
         )
     }
+
+    /**
+     * Signal the InputMethodManager to startStylusHandwriting. This method can be called
+     * after the editor calls startInput or just before the editor calls startInput.
+     */
+    override fun startStylusHandwriting() {
+        stylusHandwritingTrigger?.tryEmit(Unit)
+    }
 }
 
 /**
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt
index d26d551..5d9ee84 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDragAndDropNode.android.kt
@@ -44,7 +44,7 @@
         shouldStartDragAndDrop = { dragAndDropEvent ->
             // If there's a receiveContent modifier wrapping around this TextField, initially all
             // dragging items should be accepted for drop. This is expected to be met by the caller
-            // of this function.
+            // of [textFieldDragAndDropNode] function.
             val clipDescription = dragAndDropEvent.toAndroidDragEvent().clipDescription
             hintMediaTypes().any {
                 it == MediaType.All || clipDescription.hasMimeType(it.representation)
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt
index 6dbf160..9437e55 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateSaverTest.kt
@@ -37,7 +37,7 @@
 
         assertNotNull(restoredState)
         assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-        assertThat(restoredState.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(restoredState.selection).isEqualTo(TextRange(0, 5))
     }
 
     @Test
@@ -57,7 +57,7 @@
         assertThat(restoredState.undoState.canUndo).isTrue()
         restoredState.undoState.undo()
         assertThat(restoredState.text.toString()).isEqualTo("hello, world")
-        assertThat(restoredState.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(restoredState.selection).isEqualTo(TextRange(0, 5))
     }
 
     private object TestSaverScope : SaverScope {
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
index 457a4a6..4afc7a0 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/TextFieldStateTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text.input
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.snapshots.SnapshotStateObserver
 import androidx.compose.ui.text.TextRange
@@ -26,6 +27,8 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.job
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
@@ -46,21 +49,62 @@
     fun defaultInitialTextAndSelection() {
         val state = TextFieldState()
         assertThat(state.text.toString()).isEqualTo("")
-        assertThat(state.text.selection).isEqualTo(TextRange.Zero)
+        assertThat(state.selection).isEqualTo(TextRange.Zero)
     }
 
     @Test
     fun customInitialTextAndDefaultSelection() {
         val state = TextFieldState(initialText = "hello")
         assertThat(state.text.toString()).isEqualTo("hello")
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
     }
 
     @Test
     fun customInitialTextAndSelection() {
         val state = TextFieldState(initialText = "hello", initialSelection = TextRange(0, 1))
         assertThat(state.text.toString()).isEqualTo("hello")
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 1))
+        assertThat(state.selection).isEqualTo(TextRange(0, 1))
+    }
+
+    @Test
+    fun edit_doesNotAllow_reentrantBehavior() {
+        assertFailsWith<IllegalStateException>(
+            "TextFieldState does not support concurrent or nested editing."
+        ) {
+            state.edit {
+                replace(0, 0, "hello")
+                state.edit {
+                    replace(0, 0, "hello")
+                }
+            }
+        }
+        assertThat(state.text.toString()).isEmpty()
+    }
+
+    @Test
+    fun edit_doesNotAllow_concurrentAccess() {
+        assertFailsWith<IllegalStateException>(
+            "TextFieldState does not support concurrent or nested editing."
+        ) {
+            runTest {
+                var edit2Started = false
+                launch {
+                    state.edit {
+                        replace(0, 0, "hello")
+                        while (!edit2Started) delay(10)
+                    }
+                }
+                launch {
+                    state.edit {
+                        edit2Started = true
+                        replace(0, 0, "hello")
+                    }
+                }
+                advanceUntilIdle()
+                runCurrent()
+            }
+        }
+        assertThat(state.text.toString()).isEmpty()
     }
 
     @Test
@@ -78,6 +122,24 @@
     }
 
     @Test
+    fun edit_canEditAgain_ifFirstOneThrows() {
+        class ExpectedException : RuntimeException()
+
+        assertFailsWith<ExpectedException> {
+            state.edit {
+                replace(0, 0, "hello")
+                throw ExpectedException()
+            }
+        }
+        assertThat(state.text.toString()).isEmpty()
+
+        state.edit {
+            replace(0, 0, "hello")
+        }
+        assertThat(state.text.toString()).isEqualTo("hello")
+    }
+
+    @Test
     fun edit_invalidates_whenSelectionChanged() = runTestWithSnapshotsThenCancelChildren {
         val text = "hello"
         val state = TextFieldState(text, initialSelection = TextRange(0))
@@ -230,7 +292,7 @@
             replace(0, 0, "hello")
             placeCursorAtEnd()
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
     }
 
     @Test
@@ -239,7 +301,7 @@
             replace(0, 0, "hello")
             placeCursorBeforeCharAt(2)
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -261,7 +323,7 @@
             replace(0, 0, "hello")
             selectAll()
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(state.selection).isEqualTo(TextRange(0, 5))
     }
 
     @Test
@@ -270,7 +332,7 @@
             replace(0, 0, "hello")
             selection = TextRange(1, 4)
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(1, 4))
+        assertThat(state.selection).isEqualTo(TextRange(1, 4))
     }
 
     @Test
@@ -338,14 +400,7 @@
     fun setTextAndPlaceCursorAtEnd_works() {
         state.setTextAndPlaceCursorAtEnd("Hello")
         assertThat(state.text.toString()).isEqualTo("Hello")
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
-    }
-
-    @Test
-    fun setTextAndSelectAll_works() {
-        state.setTextAndSelectAll("Hello")
-        assertThat(state.text.toString()).isEqualTo("Hello")
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
     }
 
     @Test
@@ -406,11 +461,11 @@
         val texts = mutableListOf<TextFieldCharSequence>()
 
         launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
+            state.valueAsFlow().collectLatest { texts += it }
         }
 
         assertThat(texts).hasSize(1)
-        assertThat(texts.single()).isSameInstanceAs(state.text)
+        assertThat(texts.single()).isSameInstanceAs(state.value)
         assertThat(texts.single().toString()).isEqualTo("hello")
         assertThat(texts.single().selection).isEqualTo(TextRange(5))
     }
@@ -419,10 +474,10 @@
     fun forEachValue_fires_whenTextChanged() = runTestWithSnapshotsThenCancelChildren {
         val state = TextFieldState(initialSelection = TextRange(0))
         val texts = mutableListOf<TextFieldCharSequence>()
-        val initialText = state.text
+        val initialSelection = state.selection
 
         launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
+            state.valueAsFlow().collectLatest { texts += it }
         }
 
         state.edit {
@@ -431,9 +486,9 @@
         }
 
         assertThat(texts).hasSize(2)
-        assertThat(texts.last()).isSameInstanceAs(state.text)
+        assertThat(texts.last()).isSameInstanceAs(state.value)
         assertThat(texts.last().toString()).isEqualTo("hello")
-        assertThat(texts.last().selection).isEqualTo(initialText.selection)
+        assertThat(texts.last().selection).isEqualTo(initialSelection)
     }
 
     @Test
@@ -442,7 +497,7 @@
         val texts = mutableListOf<TextFieldCharSequence>()
 
         launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
+            state.valueAsFlow().collectLatest { texts += it }
         }
 
         state.edit {
@@ -450,7 +505,7 @@
         }
 
         assertThat(texts).hasSize(2)
-        assertThat(texts.last()).isSameInstanceAs(state.text)
+        assertThat(texts.last()).isSameInstanceAs(state.value)
         assertThat(texts.last().toString()).isEqualTo("hello")
         assertThat(texts.last().selection).isEqualTo(TextRange(5))
     }
@@ -461,7 +516,7 @@
         val texts = mutableListOf<TextFieldCharSequence>()
 
         launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
+            state.valueAsFlow().collectLatest { texts += it }
         }
 
         state.edit {
@@ -476,7 +531,7 @@
 
         assertThat(texts).hasSize(3)
         assertThat(texts[1].toString()).isEqualTo("hello")
-        assertThat(texts[2]).isSameInstanceAs(state.text)
+        assertThat(texts[2]).isSameInstanceAs(state.value)
         assertThat(texts[2].toString()).isEqualTo("hello world")
     }
 
@@ -487,7 +542,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
+                state.valueAsFlow().collectLatest { texts += it }
             }
 
             state.edit {
@@ -496,7 +551,7 @@
                 placeCursorAtEnd()
             }
 
-            assertThat(texts.last()).isSameInstanceAs(state.text)
+            assertThat(texts.last()).isSameInstanceAs(state.value)
             assertThat(texts.last().toString()).isEqualTo("hello world")
         }
 
@@ -507,7 +562,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
+                state.valueAsFlow().collectLatest { texts += it }
             }
 
             val snapshot = Snapshot.takeMutableSnapshot()
@@ -523,7 +578,7 @@
             snapshot.apply()
             snapshot.dispose()
 
-            assertThat(texts.last()).isSameInstanceAs(state.text)
+            assertThat(texts.last()).isSameInstanceAs(state.value)
         }
 
     @Test
@@ -533,7 +588,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
+                state.valueAsFlow().collectLatest { texts += it }
             }
 
             val snapshot = Snapshot.takeMutableSnapshot()
@@ -557,7 +612,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue {
+                state.valueAsFlow().collectLatest {
                     texts += it
                     awaitCancellation()
                 }
@@ -571,6 +626,32 @@
                 .inOrder()
         }
 
+    @Test
+    fun snapshotFlowOfText_onlyFiresIfContentChanges() {
+        runTestWithSnapshotsThenCancelChildren {
+            val state = TextFieldState()
+            val texts = mutableListOf<CharSequence>()
+
+            launch(Dispatchers.Unconfined) {
+                snapshotFlow {
+                    state.text
+                }.collect {
+                    texts += it
+                }
+            }
+
+            state.edit { append("a") }
+            state.edit { append("b") }
+            state.edit { placeCursorBeforeCharAt(0) }
+            state.edit { placeCursorAtEnd() }
+            state.edit { append("c") }
+
+            assertThat(texts.map { it.toString() })
+                .containsExactly("", "a", "ab", "abc")
+                .inOrder()
+        }
+    }
+
     private fun runTestWithSnapshotsThenCancelChildren(testBody: suspend TestScope.() -> Unit) {
         val globalWriteObserverHandle = Snapshot.registerGlobalWriteObserver {
             // This is normally done by the compose runtime.
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
index 168217a..bd3f689 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TextFieldStateInternalBufferTest.kt
@@ -36,7 +36,7 @@
         val firstValue = TextFieldCharSequence("ABCDE", TextRange.Zero)
         val state = TextFieldState(firstValue)
 
-        assertThat(state.text).isEqualTo(firstValue)
+        assertThat(state.value).isEqualTo(firstValue)
     }
 
     @Test
@@ -51,7 +51,7 @@
         }
 
         state.editAsUser { commitText("X", 1) }
-        val newState = state.text
+        val newState = state.value
 
         assertThat(newState.toString()).isEqualTo("XABCDE")
         assertThat(newState.selection.min).isEqualTo(1)
@@ -73,7 +73,7 @@
         }
 
         state.editAsUser { setSelection(0, 2) }
-        val newState = state.text
+        val newState = state.value
 
         assertThat(newState.toString()).isEqualTo("ABCDE")
         assertThat(newState.selection.min).isEqualTo(0)
@@ -241,13 +241,13 @@
         val newValue =
             TextFieldCharSequence(
                 "cd",
-                state.text.selection,
-                state.text.composition
+                state.selection,
+                state.composition
             )
         state.resetStateAndNotifyIme(newValue)
 
         assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.composition).isNull()
+        assertThat(state.composition).isNull()
         assertThat(resetCalled).isEqualTo(1)
         assertThat(selectionCalled).isEqualTo(1)
     }
@@ -267,13 +267,13 @@
         val newValue =
             TextFieldCharSequence(
                 state.text,
-                state.text.selection,
-                state.text.composition
+                state.selection,
+                state.composition
             )
         state.resetStateAndNotifyIme(newValue)
 
         assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.composition).isEqualTo(composition)
+        assertThat(state.composition).isEqualTo(composition)
     }
 
     @Test
@@ -290,13 +290,13 @@
         val newValue =
             TextFieldCharSequence(
                 state.text,
-                state.text.selection,
+                state.selection,
                 composition = TextRange(0, 2)
             )
         state.resetStateAndNotifyIme(newValue)
 
         assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.composition).isNull()
+        assertThat(state.composition).isNull()
     }
 
     @Test
@@ -312,13 +312,13 @@
         // change the composition
         val newValue = TextFieldCharSequence(
             state.text,
-            state.text.selection,
+            state.selection,
             composition = TextRange(0, 1)
         )
         state.resetStateAndNotifyIme(newValue)
 
         assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.composition).isNull()
+        assertThat(state.composition).isNull()
     }
 
     @Test
@@ -340,13 +340,13 @@
         val newValue = TextFieldCharSequence(
             state.text,
             selection = newSelection,
-            composition = state.text.composition
+            composition = state.composition
         )
         state.resetStateAndNotifyIme(newValue)
 
         assertThat(state.text.toString()).isEqualTo(newValue.toString())
-        assertThat(state.text.composition).isEqualTo(composition)
-        assertThat(state.text.selection).isEqualTo(newSelection)
+        assertThat(state.composition).isEqualTo(composition)
+        assertThat(state.selection).isEqualTo(newSelection)
     }
 
     @Test
@@ -541,7 +541,7 @@
 
         state.editAsUser { setComposingRegion(2, 3) }
 
-        assertThat(state.text.composition).isEqualTo(TextRange(2, 3))
+        assertThat(state.composition).isEqualTo(TextRange(2, 3))
     }
 
     @Test
@@ -552,7 +552,7 @@
 
         state.editAsUser { setComposingRegion(2, 3) }
 
-        assertThat(state.text.composition).isEqualTo(TextRange(2, 3))
+        assertThat(state.composition).isEqualTo(TextRange(2, 3))
     }
 
     private fun TextFieldState(
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt
index 029b76d..f9ce97e 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextSelectionMovementTest.kt
@@ -43,7 +43,7 @@
 
         calculateNextCursorPosition(transformedState)
 
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(3))
     }
 
@@ -59,7 +59,7 @@
 
         calculatePreviousCursorPosition(transformedState)
 
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(1))
     }
 
@@ -73,19 +73,19 @@
             TransformedTextFieldState(state, outputTransformation = outputTransformation)
 
         calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.selectionWedgeAffinity)
             .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
 
         calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(3))
         assertThat(transformedState.selectionWedgeAffinity)
             .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
 
         calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(4))
         assertThat(transformedState.selectionWedgeAffinity)
             .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
@@ -102,19 +102,19 @@
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(4))
 
         calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(3))
         assertThat(transformedState.selectionWedgeAffinity)
             .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.End))
 
         calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(1))
         assertThat(transformedState.selectionWedgeAffinity)
             .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
 
         calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(0))
+        assertThat(state.selection).isEqualTo(TextRange(0))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(0))
         assertThat(transformedState.selectionWedgeAffinity)
             .isEqualTo(SelectionWedgeAffinity(WedgeAffinity.Start))
@@ -130,11 +130,11 @@
             TransformedTextFieldState(state, outputTransformation = outputTransformation)
 
         calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(1, 3))
+        assertThat(state.selection).isEqualTo(TextRange(1, 3))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(1))
 
         calculateNextCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(4))
+        assertThat(state.selection).isEqualTo(TextRange(4))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(2))
     }
 
@@ -149,11 +149,11 @@
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(2))
 
         calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(1, 3))
+        assertThat(state.selection).isEqualTo(TextRange(1, 3))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(1))
 
         calculatePreviousCursorPosition(transformedState)
-        assertThat(state.text.selection).isEqualTo(TextRange(0))
+        assertThat(state.selection).isEqualTo(TextRange(0))
         assertThat(transformedState.visualText.selection).isEqualTo(TextRange(0))
     }
 
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt
index 6c07153..ea0d8d1 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/input/internal/undo/TextUndoTest.kt
@@ -76,7 +76,7 @@
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("ac")
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("")
@@ -160,15 +160,15 @@
         state.typeAtStart("c") // "|a" -> "c|a"
 
         assertThat(state.text.toString()).isEqualTo("ca")
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("a")
-        assertThat(state.text.selection).isEqualTo(TextRange(0))
+        assertThat(state.selection).isEqualTo(TextRange(0))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("ab")
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("")
@@ -182,15 +182,15 @@
         state.typeAtEnd("g") // "defg|"
 
         assertThat(state.text.toString()).isEqualTo("defg")
-        assertThat(state.text.selection).isEqualTo(TextRange(4))
+        assertThat(state.selection).isEqualTo(TextRange(4))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("def")
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("abcd")
-        assertThat(state.text.selection).isEqualTo(TextRange(4))
+        assertThat(state.selection).isEqualTo(TextRange(4))
     }
 
     @Test
@@ -202,15 +202,15 @@
         state.deleteAt(2) // "de|"
 
         assertThat(state.text.toString()).isEqualTo("de")
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("def")
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("ab")
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -222,15 +222,15 @@
         state.typeAtEnd("c") // "ab\nc|"
 
         assertThat(state.text.toString()).isEqualTo("ab\nc")
-        assertThat(state.text.selection).isEqualTo(TextRange(4))
+        assertThat(state.selection).isEqualTo(TextRange(4))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("ab\n")
-        assertThat(state.text.selection).isEqualTo(TextRange(3))
+        assertThat(state.selection).isEqualTo(TextRange(3))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("ab")
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("")
@@ -243,11 +243,11 @@
         state.type("d") // "d|c"
 
         assertThat(state.text.toString()).isEqualTo("dc")
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
 
         state.undoState.undo()
         assertThat(state.text.toString()).isEqualTo("abc")
-        assertThat(state.text.selection).isEqualTo(TextRange(2, 0))
+        assertThat(state.selection).isEqualTo(TextRange(2, 0))
     }
 
     @Test
@@ -258,14 +258,14 @@
         state.type("e") // "e|cd"
 
         assertThat(state.text.toString()).isEqualTo("ecd")
-        assertThat(state.text.selection).isEqualTo(TextRange(1))
+        assertThat(state.selection).isEqualTo(TextRange(1))
 
         state.undoState.undo() // "|ab|cd"
         state.undoState.undo() // "|abc"
         state.undoState.redo() // "abcd|"
 
         assertThat(state.text.toString()).isEqualTo("abcd")
-        assertThat(state.text.selection).isEqualTo(TextRange(4, 4))
+        assertThat(state.selection).isEqualTo(TextRange(4, 4))
     }
 
     @Test
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 3adfa54..bce7083 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
@@ -16,8 +16,8 @@
 
 package androidx.compose.foundation
 
-import androidx.compose.foundation.gestures.ModifierLocalScrollableContainer
 import androidx.compose.foundation.gestures.PressGestureScope
+import androidx.compose.foundation.gestures.ScrollableContainerNode
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.interaction.HoverInteraction
@@ -29,7 +29,6 @@
 import androidx.compose.ui.composed
 import androidx.compose.ui.focus.FocusEventModifierNode
 import androidx.compose.ui.focus.FocusState
-import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.KeyEvent
@@ -41,13 +40,14 @@
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.PointerInputModifierNode
 import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.node.invalidateSemantics
+import androidx.compose.ui.node.traverseAncestors
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.Role
@@ -180,7 +180,6 @@
     role: Role? = null,
     onClick: () -> Unit
 ) = clickableWithIndicationIfNeeded(
-    enabled = enabled,
     interactionSource = interactionSource,
     indication = indication
 ) { intSource, indicationNodeFactory ->
@@ -335,7 +334,6 @@
     onDoubleClick: (() -> Unit)? = null,
     onClick: () -> Unit
 ) = clickableWithIndicationIfNeeded(
-    enabled = enabled,
     interactionSource = interactionSource,
     indication = indication
 ) { intSource, indicationNodeFactory ->
@@ -357,11 +355,10 @@
  * [createClickable] is the lambda that creates the actual clickable element, which will be chained
  * with [Modifier.indication] if needed.
  */
-internal fun Modifier.clickableWithIndicationIfNeeded(
-    enabled: Boolean,
+internal inline fun Modifier.clickableWithIndicationIfNeeded(
     interactionSource: MutableInteractionSource?,
     indication: Indication?,
-    createClickable: (MutableInteractionSource?, IndicationNodeFactory?) -> Modifier
+    crossinline createClickable: (MutableInteractionSource?, IndicationNodeFactory?) -> Modifier
 ): Modifier {
     return this.then(when {
         // Fast path - indication is managed internally
@@ -381,7 +378,7 @@
                 .indication(newInteractionSource, indication)
                 .then(createClickable(newInteractionSource, null))
         }
-    }).then(if (enabled) Modifier.focusTarget() else Modifier)
+    })
 }
 
 /**
@@ -397,7 +394,7 @@
  * container, we still want to delay presses in case presses in Compose convert to a scroll outside
  * of Compose.
  *
- * Combine this with [ModifierLocalScrollableContainer], which returns whether a [Modifier] is
+ * Combine this with [hasScrollableContainer], which returns whether a [Modifier] is
  * within a scrollable Compose layout, to calculate whether this modifier is within some form of
  * scrollable container, and hence should delay presses.
  */
@@ -876,7 +873,7 @@
     private var role: Role?,
     onClick: () -> Unit
 ) : DelegatingNode(), PointerInputModifierNode, KeyInputModifierNode, FocusEventModifierNode,
-    SemanticsModifierNode, ModifierLocalModifierNode {
+    SemanticsModifierNode, TraversableNode {
     protected var enabled = enabled
         private set
     protected var onClick = onClick
@@ -1156,7 +1153,7 @@
     }
 
     private fun delayPressInteraction(): Boolean =
-        ModifierLocalScrollableContainer.current || isComposeRootInScrollableContainer()
+        hasScrollableContainer() || isComposeRootInScrollableContainer()
 
     private fun emitHoverEnter() {
         if (hoverInteraction == null) {
@@ -1181,6 +1178,10 @@
             hoverInteraction = null
         }
     }
+
+    override val traverseKey: Any = TraverseKey
+
+    companion object TraverseKey
 }
 
 private class ClickableSemanticsElement(
@@ -1257,6 +1258,7 @@
 
     override val shouldMergeDescendantSemantics: Boolean
         get() = true
+
     override fun SemanticsPropertyReceiver.applySemantics() {
         if (this@ClickableSemanticsNode.role != null) {
             role = this@ClickableSemanticsNode.role!!
@@ -1276,3 +1278,12 @@
         }
     }
 }
+
+internal fun TraversableNode.hasScrollableContainer(): Boolean {
+    var hasScrollable = false
+    traverseAncestors(ScrollableContainerNode.TraverseKey) { node ->
+        hasScrollable = hasScrollable || (node as ScrollableContainerNode).enabled
+        !hasScrollable
+    }
+    return hasScrollable
+}
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 5e32fa2..272d758 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
@@ -27,6 +27,7 @@
 import androidx.compose.ui.focus.FocusPropertiesModifierNode
 import androidx.compose.ui.focus.FocusRequesterModifierNode
 import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.focus.FocusTargetModifierNode
 import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.focus.requestFocus
@@ -70,9 +71,7 @@
     interactionSource: MutableInteractionSource? = null,
 ) = this.then(
     if (enabled) {
-        FocusableElement(
-            interactionSource
-        ).focusTarget()
+        FocusableElement(interactionSource)
     } else {
         Modifier
     }
@@ -195,6 +194,10 @@
     private val focusablePinnableContainer = delegate(FocusablePinnableContainerNode())
     private val focusedBoundsNode = delegate(FocusedBoundsNode())
 
+    init {
+        delegate(FocusTargetModifierNode())
+    }
+
     // Focusables have a few different cases where they need to make sure they stay visible:
     //
     // 1. Focusable node newly receives focus – always bring entire node into view. That's what this
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 2dc3a46f..01f5f64 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
@@ -18,17 +18,12 @@
 
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.modifier.ModifierLocalMap
-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.node.TraversableNode
+import androidx.compose.ui.node.findNearestAncestor
 import androidx.compose.ui.platform.InspectorInfo
 
-internal val ModifierLocalFocusedBoundsObserver =
-    modifierLocalOf<((LayoutCoordinates?) -> Unit)?> { null }
-
 /**
  * Calls [onPositioned] whenever the bounds of the currently-focused area changes.
  * If a child of this node has focus, [onPositioned] will be called immediately with a non-null
@@ -48,7 +43,8 @@
 private class FocusedBoundsObserverElement(
     val onPositioned: (LayoutCoordinates?) -> Unit
 ) : ModifierNodeElement<FocusedBoundsObserverNode>() {
-    override fun create(): FocusedBoundsObserverNode = FocusedBoundsObserverNode(onPositioned)
+    override fun create(): FocusedBoundsObserverNode =
+        FocusedBoundsObserverNode(onPositioned)
 
     override fun update(node: FocusedBoundsObserverNode) {
         node.onPositioned = onPositioned
@@ -70,20 +66,18 @@
 
 internal class FocusedBoundsObserverNode(
     var onPositioned: (LayoutCoordinates?) -> Unit
-) : Modifier.Node(), ModifierLocalModifierNode {
-    private val parent: ((LayoutCoordinates?) -> Unit)?
-        get() = if (isAttached) ModifierLocalFocusedBoundsObserver.current else null
+) : Modifier.Node(), TraversableNode {
+
+    override val traverseKey: Any = TraverseKey
 
     /** Called when a child gains/loses focus or is focused and changes position. */
-    private val focusBoundsObserver: (LayoutCoordinates?) -> Unit = { focusedBounds ->
-        if (isAttached) {
-            onPositioned(focusedBounds)
-            parent?.invoke(focusedBounds)
-        }
+
+     fun onFocusBoundsChanged(focusedBounds: LayoutCoordinates?) {
+        onPositioned(focusedBounds)
+        findNearestAncestor()?.onFocusBoundsChanged(focusedBounds)
     }
 
-    override val providedValues: ModifierLocalMap =
-        modifierLocalMapOf(ModifierLocalFocusedBoundsObserver to focusBoundsObserver)
+    companion object TraverseKey
 }
 
 /**
@@ -92,15 +86,17 @@
  * 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(), ModifierLocalModifierNode,
+internal class FocusedBoundsNode : Modifier.Node(), TraversableNode,
     GlobalPositionAwareModifierNode {
     private var isFocused: Boolean = false
 
+    override val traverseKey: Any get() = TraverseKey
+
     override val shouldAutoInvalidate: Boolean = false
 
-    private val observer: ((LayoutCoordinates?) -> Unit)?
+    private val observer: FocusedBoundsObserverNode?
         get() = if (isAttached) {
-            ModifierLocalFocusedBoundsObserver.current
+            findNearestAncestor(FocusedBoundsObserverNode.TraverseKey) as? FocusedBoundsObserverNode
         } else {
             null
         }
@@ -115,7 +111,7 @@
     fun setFocus(focused: Boolean) {
         if (focused == isFocused) return
         if (!focused) {
-            observer?.invoke(null)
+            observer?.onFocusBoundsChanged(null)
         } else {
             notifyObserverWhenAttached()
         }
@@ -128,13 +124,15 @@
         if (coordinates.isAttached) {
             notifyObserverWhenAttached()
         } else {
-            observer?.invoke(null)
+            observer?.onFocusBoundsChanged(null)
         }
     }
 
     private fun notifyObserverWhenAttached() {
         if (layoutCoordinates != null && layoutCoordinates!!.isAttached) {
-            observer?.invoke(layoutCoordinates)
+            observer?.onFocusBoundsChanged(layoutCoordinates)
         }
     }
+
+    companion object TraverseKey
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index 5ca3dbde..670fe29 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -39,6 +39,7 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.Measurable
@@ -51,10 +52,10 @@
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.ScrollAxisRange
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
-import androidx.compose.ui.semantics.getScrollViewportLength
 import androidx.compose.ui.semantics.horizontalScrollAxisRange
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.scrollBy
+import androidx.compose.ui.semantics.scrollByOffset
 import androidx.compose.ui.semantics.verticalScrollAxisRange
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.util.fastRoundToInt
@@ -366,9 +367,14 @@
                 }
             )
 
-            getScrollViewportLength {
-                it.add(state.viewportSize.toFloat())
-                true
+            scrollByOffset { offset ->
+                if (isVertical) {
+                    val consumed = (state as ScrollableState).animateScrollBy(offset.y)
+                    Offset(0f, consumed)
+                } else {
+                    val consumed = (state as ScrollableState).animateScrollBy(offset.x)
+                    Offset(consumed, 0f)
+                }
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContent.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContent.kt
index 90d2762..939c724 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContent.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContent.kt
@@ -37,77 +37,45 @@
  * Content in this context refers to a [TransferableContent] that could be received from another
  * app through Drag-and-Drop, Copy/Paste, or from the Software Keyboard.
  *
- * @param hintMediaTypes A set of media types that are expected by this receiver. This set
- * gets passed to the Software Keyboard to send information about what type of content the editor
- * supports. It's possible that this modifier receives other type of content that's not specified in
- * this set. Please make sure to check again whether the received [TransferableContent] carries a
- * supported [MediaType]. An empty [MediaType] set implies [MediaType.All].
- * @param onReceive Callback that's triggered when a content is successfully committed. Return
- * an optional [TransferableContent] that contains the unprocessed or unaccepted parts of the
- * received [TransferableContent]. The remaining [TransferableContent] first will be sent to to the
- * closest ancestor [receiveContent] modifier. This chain will continue until there's no ancestor
- * modifier left, or [TransferableContent] is fully consumed. After, the source subsystem that
- * created the original [TransferableContent] and initiated the chain will receive any remaining
- * items to execute its default behavior. For example a text editor that receives content should
- * insert any remaining text to the drop position.
+ * There is no pre-filtering for the received content by media type, e.g. software Keyboard would
+ * assume that the app can handle any content that's sent to it. Therefore, it's crucial to check
+ * the received content's type and other related information before reading and processing it.
+ * Please refer to [TransferableContent.hasMediaType] and [TransferableContent.clipMetadata] to
+ * learn more about how to do proper checks on the received item.
  *
- * @sample androidx.compose.foundation.samples.ReceiveContentBasicSample
- */
-@ExperimentalFoundationApi
-fun Modifier.receiveContent(
-    hintMediaTypes: Set<MediaType>,
-    onReceive: (TransferableContent) -> TransferableContent?
-): Modifier = then(
-    ReceiveContentElement(
-        hintMediaTypes = hintMediaTypes,
-        receiveContentListener = ReceiveContentListener(onReceive)
-    )
-)
-
-/**
- * Configures the current node and any children nodes as a Content Receiver.
+ * @param receiveContentListener Listener to respond to the receive event. This interface also
+ * includes a set of callbacks for certain Drag-and-Drop state changes. Please checkout
+ * [ReceiveContentListener] docs for an explanation of each callback.
  *
- * Content in this context refers to a [TransferableContent] that could be received from another
- * app through Drag-and-Drop, Copy/Paste, or from the Software Keyboard.
- *
- * @param hintMediaTypes A set of media types that are expected by this receiver. This set
- * gets passed to the Software Keyboard to send information about what type of content the editor
- * supports. It's possible that this modifier receives other type of content that's not specified in
- * this set. Please make sure to check again whether the received [TransferableContent] carries a
- * supported [MediaType]. An empty [MediaType] set implies [MediaType.All].
- * @param receiveContentListener A set of callbacks that includes certain Drag-and-Drop state
- * changes. Please checkout [ReceiveContentListener] docs for an explanation of each callback.
+ * @see TransferableContent
+ * @see hasMediaType
  *
  * @sample androidx.compose.foundation.samples.ReceiveContentFullSample
  */
 @Suppress("ExecutorRegistration")
 @ExperimentalFoundationApi
-fun Modifier.receiveContent(
-    hintMediaTypes: Set<MediaType>,
+fun Modifier.contentReceiver(
     receiveContentListener: ReceiveContentListener
 ): Modifier = then(
     ReceiveContentElement(
-        hintMediaTypes = hintMediaTypes.toSet(),
         receiveContentListener = receiveContentListener
     )
 )
 
 @OptIn(ExperimentalFoundationApi::class)
 internal data class ReceiveContentElement(
-    val hintMediaTypes: Set<MediaType>,
     val receiveContentListener: ReceiveContentListener
 ) : ModifierNodeElement<ReceiveContentNode>() {
     override fun create(): ReceiveContentNode {
-        return ReceiveContentNode(hintMediaTypes, receiveContentListener)
+        return ReceiveContentNode(receiveContentListener)
     }
 
     override fun update(node: ReceiveContentNode) {
-        node.updateNode(hintMediaTypes, receiveContentListener)
+        node.updateNode(receiveContentListener)
     }
 
     override fun InspectorInfo.inspectableProperties() {
         name = "receiveContent"
-        properties["hintMediaTypes"] = hintMediaTypes
     }
 }
 
@@ -116,7 +84,6 @@
 // TraversableNode if it was available, the switch should be fairly easy when the bug is fixed.
 @OptIn(ExperimentalFoundationApi::class)
 internal class ReceiveContentNode(
-    var hintMediaTypes: Set<MediaType>,
     var receiveContentListener: ReceiveContentListener
 ) : DelegatingNode(), ModifierLocalModifierNode,
     CompositionLocalConsumerModifierNode {
@@ -127,7 +94,7 @@
     // The default provided configuration is the one supplied to this node. Once the node is
     // attached, it should provide a delegating version to ancestor nodes.
     override val providedValues: ModifierLocalMap =
-        modifierLocalMapOf<ReceiveContentConfiguration?>(
+        modifierLocalMapOf(
             ModifierLocalReceiveContent to receiveContentConfiguration
         )
 
@@ -140,11 +107,7 @@
         )
     }
 
-    fun updateNode(
-        hintMediaTypes: Set<MediaType>,
-        receiveContentListener: ReceiveContentListener
-    ) {
-        this.hintMediaTypes = hintMediaTypes
+    fun updateNode(receiveContentListener: ReceiveContentListener) {
         this.receiveContentListener = receiveContentListener
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContentListener.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContentListener.kt
index 40934de..25f391a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContentListener.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/ReceiveContentListener.kt
@@ -20,14 +20,18 @@
 import androidx.compose.foundation.draganddrop.dragAndDropTarget
 
 /**
- * A set of callbacks for [receiveContent] modifier to get information about certain Drag-and-Drop
+ * A set of callbacks for [contentReceiver] modifier to get information about certain Drag-and-Drop
  * state changes, as well as receiving the payload carrying [TransferableContent].
  *
- * [receiveContent]'s drop target supports nesting. When two [receiveContent] modifiers are nested
- * on the composition tree, parent's drop target actually includes child's bounds, meaning that
- * they are not mutually exclusive like the regular [dragAndDropTarget].
+ * [contentReceiver]'s drop target behaves a little different compared to the regular
+ * [dragAndDropTarget] modifier. When two [contentReceiver] modifiers are nested on the composition
+ * tree, drop area of the parent node encapsulates drop area of the child node, meaning that they
+ * wouldn't be mutually exclusive like the regular [dragAndDropTarget] nesting. This becomes useful
+ * when you want to extend the drop area around a TextField by wrapping the TextField in a larger
+ * component with a [contentReceiver] modifier. We can guarantee that the container won't receive
+ * dragExit event when the dragging item moves over to TextField.
  *
- * Let's assume we have two [receiveContent] boxes named A and B where B is a child of A, aligned
+ * Let's assume we have two [contentReceiver] boxes named A and B where B is a child of A, aligned
  * to bottom end.
  *
  * ---------
@@ -51,22 +55,22 @@
  *
  * The interesting part in this order of calls is that A does not receive an exit event when the
  * item moves over to B. This is different than what would happen if you were to use
- * [dragAndDropTarget] modifier because semantically [receiveContent] works as a chain of nodes.
+ * [dragAndDropTarget] modifier because semantically [contentReceiver] works as a chain of nodes.
  * If the item were to be dropped on B, its [onReceive] chain would also call A's [onReceive] with
  * what's left from B.
  */
 @ExperimentalFoundationApi
-interface ReceiveContentListener {
+fun interface ReceiveContentListener {
 
     /**
-     * Optional callback that's called when a dragging session starts. All [receiveContent] nodes
+     * Optional callback that's called when a dragging session starts. All [contentReceiver] nodes
      * in the current composition tree receives this callback immediately.
      */
     fun onDragStart() = Unit
 
     /**
      * Optional callback that's called when a dragging session ends by either successful drop, or
-     * cancellation. All [receiveContent] nodes in the current composition tree receives this
+     * cancellation. All [contentReceiver] nodes in the current composition tree receives this
      * callback immediately.
      */
     fun onDragEnd() = Unit
@@ -83,9 +87,9 @@
 
     /**
      * Callback that's triggered when a content is successfully committed.
-     * Return an optional [TransferableContent] that contains the ignored parts of the received
+     * @return An optional [TransferableContent] that contains the ignored parts of the received
      * [TransferableContent] by this node. The remaining [TransferableContent] first will be sent to
-     * to the closest ancestor [receiveContent] modifier. This chain will continue until there's no
+     * to the closest ancestor [contentReceiver] modifier. This chain will continue until there's no
      * ancestor modifier left, or [TransferableContent] is fully consumed. After, the source
      * subsystem that created the original [TransferableContent] and initiated the chain will
      * receive any remaining items to apply its default behavior. For example a text editor that
@@ -94,15 +98,3 @@
      */
     fun onReceive(transferableContent: TransferableContent): TransferableContent?
 }
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun ReceiveContentListener(
-    onReceive: (TransferableContent) -> TransferableContent?
-): ReceiveContentListener {
-    val paramOnReceive = onReceive
-    return object : ReceiveContentListener {
-        override fun onReceive(transferableContent: TransferableContent): TransferableContent? {
-            return paramOnReceive(transferableContent)
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/TransferableContent.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/TransferableContent.kt
index 00fb943..bc812cb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/TransferableContent.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/TransferableContent.kt
@@ -25,13 +25,13 @@
  *
  * Note; Consult platform-specific guidelines for best practices in content transfer operations.
  *
- * @param clipEntry The main content data, typically representing a text, image, file, or other
+ * @property clipEntry The main content data, typically representing a text, image, file, or other
  * transferable item.
- * @param source The source from which the content originated like Keyboard, DragAndDrop, or
+ * @property source The source from which the content originated like Keyboard, DragAndDrop, or
  * Clipboard.
- * @param clipMetadata Metadata associated with the content, providing additional information or
+ * @property clipMetadata Metadata associated with the content, providing additional information or
  * context.
- * @param platformTransferableContent Optional platform-specific representation of the content, or
+ * @property platformTransferableContent Optional platform-specific representation of the content, or
  * additional platform-specific information, that can be used to access platform level APIs.
  */
 @ExperimentalFoundationApi
@@ -51,10 +51,21 @@
 
         companion object {
 
+            /**
+             * Indicates that the [TransferableContent] originates from the soft keyboard (also
+             * known as input method editor or IME)
+             */
             val Keyboard = Source(0)
 
+            /**
+             * Indicates that the [TransferableContent] was passed on by the system drag and drop.
+             */
             val DragAndDrop = Source(1)
 
+            /**
+             * Indicates that the [TransferableContent] comes from the clipboard via paste.
+             * (e.g. "Paste" action in the floating action menu or "Ctrl+V" key combination)
+             */
             val Clipboard = Source(2)
         }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/ReceiveContentConfiguration.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/ReceiveContentConfiguration.kt
index dbe906e..cdf1193 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/ReceiveContentConfiguration.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/content/internal/ReceiveContentConfiguration.kt
@@ -19,16 +19,14 @@
 package androidx.compose.foundation.content.internal
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.content.MediaType
 import androidx.compose.foundation.content.ReceiveContentListener
 import androidx.compose.foundation.content.ReceiveContentNode
 import androidx.compose.foundation.content.TransferableContent
-import androidx.compose.foundation.content.receiveContent
+import androidx.compose.foundation.content.contentReceiver
 import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.modifier.modifierLocalOf
 
 internal abstract class ReceiveContentConfiguration {
-    abstract val hintMediaTypes: Set<MediaType>
     abstract val receiveContentListener: ReceiveContentListener
 
     fun onCommitContent(transferableContent: TransferableContent): Boolean {
@@ -38,16 +36,14 @@
 
     companion object {
         operator fun invoke(
-            hintMediaTypes: Set<MediaType>,
             receiveContentListener: ReceiveContentListener
         ): ReceiveContentConfiguration = ReceiveContentConfigurationImpl(
-            hintMediaTypes, receiveContentListener
+            receiveContentListener
         )
     }
 }
 
 private data class ReceiveContentConfigurationImpl(
-    override val hintMediaTypes: Set<MediaType>,
     override val receiveContentListener: ReceiveContentListener
 ) : ReceiveContentConfiguration()
 
@@ -76,54 +72,8 @@
 ) : ReceiveContentConfiguration() {
 
     /**
-     * The set of media types that were read from the ancestor nodes when [cachedHintMediaTypes]
-     * was last calculated.
-     */
-    private var lastParentHintMediaTypes: Set<MediaType>? = null
-
-    /**
-     * The set of media types that were configured for this node when [cachedHintMediaTypes]
-     * was last calculated.
-     */
-    private var lastHintMediaTypes: Set<MediaType>? = null
-
-    /**
-     * The merged set of [lastParentHintMediaTypes] and [lastHintMediaTypes]. [hintMediaTypes]
-     * should always return this value.
-     */
-    private var cachedHintMediaTypes: Set<MediaType> = receiveContentNode.hintMediaTypes
-
-    override val hintMediaTypes: Set<MediaType>
-        get() {
-            val fromParent = with(receiveContentNode) {
-                getReceiveContentConfiguration()?.hintMediaTypes
-            }
-            val fromNode = receiveContentNode.hintMediaTypes
-            var calculatedHintMediaTypes = when {
-                // do not allocate again. return the last merged set.
-                fromParent == lastParentHintMediaTypes && fromNode == lastHintMediaTypes ->
-                    cachedHintMediaTypes
-                // nothing coming from top, we can just return this node's configuration.
-                fromParent == null -> fromNode
-                // there's a change from the last calculation, recalculate
-                else -> fromNode + fromParent
-            }
-
-            if (calculatedHintMediaTypes.isEmpty()) {
-                calculatedHintMediaTypes = setOf(MediaType.All)
-            }
-
-            // after calculating the result, cache the inputs and the output before returning.
-            lastParentHintMediaTypes = fromParent
-            lastHintMediaTypes = fromNode
-            cachedHintMediaTypes = calculatedHintMediaTypes
-
-            return calculatedHintMediaTypes
-        }
-
-    /**
-     * A getter that returns the closest [receiveContent] modifier configuration if this node is
-     * attached. It returns null if the node is detached or there is no parent [receiveContent]
+     * A getter that returns the closest [contentReceiver] modifier configuration if this node is
+     * attached. It returns null if the node is detached or there is no parent [contentReceiver]
      * found.
      */
     private fun getParentReceiveContentListener(): ReceiveContentListener? {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
index 37b5001..2a082d1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropTarget.kt
@@ -63,10 +63,10 @@
         shouldStartDragAndDrop = shouldStartDragAndDrop,
     )
 
-    override fun update(node: DragAndDropTargetNode) = with(node) {
-        target = this@DropTargetElement.target
-        shouldStartDragAndDrop = this@DropTargetElement.shouldStartDragAndDrop
-    }
+    override fun update(node: DragAndDropTargetNode) = node.update(
+        target = target,
+        shouldStartDragAndDrop = shouldStartDragAndDrop
+    )
 
     override fun InspectorInfo.inspectableProperties() {
         name = "dropTarget"
@@ -91,14 +91,39 @@
 
 @ExperimentalFoundationApi
 private class DragAndDropTargetNode(
-    var shouldStartDragAndDrop: (event: DragAndDropEvent) -> Boolean,
-    var target: DragAndDropTarget
+    private var shouldStartDragAndDrop: (event: DragAndDropEvent) -> Boolean,
+    private var target: DragAndDropTarget
 ) : DelegatingNode() {
-    init {
-        delegate(
+
+    private var dragAndDropNode: DragAndDropModifierNode? = null
+
+    override fun onAttach() {
+        createAndAttachDragAndDropModifierNode()
+    }
+
+    fun update(
+        shouldStartDragAndDrop: (event: DragAndDropEvent) -> Boolean,
+        target: DragAndDropTarget
+    ) {
+        this.shouldStartDragAndDrop = shouldStartDragAndDrop
+        if (target != this.target) {
+            dragAndDropNode?.let { undelegate(it) }
+            this.target = target
+            createAndAttachDragAndDropModifierNode()
+        }
+    }
+
+    override fun onDetach() {
+        undelegate(dragAndDropNode!!)
+    }
+
+    private fun createAndAttachDragAndDropModifierNode() {
+        dragAndDropNode = delegate(
             DragAndDropModifierNode(
-                shouldStartDragAndDrop = shouldStartDragAndDrop,
-                target = target
+                // We wrap the this.shouldStartDragAndDrop invocation in a lambda as it might change over
+                // time, and updates to shouldStartDragAndDrop are not destructive.
+                shouldStartDragAndDrop = { this.shouldStartDragAndDrop(it) },
+                target = this.target
             )
         )
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index 0d055ac..9adcd08 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -60,6 +60,401 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
+// start Overscroll
+// This file contains both a Modifier.anchoredDraggable overload with overscroll and without
+// Everything below the Overscroll part should be copied to M2/M3 and kept in sync.
+/**
+ * Enable drag gestures between a set of predefined values.
+ *
+ * When a drag is detected, the offset of the [AnchoredDraggableState] will be updated with the drag
+ * delta. You should use this offset to move your content accordingly (see [Modifier.offset]).
+ * When the drag ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [AnchoredDraggableState] will also be updated to the value
+ * corresponding to the new anchor.
+ *
+ * Dragging is constrained between the minimum and maximum anchors.
+ *
+ * @param state The associated [AnchoredDraggableState].
+ * @param orientation The orientation in which the [anchoredDraggable] can be dragged.
+ * @param enabled Whether this [anchoredDraggable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the drag, so a top to bottom
+ * drag will behave like bottom to top, and a left to right drag will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to
+ * the internal [Modifier.draggable].
+ * @param overscrollEffect optional effect to dispatch any excess delta or velocity to. The excess
+ * delta or velocity are a result of dragging/flinging and reaching the bounds. If you provide an
+ * [overscrollEffect], make sure to apply [androidx.compose.foundation.overscroll] to render the
+ * effect as well.
+ * @param startDragImmediately when set to false, [draggable] will start dragging only when the
+ * gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
+ * widget when pressing on it. See [draggable] to learn more about startDragImmediately.
+ */
+@ExperimentalFoundationApi
+fun <T> Modifier.anchoredDraggable(
+    state: AnchoredDraggableState<T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    overscrollEffect: OverscrollEffect? = null,
+    startDragImmediately: Boolean = state.isAnimationRunning
+): Modifier = this then AnchoredDraggableOverscrollElement(
+    state = state,
+    orientation = orientation,
+    enabled = enabled,
+    reverseDirection = reverseDirection,
+    interactionSource = interactionSource,
+    overscrollEffect = overscrollEffect,
+    startDragImmediately = startDragImmediately
+)
+
+@OptIn(ExperimentalFoundationApi::class)
+private class AnchoredDraggableOverscrollElement<T>(
+    private val state: AnchoredDraggableState<T>,
+    private val orientation: Orientation,
+    private val enabled: Boolean,
+    private val reverseDirection: Boolean,
+    private val interactionSource: MutableInteractionSource?,
+    private val startDragImmediately: Boolean,
+    private val overscrollEffect: OverscrollEffect?,
+) : ModifierNodeElement<AnchoredDraggableOverscrollNode<T>>() {
+    override fun create() = AnchoredDraggableOverscrollNode(
+        state,
+        orientation,
+        enabled,
+        reverseDirection,
+        interactionSource,
+        startDragImmediately,
+        overscrollEffect,
+    )
+
+    override fun update(node: AnchoredDraggableOverscrollNode<T>) {
+        node.update(
+            state,
+            orientation,
+            enabled,
+            reverseDirection,
+            interactionSource,
+            overscrollEffect,
+            startDragImmediately
+        )
+    }
+
+    override fun hashCode(): Int {
+        var result = state.hashCode()
+        result = 31 * result + orientation.hashCode()
+        result = 31 * result + enabled.hashCode()
+        result = 31 * result + reverseDirection.hashCode()
+        result = 31 * result + interactionSource.hashCode()
+        result = 31 * result + startDragImmediately.hashCode()
+        result = 31 * result + overscrollEffect.hashCode()
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+
+        if (other !is AnchoredDraggableOverscrollElement<*>) return false
+
+        if (state != other.state) return false
+        if (orientation != other.orientation) return false
+        if (enabled != other.enabled) return false
+        if (reverseDirection != other.reverseDirection) return false
+        if (interactionSource != other.interactionSource) return false
+        if (startDragImmediately != other.startDragImmediately) return false
+        if (overscrollEffect != other.overscrollEffect) return false
+
+        return true
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "anchoredDraggable"
+        properties["state"] = state
+        properties["orientation"] = orientation
+        properties["enabled"] = enabled
+        properties["reverseDirection"] = reverseDirection
+        properties["interactionSource"] = interactionSource
+        properties["startDragImmediately"] = startDragImmediately
+        properties["overscrollEffect"] = overscrollEffect
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private class AnchoredDraggableOverscrollNode<T>(
+    state: AnchoredDraggableState<T>,
+    orientation: Orientation,
+    enabled: Boolean,
+    reverseDirection: Boolean,
+    interactionSource: MutableInteractionSource?,
+    startDragImmediately: Boolean,
+    private var overscrollEffect: OverscrollEffect?,
+) : AnchoredDraggableNode<T>(
+    state = state,
+    orientation = orientation,
+    enabled = enabled,
+    reverseDirection = reverseDirection,
+    interactionSource = interactionSource,
+    startDragImmediately = startDragImmediately
+) {
+    override suspend fun AnchoredDragScope.anchoredDrag(
+        forEachDelta: suspend ((dragDelta: DragEvent.DragDelta) -> Unit) -> Unit
+    ) {
+        forEachDelta { dragDelta ->
+            if (overscrollEffect == null) {
+                dragTo(state.newOffsetForDelta(dragDelta.delta.reverseIfNeeded().toFloat()))
+            } else {
+                overscrollEffect!!.applyToScroll(
+                    delta = dragDelta.delta.reverseIfNeeded(),
+                    source = NestedScrollSource.Drag
+                ) { deltaForDrag ->
+                    val dragOffset = state.newOffsetForDelta(deltaForDrag.toFloat())
+                    val consumedDelta = (dragOffset - state.requireOffset()).toOffset()
+                    dragTo(dragOffset)
+                    consumedDelta
+                }
+            }
+        }
+    }
+
+    override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) {
+        if (overscrollEffect == null) {
+            state.settle(velocity.reverseIfNeeded().toFloat()).toVelocity()
+        } else {
+            overscrollEffect!!.applyToFling(
+                velocity = velocity.reverseIfNeeded()
+            ) { availableVelocity ->
+                val consumed = state.settle(availableVelocity.toFloat()).toVelocity()
+                val currentOffset = state.requireOffset()
+                val minAnchor = state.anchors.minAnchor()
+                val maxAnchor = state.anchors.maxAnchor()
+                // return consumed velocity only if we are reaching the min/max anchors
+                if (currentOffset >= maxAnchor || currentOffset <= minAnchor) {
+                    consumed
+                } else {
+                    availableVelocity
+                }
+            }
+        }
+    }
+
+    fun update(
+        state: AnchoredDraggableState<T>,
+        orientation: Orientation,
+        enabled: Boolean,
+        reverseDirection: Boolean,
+        interactionSource: MutableInteractionSource?,
+        overscrollEffect: OverscrollEffect?,
+        startDragImmediately: Boolean
+    ) {
+        this.overscrollEffect = overscrollEffect
+        update(
+            state = state,
+            orientation = orientation,
+            enabled = enabled,
+            reverseDirection = reverseDirection,
+            interactionSource = interactionSource,
+            startDragImmediately = startDragImmediately
+        )
+    }
+}
+// end Overscroll
+
+/**
+ * Enable drag gestures between a set of predefined values.
+ *
+ * When a drag is detected, the offset of the [AnchoredDraggableState] will be updated with the drag
+ * delta. You should use this offset to move your content accordingly (see [Modifier.offset]).
+ * When the drag ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [AnchoredDraggableState] will also be updated to the value
+ * corresponding to the new anchor.
+ *
+ * Dragging is constrained between the minimum and maximum anchors.
+ *
+ * @param state The associated [AnchoredDraggableState].
+ * @param orientation The orientation in which the [anchoredDraggable] can be dragged.
+ * @param enabled Whether this [anchoredDraggable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the drag, so a top to bottom
+ * drag will behave like bottom to top, and a left to right drag will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to
+ * the internal [Modifier.draggable].
+ * @param startDragImmediately when set to false, [draggable] will start dragging only when the
+ * gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
+ * widget when pressing on it. See [draggable] to learn more about startDragImmediately.
+ */
+@ExperimentalFoundationApi
+fun <T> Modifier.anchoredDraggable(
+    state: AnchoredDraggableState<T>,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    reverseDirection: Boolean = false,
+    interactionSource: MutableInteractionSource? = null,
+    startDragImmediately: Boolean = state.isAnimationRunning
+): Modifier = this then AnchoredDraggableElement(
+    state = state,
+    orientation = orientation,
+    enabled = enabled,
+    reverseDirection = reverseDirection,
+    interactionSource = interactionSource,
+    startDragImmediately = startDragImmediately
+)
+
+@OptIn(ExperimentalFoundationApi::class)
+private class AnchoredDraggableElement<T>(
+    private val state: AnchoredDraggableState<T>,
+    private val orientation: Orientation,
+    private val enabled: Boolean,
+    private val reverseDirection: Boolean,
+    private val interactionSource: MutableInteractionSource?,
+    private val startDragImmediately: Boolean
+) : ModifierNodeElement<AnchoredDraggableNode<T>>() {
+    override fun create() = AnchoredDraggableNode(
+        state,
+        orientation,
+        enabled,
+        reverseDirection,
+        interactionSource,
+        startDragImmediately
+    )
+
+    override fun update(node: AnchoredDraggableNode<T>) {
+        node.update(
+            state,
+            orientation,
+            enabled,
+            reverseDirection,
+            interactionSource,
+            startDragImmediately
+        )
+    }
+
+    override fun hashCode(): Int {
+        var result = state.hashCode()
+        result = 31 * result + orientation.hashCode()
+        result = 31 * result + enabled.hashCode()
+        result = 31 * result + reverseDirection.hashCode()
+        result = 31 * result + interactionSource.hashCode()
+        result = 31 * result + startDragImmediately.hashCode()
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+
+        if (other !is AnchoredDraggableElement<*>) return false
+
+        if (state != other.state) return false
+        if (orientation != other.orientation) return false
+        if (enabled != other.enabled) return false
+        if (reverseDirection != other.reverseDirection) return false
+        if (interactionSource != other.interactionSource) return false
+        if (startDragImmediately != other.startDragImmediately) return false
+
+        return true
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "anchoredDraggable"
+        properties["state"] = state
+        properties["orientation"] = orientation
+        properties["enabled"] = enabled
+        properties["reverseDirection"] = reverseDirection
+        properties["interactionSource"] = interactionSource
+        properties["startDragImmediately"] = startDragImmediately
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private open class AnchoredDraggableNode<T>(
+    protected var state: AnchoredDraggableState<T>,
+    protected var orientation: Orientation,
+    enabled: Boolean,
+    protected var reverseDirection: Boolean,
+    interactionSource: MutableInteractionSource?,
+    protected var startDragImmediately: Boolean
+) : DragGestureNode(
+    canDrag = AlwaysDrag,
+    enabled = enabled,
+    interactionSource = interactionSource
+) {
+
+    open suspend fun AnchoredDragScope.anchoredDrag(
+        forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit
+    ) {
+        forEachDelta { dragDelta ->
+            dragTo(state.newOffsetForDelta(dragDelta.delta.reverseIfNeeded().toFloat()))
+        }
+    }
+
+    override suspend fun drag(forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit) {
+        state.anchoredDrag(MutatePriority.Default) { anchoredDrag(forEachDelta) }
+    }
+
+    override val pointerDirectionConfig: PointerDirectionConfig
+        get() = orientation.toPointerDirectionConfig()
+
+    override suspend fun CoroutineScope.onDragStarted(startedPosition: Offset) {}
+
+    override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) {
+        state.settle(velocity.reverseIfNeeded().toFloat()).toVelocity()
+    }
+
+    override fun startDragImmediately(): Boolean = startDragImmediately
+
+    fun update(
+        state: AnchoredDraggableState<T>,
+        orientation: Orientation,
+        enabled: Boolean,
+        reverseDirection: Boolean,
+        interactionSource: MutableInteractionSource?,
+        startDragImmediately: Boolean
+    ) {
+        var resetPointerInputHandling = false
+
+        if (this.state != state) {
+            this.state = state
+            resetPointerInputHandling = true
+        }
+        if (this.orientation != orientation) {
+            this.orientation = orientation
+            resetPointerInputHandling = true
+        }
+
+        if (this.reverseDirection != reverseDirection) {
+            this.reverseDirection = reverseDirection
+            resetPointerInputHandling = true
+        }
+
+        this.startDragImmediately = startDragImmediately
+
+        update(
+            enabled = enabled,
+            interactionSource = interactionSource,
+            isResetPointerInputHandling = resetPointerInputHandling,
+        )
+    }
+
+    protected fun Float.toOffset() = Offset(
+        x = if (orientation == Orientation.Horizontal) this else 0f,
+        y = if (orientation == Orientation.Vertical) this else 0f,
+    )
+
+    protected fun Float.toVelocity() = Velocity(
+        x = if (orientation == Orientation.Horizontal) this else 0f,
+        y = if (orientation == Orientation.Vertical) this else 0f,
+    )
+
+    protected fun Velocity.toFloat() =
+        if (orientation == Orientation.Vertical) this.y else this.x
+
+    protected fun Offset.toFloat() =
+        if (orientation == Orientation.Vertical) this.y else this.x
+
+    protected fun Velocity.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
+    protected fun Offset.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
+}
+
+private val AlwaysDrag: (PointerInputChange) -> Boolean = { true }
+
 /**
  * Structure that represents the anchors of a [AnchoredDraggableState].
  *
@@ -183,244 +578,6 @@
 }
 
 /**
- * Enable drag gestures between a set of predefined values.
- *
- * When a drag is detected, the offset of the [AnchoredDraggableState] will be updated with the drag
- * delta. You should use this offset to move your content accordingly (see [Modifier.offset]).
- * When the drag ends, the offset will be animated to one of the anchors and when that anchor is
- * reached, the value of the [AnchoredDraggableState] will also be updated to the value
- * corresponding to the new anchor.
- *
- * Dragging is constrained between the minimum and maximum anchors.
- *
- * @param state The associated [AnchoredDraggableState].
- * @param orientation The orientation in which the [anchoredDraggable] can be dragged.
- * @param enabled Whether this [anchoredDraggable] is enabled and should react to the user's input.
- * @param reverseDirection Whether to reverse the direction of the drag, so a top to bottom
- * drag will behave like bottom to top, and a left to right drag will behave like right to left.
- * @param interactionSource Optional [MutableInteractionSource] that will passed on to
- * the internal [Modifier.draggable].
- * @param overscrollEffect optional effect to dispatch any excess delta or velocity to. The excess
- * delta or velocity are a result of dragging/flinging and reaching the bounds. If you provide an
- * [overscrollEffect], make sure to apply [androidx.compose.foundation.overscroll] to render the
- * effect as well.
- * @param startDragImmediately when set to false, [draggable] will start dragging only when the
- * gesture crosses the touchSlop. This is useful to prevent users from "catching" an animating
- * widget when pressing on it. See [draggable] to learn more about startDragImmediately.
- */
-@ExperimentalFoundationApi
-fun <T> Modifier.anchoredDraggable(
-    state: AnchoredDraggableState<T>,
-    orientation: Orientation,
-    enabled: Boolean = true,
-    reverseDirection: Boolean = false,
-    interactionSource: MutableInteractionSource? = null,
-    overscrollEffect: OverscrollEffect? = null,
-    startDragImmediately: Boolean = state.isAnimationRunning
-): Modifier = this then AnchoredDraggableElement(
-    state = state,
-    orientation = orientation,
-    enabled = enabled,
-    reverseDirection = reverseDirection,
-    interactionSource = interactionSource,
-    overscrollEffect = overscrollEffect,
-    startDragImmediately = startDragImmediately
-)
-
-@OptIn(ExperimentalFoundationApi::class)
-private class AnchoredDraggableElement<T>(
-    private val state: AnchoredDraggableState<T>,
-    private val orientation: Orientation,
-    private val enabled: Boolean,
-    private val reverseDirection: Boolean,
-    private val interactionSource: MutableInteractionSource?,
-    private val overscrollEffect: OverscrollEffect?,
-    private val startDragImmediately: Boolean
-) : ModifierNodeElement<AnchoredDraggableNode<T>>() {
-    override fun create(): AnchoredDraggableNode<T> {
-        return AnchoredDraggableNode(
-            state,
-            orientation,
-            enabled,
-            reverseDirection,
-            interactionSource,
-            overscrollEffect,
-            { startDragImmediately }
-        )
-    }
-
-    override fun update(node: AnchoredDraggableNode<T>) {
-        node.update(
-            state,
-            orientation,
-            enabled,
-            reverseDirection,
-            interactionSource,
-            overscrollEffect,
-            { startDragImmediately }
-        )
-    }
-
-    override fun hashCode(): Int {
-        var result = state.hashCode()
-        result = 31 * result + orientation.hashCode()
-        result = 31 * result + enabled.hashCode()
-        result = 31 * result + reverseDirection.hashCode()
-        result = 31 * result + interactionSource.hashCode()
-        result = 31 * result + overscrollEffect.hashCode()
-        result = 31 * result + startDragImmediately.hashCode()
-        return result
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-
-        if (other !is AnchoredDraggableElement<*>) return false
-
-        if (state != other.state) return false
-        if (orientation != other.orientation) return false
-        if (enabled != other.enabled) return false
-        if (reverseDirection != other.reverseDirection) return false
-        if (interactionSource != other.interactionSource) return false
-        if (overscrollEffect != other.overscrollEffect) return false
-        if (startDragImmediately != other.startDragImmediately) return false
-
-        return true
-    }
-
-    override fun InspectorInfo.inspectableProperties() {
-        name = "anchoredDraggable"
-        properties["state"] = state
-        properties["orientation"] = orientation
-        properties["enabled"] = enabled
-        properties["reverseDirection"] = reverseDirection
-        properties["interactionSource"] = interactionSource
-        properties["overscrollEffect"] = overscrollEffect
-        properties["startDragImmediately"] = startDragImmediately
-    }
-}
-
-@ExperimentalFoundationApi
-private class AnchoredDraggableNode<T>(
-    private var state: AnchoredDraggableState<T>,
-    private var orientation: Orientation,
-    enabled: Boolean,
-    private var reverseDirection: Boolean,
-    interactionSource: MutableInteractionSource?,
-    private var overscrollEffect: OverscrollEffect?,
-    private var startDragImmediately: () -> Boolean
-) : DragGestureNode(
-    canDrag = AlwaysDrag,
-    enabled = enabled,
-    interactionSource = interactionSource
-) {
-
-    override suspend fun drag(forEachDelta: suspend ((dragDelta: DragDelta) -> Unit) -> Unit) {
-        state.anchoredDrag(MutatePriority.Default) {
-            forEachDelta { dragDelta ->
-                if (overscrollEffect == null) {
-                    dragTo(state.newOffsetForDelta(dragDelta.delta.reverseIfNeeded().toFloat()))
-                } else {
-                    overscrollEffect!!.applyToScroll(
-                        delta = dragDelta.delta.reverseIfNeeded(),
-                        source = NestedScrollSource.Drag
-                    ) { deltaForDrag ->
-                        val dragOffset = state.newOffsetForDelta(deltaForDrag.toFloat())
-                        val consumedDelta = (dragOffset - state.requireOffset()).toOffset()
-                        dragTo(dragOffset)
-                        consumedDelta
-                    }
-                }
-            }
-        }
-    }
-
-    override val pointerDirectionConfig: PointerDirectionConfig
-        get() = orientation.toPointerDirectionConfig()
-
-    override suspend fun CoroutineScope.onDragStarted(startedPosition: Offset) {}
-
-    override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) {
-        if (overscrollEffect == null) {
-            state.settle(velocity.reverseIfNeeded().toFloat()).toVelocity()
-        } else {
-            overscrollEffect!!.applyToFling(
-                velocity = velocity.reverseIfNeeded()
-            ) { availableVelocity ->
-                val consumed = state.settle(availableVelocity.toFloat()).toVelocity()
-                val currentOffset = state.requireOffset()
-                val minAnchor = state.anchors.minAnchor()
-                val maxAnchor = state.anchors.maxAnchor()
-                // return consumed velocity only if we are reaching the min/max anchors
-                if (currentOffset >= maxAnchor || currentOffset <= minAnchor) {
-                    consumed
-                } else {
-                    availableVelocity
-                }
-            }
-        }
-    }
-
-    override fun startDragImmediately(): Boolean = startDragImmediately.invoke()
-
-    fun update(
-        state: AnchoredDraggableState<T>,
-        orientation: Orientation,
-        enabled: Boolean,
-        reverseDirection: Boolean,
-        interactionSource: MutableInteractionSource?,
-        overscrollEffect: OverscrollEffect?,
-        startDragImmediately: () -> Boolean
-    ) {
-        var resetPointerInputHandling = false
-
-        if (this.state != state) {
-            this.state = state
-            resetPointerInputHandling = true
-        }
-        if (this.orientation != orientation) {
-            this.orientation = orientation
-            resetPointerInputHandling = true
-        }
-
-        if (this.reverseDirection != reverseDirection) {
-            this.reverseDirection = reverseDirection
-            resetPointerInputHandling = true
-        }
-
-        this.overscrollEffect = overscrollEffect
-        this.startDragImmediately = startDragImmediately
-
-        update(
-            enabled = enabled,
-            interactionSource = interactionSource,
-            isResetPointerInputHandling = resetPointerInputHandling,
-        )
-    }
-
-    private fun Float.toOffset() = Offset(
-        x = if (orientation == Orientation.Horizontal) this else 0f,
-        y = if (orientation == Orientation.Vertical) this else 0f,
-    )
-
-    fun Float.toVelocity() = Velocity(
-        x = if (orientation == Orientation.Horizontal) this else 0f,
-        y = if (orientation == Orientation.Vertical) this else 0f,
-    )
-
-    private fun Velocity.toFloat() =
-        if (orientation == Orientation.Vertical) this.y else this.x
-
-    private fun Offset.toFloat() =
-        if (orientation == Orientation.Vertical) this.y else this.x
-
-    private fun Velocity.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
-    private fun Offset.reverseIfNeeded() = if (reverseDirection) this * -1f else this * 1f
-}
-
-private val AlwaysDrag: (PointerInputChange) -> Boolean = { true }
-
-/**
  * State of the [anchoredDraggable] modifier.
  * Use the constructor overload with anchors if the anchors are defined in composition, or update
  * the anchors using [updateAnchors].
@@ -732,11 +889,29 @@
                 // In the first invocation, we do not have a direction. The previous offset will be
                 // NaN in the first invocation of dragTo; so we will only initialize in the second
                 // invocation when we have a direction to calculate the thresholds with
-                update(isMovingForward)
+                // update(isMovingForward)
+                initialize(isMovingForward)
+                val crossedThresholdTowardsNextAnchor = if (isMovingForward) {
+                    newOffset >= absoluteThresholdToCross
+                } else {
+                    newOffset <= absoluteThresholdToCross
+                }
+                if (crossedThresholdTowardsNextAnchor) {
+                    update(isMovingForward)
+                }
                 initialized = true
             }
         }
 
+        fun initialize(isMovingForward: Boolean) {
+            val currentAnchorPosition = anchors.positionOf(currentValue)
+            val nextAnchor = anchors.closestAnchor(offset, isMovingForward) ?: currentValue
+            val nextAnchorPosition = anchors.positionOf(nextAnchor!!)
+            val relativeThreshold = (nextAnchorPosition - currentAnchorPosition) / 2f
+            absoluteThresholdToCross = currentAnchorPosition + relativeThreshold
+            nextValue = nextAnchor
+        }
+
         fun update(isMovingForward: Boolean) {
             val currentAnchorPosition = anchors.positionOf(currentValue)
             min = anchors.minAnchor()
@@ -785,11 +960,13 @@
                 anchoredDragScope.block(latestAnchors)
             }
             val closest = anchors.closestAnchor(offset)
-            if (closest != null && confirmValueChange.invoke(closest)) {
+            if (closest != null) {
                 val closestAnchorOffset = anchors.positionOf(closest)
-                anchoredDragScope.dragTo(closestAnchorOffset, lastVelocity)
-                settledValue = closest
-                currentValue = closest
+                val isAtClosestAnchor = abs(offset - closestAnchorOffset) < 0.5f
+                if (isAtClosestAnchor && confirmValueChange.invoke(closest)) {
+                    settledValue = closest
+                    currentValue = closest
+                }
             }
         }
     }
@@ -938,9 +1115,9 @@
 ) {
     with(anchoredDragScope) {
         val targetOffset = anchors.positionOf(latestTarget)
-        if (!targetOffset.isNaN()) {
+        var prev = if (offset.isNaN()) 0f else offset
+        if (!targetOffset.isNaN() && prev != targetOffset) {
             debugLog { "Target animation is used" }
-            var prev = if (offset.isNaN()) 0f else offset
             animate(prev, targetOffset, velocity, snapAnimationSpec) { value, velocity ->
                 // Our onDrag coerces the value within the bounds, but an animation may
                 // overshoot, for example a spring animation or an overshooting interpolator
@@ -997,44 +1174,48 @@
         val targetOffset = anchors.positionOf(latestTarget)
         if (!targetOffset.isNaN()) {
             var prev = if (offset.isNaN()) 0f else offset
-            // If targetOffset is not in the same direction as the direction of the drag (sign
-            // of the velocity) we fall back to using target animation.
-            // If the component is at the target offset already, we use decay animation that will
-            // not consume any velocity.
-            if (velocity * (targetOffset - prev) < 0f || velocity == 0f) {
-                animateTo(velocity, this, anchors, latestTarget)
-                remainingVelocity = 0f
-            } else {
-                val projectedDecayOffset = decayAnimationSpec.calculateTargetValue(prev, velocity)
-                debugLog {
-                    "offset = $prev\tvelocity = $velocity\t" +
-                        "targetOffset = $targetOffset\tprojectedOffset = $projectedDecayOffset"
-                }
-
-                val canDecayToTarget = if (velocity > 0) {
-                    projectedDecayOffset >= targetOffset
-                } else {
-                    projectedDecayOffset <= targetOffset
-                }
-                if (canDecayToTarget) {
-                    debugLog { "Decay animation is used" }
-                    AnimationState(prev, velocity)
-                        .animateDecay(decayAnimationSpec) {
-                            if (abs(value) >= abs(targetOffset)) {
-                                val finalValue = value.coerceToTarget(targetOffset)
-                                dragTo(finalValue, this.velocity)
-                                remainingVelocity = if (this.velocity.isNaN()) 0f else this.velocity
-                                prev = finalValue
-                                cancelAnimation()
-                            } else {
-                                dragTo(value, this.velocity)
-                                remainingVelocity = this.velocity
-                                prev = value
-                            }
-                        }
-                } else {
+            if (prev != targetOffset) {
+                // If targetOffset is not in the same direction as the direction of the drag (sign
+                // of the velocity) we fall back to using target animation.
+                // If the component is at the target offset already, we use decay animation that will
+                // not consume any velocity.
+                if (velocity * (targetOffset - prev) < 0f || velocity == 0f) {
                     animateTo(velocity, this, anchors, latestTarget)
                     remainingVelocity = 0f
+                } else {
+                    val projectedDecayOffset =
+                        decayAnimationSpec.calculateTargetValue(prev, velocity)
+                    debugLog {
+                        "offset = $prev\tvelocity = $velocity\t" +
+                            "targetOffset = $targetOffset\tprojectedOffset = $projectedDecayOffset"
+                    }
+
+                    val canDecayToTarget = if (velocity > 0) {
+                        projectedDecayOffset >= targetOffset
+                    } else {
+                        projectedDecayOffset <= targetOffset
+                    }
+                    if (canDecayToTarget) {
+                        debugLog { "Decay animation is used" }
+                        AnimationState(prev, velocity)
+                            .animateDecay(decayAnimationSpec) {
+                                if (abs(value) >= abs(targetOffset)) {
+                                    val finalValue = value.coerceToTarget(targetOffset)
+                                    dragTo(finalValue, this.velocity)
+                                    remainingVelocity =
+                                        if (this.velocity.isNaN()) 0f else this.velocity
+                                    prev = finalValue
+                                    cancelAnimation()
+                                } else {
+                                    dragTo(value, this.velocity)
+                                    remainingVelocity = this.velocity
+                                    prev = value
+                                }
+                            }
+                    } else {
+                        animateTo(velocity, this, anchors, latestTarget)
+                        remainingVelocity = 0f
+                    }
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
index 46d913c..9976dc1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
@@ -102,8 +102,6 @@
         private set
 
     private var isAnimationRunning = false
-    private val animationState =
-        UpdatableAnimationState(bringIntoViewSpec.scrollAnimationSpec)
 
     override fun calculateRectForParent(localRect: Rect): Rect {
         check(viewportSize != IntSize.Zero) {
@@ -176,7 +174,7 @@
         check(!isAnimationRunning) { "launchAnimation called when previous animation was running" }
 
         if (DEBUG) println("[$TAG] launchAnimation")
-
+        val animationState = UpdatableAnimationState(bringIntoViewSpec.scrollAnimationSpec)
         coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
             var cancellationException: CancellationException? = null
             val animationJob = coroutineContext.job
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index 1b92b0e..f95084a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -222,7 +222,7 @@
         orientation,
         enabled,
         interactionSource,
-        if (startDragImmediately) StartDragImmediately else DoNotStartDragImmediately,
+        startDragImmediately,
         onDragStarted,
         onDragStopped,
         reverseDirection
@@ -235,7 +235,7 @@
             orientation,
             enabled,
             interactionSource,
-            if (startDragImmediately) StartDragImmediately else DoNotStartDragImmediately,
+            startDragImmediately,
             onDragStarted,
             onDragStopped,
             reverseDirection
@@ -286,8 +286,6 @@
     }
 
     companion object {
-        val StartDragImmediately = { true }
-        val DoNotStartDragImmediately = { false }
         val CanDrag: (PointerInputChange) -> Boolean = { true }
     }
 }
@@ -298,7 +296,7 @@
     private var orientation: Orientation,
     enabled: Boolean,
     interactionSource: MutableInteractionSource?,
-    private var startDragImmediately: () -> Boolean,
+    private var startDragImmediately: Boolean,
     private var onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
     private var onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit,
     private var reverseDirection: Boolean
@@ -324,7 +322,7 @@
     override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) =
         this@DraggableNode.onDragStopped(this, velocity.reverseIfNeeded().toFloat(orientation))
 
-    override fun startDragImmediately(): Boolean = startDragImmediately.invoke()
+    override fun startDragImmediately(): Boolean = startDragImmediately
 
     fun update(
         state: DraggableState,
@@ -332,7 +330,7 @@
         orientation: Orientation,
         enabled: Boolean,
         interactionSource: MutableInteractionSource?,
-        startDragImmediately: () -> Boolean,
+        startDragImmediately: Boolean,
         onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit,
         onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit,
         reverseDirection: Boolean
@@ -371,16 +369,24 @@
  * A node that performs drag gesture recognition and event propagation.
  */
 internal abstract class DragGestureNode(
-    private var canDrag: (PointerInputChange) -> Boolean,
-    private var enabled: Boolean,
-    private var interactionSource: MutableInteractionSource?,
+    canDrag: (PointerInputChange) -> Boolean,
+    enabled: Boolean,
+    interactionSource: MutableInteractionSource?,
 ) : DelegatingNode(), PointerInputModifierNode, CompositionLocalConsumerModifierNode {
 
+    protected var canDrag = canDrag
+        private set
+    protected var enabled = enabled
+        private set
+    protected var interactionSource = interactionSource
+        private set
+
     // Use wrapper lambdas here to make sure that if these properties are updated while we suspend,
     // we point to the new reference when we invoke them. startDragImmediately is a lambda since we
     // need the most recent value passed to it from Scrollable.
-    private val _canDrag: (PointerInputChange) -> Boolean = { canDrag(it) }
-    private val velocityTracker = VelocityTracker()
+    private val _canDrag: (PointerInputChange) -> Boolean = { this.canDrag(it) }
+    private var channel: Channel<DragEvent>? = null
+    private var dragInteraction: DragInteraction.Start? = null
     private var isListeningForEvents = false
 
     /**
@@ -427,14 +433,14 @@
          */
         coroutineScope.launch {
             while (isActive) {
-                var event = channel.receive()
+                var event = channel?.receive()
                 if (event !is DragStarted) continue
                 processDragStart(event)
                 try {
                     drag { processDelta ->
                         while (event !is DragStopped && event !is DragCancelled) {
                             (event as? DragDelta)?.let(processDelta)
-                            event = channel.receive()
+                            event = channel?.receive()
                         }
                     }
                     if (event is DragStopped) {
@@ -451,7 +457,10 @@
 
     private val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
         // TODO: conditionally undelegate when aosp/2462416 lands?
-        if (!enabled) return@SuspendingPointerInputModifierNode
+        if (!this@DragGestureNode.enabled) return@SuspendingPointerInputModifierNode
+        // re-create tracker when pointer input block restarts. This lazily creates the tracker
+        // only when it is need.
+        val velocityTracker = VelocityTracker()
         coroutineScope {
             try {
                 awaitPointerEventScope {
@@ -467,6 +476,9 @@
                              * and should be propagated
                              */
                             if (!isListeningForEvents) {
+                                if (channel == null) {
+                                    channel = Channel(capacity = Channel.UNLIMITED)
+                                }
                                 startListeningForEvents()
                             }
                             var isDragSuccessful = false
@@ -496,7 +508,7 @@
                                 } else {
                                     DragCancelled
                                 }
-                                channel.trySend(event)
+                                channel?.trySend(event)
                             }
                         }
                     }
@@ -509,9 +521,6 @@
         }
     })
 
-    private val channel = Channel<DragEvent>(capacity = Channel.UNLIMITED)
-    private var dragInteraction: DragInteraction.Start? = null
-
     override fun onDetach() {
         isListeningForEvents = false
         disposeInteractionSource()
@@ -628,7 +637,7 @@
     startEvent: PointerInputChange,
     initialDelta: Offset,
     velocityTracker: VelocityTracker,
-    channel: SendChannel<DragEvent>,
+    channel: SendChannel<DragEvent>?,
     hasDragged: (PointerInputChange) -> Boolean,
 ): Boolean {
 
@@ -637,9 +646,9 @@
     val ySign = sign(startEvent.position.y)
     val adjustedStart = startEvent.position -
         Offset(overSlopOffset.x * xSign, overSlopOffset.y * ySign)
-    channel.trySend(DragStarted(adjustedStart))
+    channel?.trySend(DragStarted(adjustedStart))
 
-    channel.trySend(DragDelta(initialDelta))
+    channel?.trySend(DragDelta(initialDelta))
 
     return onDragOrUp(hasDragged, startEvent.id) { event ->
         // Velocity tracker takes all events, even UP
@@ -649,7 +658,7 @@
         if (!event.changedToUpIgnoreConsumed()) {
             val delta = event.positionChange()
             event.consume()
-            channel.trySend(DragDelta(delta))
+            channel?.trySend(DragDelta(delta))
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 1d76f11..7cafe9d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -29,6 +29,7 @@
 import androidx.compose.foundation.OverscrollEffect
 import androidx.compose.foundation.gestures.BringIntoViewSpec.Companion.DefaultBringIntoViewSpec
 import androidx.compose.foundation.gestures.Orientation.Horizontal
+import androidx.compose.foundation.gestures.Orientation.Vertical
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.relocation.BringIntoViewResponderNode
 import androidx.compose.foundation.rememberOverscrollEffect
@@ -55,22 +56,18 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Fling
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Wheel
 import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
-import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerType
-import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.modifier.ModifierLocalMap
-import androidx.compose.ui.modifier.ModifierLocalModifierNode
-import androidx.compose.ui.modifier.modifierLocalMapOf
-import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.ObserverModifierNode
+import androidx.compose.ui.node.TraversableNode
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.node.requireDensity
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -161,7 +158,7 @@
  * scrolling when scroll requests are received from the focus system.
  *
  * Note: This API is experimental as it brings support for some experimental features:
- * [overscrollEffect] and [bringIntoViewScroller].
+ * [overscrollEffect] and [bringIntoViewSpec].
  */
 @Stable
 @ExperimentalFoundationApi
@@ -199,11 +196,11 @@
     override fun create(): ScrollableNode {
         return ScrollableNode(
             state,
-            orientation,
             overscrollEffect,
+            flingBehavior,
+            orientation,
             enabled,
             reverseDirection,
-            flingBehavior,
             interactionSource,
             bringIntoViewSpec
         )
@@ -260,29 +257,36 @@
         properties["reverseDirection"] = reverseDirection
         properties["flingBehavior"] = flingBehavior
         properties["interactionSource"] = interactionSource
-        properties["scrollableBringIntoViewConfig"] = bringIntoViewSpec
+        properties["bringIntoViewSpec"] = bringIntoViewSpec
     }
 }
 
 @OptIn(ExperimentalFoundationApi::class)
 private class ScrollableNode(
-    private var state: ScrollableState,
-    private var orientation: Orientation,
+    state: ScrollableState,
     private var overscrollEffect: OverscrollEffect?,
-    private var enabled: Boolean,
-    private var reverseDirection: Boolean,
     private var flingBehavior: FlingBehavior?,
-    private var interactionSource: MutableInteractionSource?,
+    orientation: Orientation,
+    enabled: Boolean,
+    reverseDirection: Boolean,
+    interactionSource: MutableInteractionSource?,
     bringIntoViewSpec: BringIntoViewSpec
-) : DelegatingNode(), ObserverModifierNode, CompositionLocalConsumerModifierNode,
+) : DragGestureNode(
+    canDrag = CanDragCalculation,
+    enabled = enabled,
+    interactionSource = interactionSource
+), ObserverModifierNode, CompositionLocalConsumerModifierNode,
     FocusPropertiesModifierNode, KeyInputModifierNode {
 
-    val nestedScrollDispatcher = NestedScrollDispatcher()
+    private val nestedScrollDispatcher = NestedScrollDispatcher()
+
+    private val scrollableContainerNode =
+        delegate(ScrollableContainerNode(enabled))
 
     // Place holder fling behavior, we'll initialize it when the density is available.
-    val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity))
+    private val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity))
 
-    val scrollingLogic = ScrollingLogic(
+    private val scrollingLogic = ScrollingLogic(
         scrollableState = state,
         orientation = orientation,
         overscrollEffect = overscrollEffect,
@@ -291,10 +295,10 @@
         nestedScrollDispatcher = nestedScrollDispatcher,
     )
 
-    val nestedScrollConnection =
+    private val nestedScrollConnection =
         ScrollableNestedScrollConnection(enabled = enabled, scrollingLogic = scrollingLogic)
 
-    val contentInViewNode =
+    private val contentInViewNode =
         delegate(
             ContentInViewNode(
                 orientation,
@@ -303,7 +307,10 @@
                 bringIntoViewSpec
             )
         )
-    val scrollableContainer = delegate(ModifierLocalScrollableContainerProvider(enabled))
+
+    // Need to wait until onAttach to read the scroll config. Currently this is static, so we
+    // don't need to worry about observation / updating this over time.
+    private var scrollConfig: ScrollConfig? = null
 
     init {
         /**
@@ -319,18 +326,26 @@
         delegate(FocusedBoundsObserverNode { contentInViewNode.onFocusBoundsChanged(it) })
     }
 
-    /**
-     * Pointer gesture handling
-     */
-    val scrollableGesturesNode = delegate(
-        ScrollableGesturesNode(
-            interactionSource = interactionSource,
-            orientation = orientation,
-            enabled = enabled,
-            nestedScrollDispatcher = nestedScrollDispatcher,
-            scrollLogic = scrollingLogic
-        )
-    )
+    override suspend fun drag(
+        forEachDelta: suspend ((dragDelta: DragEvent.DragDelta) -> Unit) -> Unit
+    ) {
+        scrollingLogic.dispatchDragEvents(forEachDelta)
+    }
+
+    override val pointerDirectionConfig: PointerDirectionConfig
+        get() = scrollingLogic.pointerDirectionConfig()
+
+    override suspend fun CoroutineScope.onDragStarted(startedPosition: Offset) {}
+
+    override suspend fun CoroutineScope.onDragStopped(velocity: Velocity) {
+        nestedScrollDispatcher.coroutineScope.launch {
+            scrollingLogic.onDragStopped(velocity)
+        }
+    }
+
+    override fun startDragImmediately(): Boolean {
+        return scrollingLogic.shouldScrollImmediately()
+    }
 
     fun update(
         state: ScrollableState,
@@ -345,12 +360,12 @@
 
         if (this.enabled != enabled) { // enabled changed
             nestedScrollConnection.enabled = enabled
-            scrollableContainer.enabled = enabled
+            scrollableContainerNode.update(enabled)
         }
         // a new fling behavior was set, change the resolved one.
         val resolvedFlingBehavior = flingBehavior ?: defaultFlingBehavior
 
-        scrollingLogic.update(
+        val resetPointerInputHandling = scrollingLogic.update(
             scrollableState = state,
             orientation = orientation,
             overscrollEffect = overscrollEffect,
@@ -359,12 +374,6 @@
             nestedScrollDispatcher = nestedScrollDispatcher
         )
 
-        scrollableGesturesNode.update(
-            interactionSource = interactionSource,
-            orientation = orientation,
-            enabled = enabled
-        )
-
         contentInViewNode.update(
             orientation,
             state,
@@ -372,19 +381,18 @@
             bringIntoViewSpec
         )
 
-        this.state = state
-        this.orientation = orientation
         this.overscrollEffect = overscrollEffect
-        this.enabled = enabled
-        this.reverseDirection = reverseDirection
         this.flingBehavior = flingBehavior
-        this.interactionSource = interactionSource
+
+        // update DragGestureNode
+        update(CanDragCalculation, enabled, interactionSource, resetPointerInputHandling)
     }
 
     @Suppress("SuspiciousCompositionLocalModifierRead")
     override fun onAttach() {
         updateDefaultFlingBehavior()
         observeReads { currentValueOf(LocalDensity) } // monitor change in Density
+        scrollConfig = platformScrollConfig()
     }
 
     override fun onObservedReadsChanged() {
@@ -408,40 +416,37 @@
             (event.type == KeyEventType.KeyDown) &&
             (!event.isCtrlPressed)
         ) {
-            with(scrollingLogic) {
-                val scrollAmount: Offset = if (orientation == Orientation.Vertical) {
-                    val viewportHeight = contentInViewNode.viewportSize.height
 
-                    val yAmount = if (event.key == Key.PageUp) {
-                        viewportHeight.toFloat()
-                    } else {
-                        -viewportHeight.toFloat()
-                    }
+            val scrollAmount: Offset = if (scrollingLogic.isVertical()) {
+                val viewportHeight = contentInViewNode.viewportSize.height
 
-                    Offset(0f, yAmount)
+                val yAmount = if (event.key == Key.PageUp) {
+                    viewportHeight.toFloat()
                 } else {
-                    val viewportWidth = contentInViewNode.viewportSize.width
-
-                    val xAmount = if (event.key == Key.PageUp) {
-                        viewportWidth.toFloat()
-                    } else {
-                        -viewportWidth.toFloat()
-                    }
-
-                    Offset(xAmount, 0f)
+                    -viewportHeight.toFloat()
                 }
 
-                // A coroutine is launched for every individual scroll event in the
-                // larger scroll gesture. If we see degradation in the future (that is,
-                // a fast scroll gesture on a slow device causes UI jank [not seen up to
-                // this point), we can switch to a more efficient solution where we
-                // lazily launch one coroutine (with the first event) and use a Channel
-                // to communicate the scroll amount to the UI thread.
-                coroutineScope.launch {
-                    scrollableState.scroll(MutatePriority.UserInput) {
-                        dispatchScroll(scrollAmount, Wheel)
-                    }
+                Offset(0f, yAmount)
+            } else {
+                val viewportWidth = contentInViewNode.viewportSize.width
+
+                val xAmount = if (event.key == Key.PageUp) {
+                    viewportWidth.toFloat()
+                } else {
+                    -viewportWidth.toFloat()
                 }
+
+                Offset(xAmount, 0f)
+            }
+
+            // A coroutine is launched for every individual scroll event in the
+            // larger scroll gesture. If we see degradation in the future (that is,
+            // a fast scroll gesture on a slow device causes UI jank [not seen up to
+            // this point), we can switch to a more efficient solution where we
+            // lazily launch one coroutine (with the first event) and use a Channel
+            // to communicate the scroll amount to the UI thread.
+            coroutineScope.launch {
+                scrollingLogic.dispatchUserInputDelta(scrollAmount, Wheel)
             }
             true
         } else {
@@ -450,6 +455,38 @@
     }
 
     override fun onPreKeyEvent(event: KeyEvent) = false
+
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize
+    ) {
+        super.onPointerEvent(pointerEvent, pass, bounds)
+        if (pass == PointerEventPass.Main && pointerEvent.type == PointerEventType.Scroll) {
+            processMouseWheelEvent(pointerEvent, bounds)
+        }
+    }
+
+    /**
+     * Mouse wheel
+     */
+    private fun processMouseWheelEvent(event: PointerEvent, size: IntSize) {
+        if (event.changes.fastAll { !it.isConsumed }) {
+            with(scrollConfig!!) {
+                val scrollAmount = requireDensity().calculateMouseWheelScroll(event, size)
+                // A coroutine is launched for every individual scroll event in the
+                // larger scroll gesture. If we see degradation in the future (that is,
+                // a fast scroll gesture on a slow device causes UI jank [not seen up to
+                // this point), we can switch to a more efficient solution where we
+                // lazily launch one coroutine (with the first event) and use a Channel
+                // to communicate the scroll amount to the UI thread.
+                coroutineScope.launch {
+                    scrollingLogic.dispatchUserInputDelta(scrollAmount, Wheel)
+                }
+                event.changes.fastForEach { it.consume() }
+            }
+        }
+    }
 }
 
 /**
@@ -592,129 +629,20 @@
 
 internal expect fun CompositionLocalConsumerModifierNode.platformScrollConfig(): ScrollConfig
 
-/**
- * A node that detects and processes all scrollable gestures.
- */
-private class ScrollableGesturesNode(
-    val scrollLogic: ScrollingLogic,
-    val orientation: Orientation,
-    val enabled: Boolean,
-    val nestedScrollDispatcher: NestedScrollDispatcher,
-    val interactionSource: MutableInteractionSource?
-) : DelegatingNode() {
-    init {
-        delegate(MouseWheelScrollNode(scrollLogic))
-    }
-
-    val draggableState = ScrollDraggableState(scrollLogic)
-    private val startDragImmediately = { scrollLogic.shouldScrollImmediately() }
-    private val onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = { velocity ->
-        nestedScrollDispatcher.coroutineScope.launch {
-            scrollLogic.onDragStopped(velocity)
-        }
-    }
-
-    val draggableGesturesNode = delegate(
-        DraggableNode(
-            draggableState,
-            orientation = orientation,
-            enabled = enabled,
-            interactionSource = interactionSource,
-            reverseDirection = false,
-            startDragImmediately = startDragImmediately,
-            onDragStopped = onDragStopped,
-            canDrag = CanDragCalculation,
-            onDragStarted = NoOpOnDragStarted
-        )
-    )
-
-    fun update(
-        orientation: Orientation,
-        enabled: Boolean,
-        interactionSource: MutableInteractionSource?,
-    ) {
-
-        // update draggable node
-        draggableGesturesNode.update(
-            draggableState,
-            orientation = orientation,
-            enabled = enabled,
-            interactionSource = interactionSource,
-            reverseDirection = false,
-            startDragImmediately = startDragImmediately,
-            onDragStarted = NoOpOnDragStarted,
-            onDragStopped = onDragStopped,
-            canDrag = CanDragCalculation
-        )
-    }
-}
-
 private val CanDragCalculation: (PointerInputChange) -> Boolean =
     { down -> down.type != PointerType.Mouse }
 
-private val NoOpOnDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {}
-
-private class MouseWheelScrollNode(
-    private val scrollingLogic: ScrollingLogic
-) : DelegatingNode(), CompositionLocalConsumerModifierNode {
-    // Need to wait until onAttach to read the scroll config. Currently this is static, so we
-    // don't need to worry about observation / updating this over time.
-    var scrollConfig: ScrollConfig? = null
-
-    override fun onAttach() {
-        scrollConfig = platformScrollConfig()
-    }
-
-    init {
-        delegate(SuspendingPointerInputModifierNode {
-            awaitPointerEventScope {
-                while (true) {
-                    val event = awaitScrollEvent()
-                    if (event.changes.fastAll { !it.isConsumed }) {
-                        with(scrollConfig!!) {
-                            val scrollAmount = calculateMouseWheelScroll(event, size)
-
-                            with(scrollingLogic) {
-                                // A coroutine is launched for every individual scroll event in the
-                                // larger scroll gesture. If we see degradation in the future (that is,
-                                // a fast scroll gesture on a slow device causes UI jank [not seen up to
-                                // this point), we can switch to a more efficient solution where we
-                                // lazily launch one coroutine (with the first event) and use a Channel
-                                // to communicate the scroll amount to the UI thread.
-                                coroutineScope.launch {
-                                    scrollableState.scroll(MutatePriority.UserInput) {
-                                        dispatchScroll(scrollAmount, Wheel)
-                                    }
-                                }
-                                event.changes.fastForEach { it.consume() }
-                            }
-                        }
-                    }
-                }
-            }
-        })
-    }
-}
-
-private suspend fun AwaitPointerEventScope.awaitScrollEvent(): PointerEvent {
-    var event: PointerEvent
-    do {
-        event = awaitPointerEvent()
-    } while (event.type != PointerEventType.Scroll)
-    return event
-}
-
 /**
  * Holds all scrolling related logic: controls nested scrolling, flinging, overscroll and delta
  * dispatching.
  */
 @OptIn(ExperimentalFoundationApi::class)
 private class ScrollingLogic(
-    var scrollableState: ScrollableState,
-    private var orientation: Orientation,
+    private var scrollableState: ScrollableState,
     private var overscrollEffect: OverscrollEffect?,
-    private var reverseDirection: Boolean,
     private var flingBehavior: FlingBehavior,
+    private var orientation: Orientation,
+    private var reverseDirection: Boolean,
     private var nestedScrollDispatcher: NestedScrollDispatcher,
 ) {
 
@@ -749,7 +677,7 @@
     )
 
     private var latestScrollScope: ScrollScope = NoOpScrollScope
-    private var latestScrollSource: NestedScrollSource = NestedScrollSource.Drag
+    private var latestScrollSource: NestedScrollSource = Drag
 
     private val performScroll: (delta: Offset) -> Offset = { delta ->
         val consumedByPreScroll =
@@ -778,7 +706,7 @@
     /**
      * @return the amount of scroll that was consumed
      */
-    fun ScrollScope.dispatchScroll(
+    private fun ScrollScope.dispatchScroll(
         initialAvailableDelta: Offset,
         source: NestedScrollSource
     ): Offset {
@@ -806,8 +734,8 @@
         }
     }
 
-    suspend fun onDragStopped(initialVelocity: Float) {
-        val availableVelocity = initialVelocity.toVelocity()
+    suspend fun onDragStopped(initialVelocity: Velocity) {
+        val availableVelocity = initialVelocity.singleAxisVelocity()
 
         val performFling: suspend (Velocity) -> Velocity = { velocity ->
             val preConsumedByParent = nestedScrollDispatcher
@@ -861,6 +789,25 @@
             overscrollEffect?.isInProgress ?: false
     }
 
+    suspend fun dispatchUserInputDelta(delta: Offset, source: NestedScrollSource) {
+        scrollableState.scroll(MutatePriority.UserInput) {
+            dispatchScroll(delta, source)
+        }
+    }
+
+    suspend fun dispatchDragEvents(
+        forEachDelta: suspend ((dragDelta: DragEvent.DragDelta) -> Unit) -> Unit
+    ) {
+        scrollableState.scroll(MutatePriority.UserInput) {
+            forEachDelta {
+                dispatchScroll(it.delta.singleAxisOffset(), Drag)
+            }
+        }
+    }
+
+    /**
+     * @return true if the pointer input should be reset
+     */
     fun update(
         scrollableState: ScrollableState,
         orientation: Orientation,
@@ -868,39 +815,29 @@
         reverseDirection: Boolean,
         flingBehavior: FlingBehavior,
         nestedScrollDispatcher: NestedScrollDispatcher,
-    ) {
-        this.scrollableState = scrollableState
-        this.orientation = orientation
+    ): Boolean {
+        var resetPointerInputHandling = false
+        if (this.scrollableState != scrollableState) {
+            this.scrollableState = scrollableState
+            resetPointerInputHandling = true
+        }
         this.overscrollEffect = overscrollEffect
-        this.reverseDirection = reverseDirection
+        if (this.orientation != orientation) {
+            this.orientation = orientation
+            resetPointerInputHandling = true
+        }
+        if (this.reverseDirection != reverseDirection) {
+            this.reverseDirection = reverseDirection
+            resetPointerInputHandling = true
+        }
         this.flingBehavior = flingBehavior
         this.nestedScrollDispatcher = nestedScrollDispatcher
-    }
-}
-
-private class ScrollDraggableState(
-    var scrollLogic: ScrollingLogic
-) : DraggableState, DragScope {
-    var latestScrollScope: ScrollScope = NoOpScrollScope
-
-    override fun dragBy(pixels: Float) {
-        with(scrollLogic) {
-            with(latestScrollScope) {
-                dispatchScroll(pixels.toOffset(), Drag)
-            }
-        }
+        return resetPointerInputHandling
     }
 
-    override suspend fun drag(dragPriority: MutatePriority, block: suspend DragScope.() -> Unit) {
-        scrollLogic.scrollableState.scroll(dragPriority) {
-            latestScrollScope = this
-            block()
-        }
-    }
+    fun pointerDirectionConfig(): PointerDirectionConfig = orientation.toPointerDirectionConfig()
 
-    override fun dispatchRawDelta(delta: Float) {
-        with(scrollLogic) { performRawScroll(delta.toOffset()) }
-    }
+    fun isVertical(): Boolean = orientation == Vertical
 }
 
 private val NoOpScrollScope: ScrollScope = object : ScrollScope {
@@ -975,33 +912,29 @@
     }
 }
 
-// TODO: b/203141462 - make this public and move it to ui
-/**
- * Whether this modifier is inside a scrollable container, provided by [Modifier.scrollable].
- * Defaults to false.
- */
-internal val ModifierLocalScrollableContainer = modifierLocalOf { false }
-
-internal val NoOpFlingBehavior = object : FlingBehavior {
-    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float = 0f
-}
-
 private const val DefaultScrollMotionDurationScaleFactor = 1f
 internal val DefaultScrollMotionDurationScale = object : MotionDurationScale {
     override val scaleFactor: Float
         get() = DefaultScrollMotionDurationScaleFactor
 }
 
-private class ModifierLocalScrollableContainerProvider(var enabled: Boolean) :
-    ModifierLocalModifierNode,
-    Modifier.Node() {
-    private val modifierLocalMap = modifierLocalMapOf(ModifierLocalScrollableContainer to true)
-    override val providedValues: ModifierLocalMap
-        get() = if (enabled) {
-            modifierLocalMap
-        } else {
-            modifierLocalMapOf()
-        }
+/**
+ * (b/311181532): This could not be flattened so we moved it to TraversableNode, but ideally
+ * ScrollabeNode should be the one to be travesable.
+ */
+internal class ScrollableContainerNode(enabled: Boolean) :
+    Modifier.Node(),
+    TraversableNode {
+    override val traverseKey: Any = TraverseKey
+
+    var enabled: Boolean = enabled
+        private set
+
+    companion object TraverseKey
+
+    fun update(enabled: Boolean) {
+        this.enabled = enabled
+    }
 }
 
 private val UnityDensity = object : Density {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TargetedFlingBehavior.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TargetedFlingBehavior.kt
index a63253a..388b65a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TargetedFlingBehavior.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/TargetedFlingBehavior.kt
@@ -37,9 +37,10 @@
      *
      * @param initialVelocity velocity available for fling in the orientation specified in
      * [androidx.compose.foundation.gestures.scrollable] that invoked this method.
-     * @param onRemainingDistanceUpdated a lambda that will be called anytime the
-     * distance to the settling offset is updated. The settling offset is the final offset where
-     * this fling will stop and may change depending on the snapping animation progression.
+     * @param onRemainingDistanceUpdated a lambda that will be called anytime the distance to the
+     * settling offset is updated. The settling offset in pixels is passed to this lambda an it
+     * represents the final offset where this fling will stop and may change depending on the
+     * snapping animation progression.
      *
      * @return remaining velocity after fling operation has ended
      */
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt
index bca3df6..97d4dee 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScope.kt
@@ -82,21 +82,57 @@
     ): Modifier
 
     /**
+     * This modifier animates the item appearance (fade in), disappearance (fade out) and placement
+     * changes (such as an item reordering).
+     *
+     * You should also provide a key via [LazyListScope.item]/[LazyListScope.items] for this
+     * modifier to enable animations.
+     *
+     * @sample androidx.compose.foundation.samples.AnimateItemSample
+     *
+     * @param fadeInSpec an animation specs to use for animating the item appearance.
+     * When null is provided the item will be appearing without animations.
+     * @param placementSpec an animation specs that will be used to animate the item placement.
+     * Aside from item reordering all other position changes caused by events like arrangement or
+     * alignment changes will also be animated. When null is provided no animations will happen.
+     * @param fadeOutSpec an animation specs to use for animating the item disappearance.
+     * When null is provided the item will be disappearance without animations.
+     */
+    fun Modifier.animateItem(
+        fadeInSpec: FiniteAnimationSpec<Float>? = spring(stiffness = Spring.StiffnessMediumLow),
+        placementSpec: FiniteAnimationSpec<IntOffset>? = spring(
+            stiffness = Spring.StiffnessMediumLow,
+            visibilityThreshold = IntOffset.VisibilityThreshold
+        ),
+        fadeOutSpec: FiniteAnimationSpec<Float>? =
+            spring(stiffness = Spring.StiffnessMediumLow),
+    ): Modifier = this
+
+    /**
      * This modifier animates the item placement within the Lazy list.
      *
      * When you provide a key via [LazyListScope.item]/[LazyListScope.items] this modifier will
      * enable item reordering animations. Aside from item reordering all other position changes
      * caused by events like arrangement or alignment changes will also be animated.
      *
-     * @sample androidx.compose.foundation.samples.ItemPlacementAnimationSample
-     *
      * @param animationSpec a finite animation that will be used to animate the item placement.
      */
+    @Deprecated(
+        "Use Modifier.animateItem() instead",
+        ReplaceWith(
+            "Modifier.animateItem(enterSpec = null, exitSpec = null, " +
+                "placementSpec = animationSpec)"
+        )
+    )
     @ExperimentalFoundationApi
     fun Modifier.animateItemPlacement(
         animationSpec: FiniteAnimationSpec<IntOffset> = spring(
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = IntOffset.VisibilityThreshold
         )
-    ): Modifier
+    ): Modifier = animateItem(
+        fadeInSpec = null,
+        placementSpec = animationSpec,
+        fadeOutSpec = null
+    )
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScopeImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScopeImpl.kt
index 4c6d370..3ca66ff 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScopeImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyItemScopeImpl.kt
@@ -17,11 +17,6 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.VisibilityThreshold
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimationSpecsNode
 import androidx.compose.runtime.State
 import androidx.compose.runtime.mutableIntStateOf
@@ -71,31 +66,20 @@
         )
     )
 
-    @ExperimentalFoundationApi
-    override fun Modifier.animateItemPlacement(
-        animationSpec: FiniteAnimationSpec<IntOffset>
-    ): Modifier = animateItem(
-        appearanceSpec = null,
-        placementSpec = animationSpec
-    )
-}
-
-@ExperimentalFoundationApi
-internal fun Modifier.animateItem(
-    appearanceSpec: FiniteAnimationSpec<Float>? = tween(220),
-    placementSpec: FiniteAnimationSpec<IntOffset>? = spring(
-        stiffness = Spring.StiffnessMediumLow,
-        visibilityThreshold = IntOffset.VisibilityThreshold
-    )
-): Modifier {
-    return if (appearanceSpec == null && placementSpec == null) {
-        this
-    } else {
-        this then AnimateItemElement(
-            appearanceSpec,
-            placementSpec
-        )
-    }
+    override fun Modifier.animateItem(
+        fadeInSpec: FiniteAnimationSpec<Float>?,
+        placementSpec: FiniteAnimationSpec<IntOffset>?,
+        fadeOutSpec: FiniteAnimationSpec<Float>?
+    ): Modifier =
+        if (fadeInSpec == null && placementSpec == null && fadeOutSpec == null) {
+            this
+        } else {
+            this then AnimateItemElement(
+                fadeInSpec,
+                placementSpec,
+                fadeOutSpec
+            )
+        }
 }
 
 private class ParentSizeElement(
@@ -178,21 +162,28 @@
 }
 
 private data class AnimateItemElement(
-    val appearanceSpec: FiniteAnimationSpec<Float>?,
-    val placementSpec: FiniteAnimationSpec<IntOffset>?
+    val fadeInSpec: FiniteAnimationSpec<Float>?,
+    val placementSpec: FiniteAnimationSpec<IntOffset>?,
+    val fadeOutSpec: FiniteAnimationSpec<Float>?
 ) : ModifierNodeElement<LazyLayoutAnimationSpecsNode>() {
 
     override fun create(): LazyLayoutAnimationSpecsNode =
-        LazyLayoutAnimationSpecsNode(appearanceSpec, placementSpec)
+        LazyLayoutAnimationSpecsNode(
+            fadeInSpec,
+            placementSpec,
+            fadeOutSpec
+        )
 
     override fun update(node: LazyLayoutAnimationSpecsNode) {
-        node.appearanceSpec = appearanceSpec
+        node.fadeInSpec = fadeInSpec
         node.placementSpec = placementSpec
+        node.fadeOutSpec = fadeOutSpec
     }
 
     override fun InspectorInfo.inspectableProperties() {
-        // TODO update the name here once we expose a new public api
-        name = "animateItemPlacement"
-        value = placementSpec
+        name = "animateItem"
+        properties["fadeInSpec"] = fadeInSpec
+        properties["placementSpec"] = placementSpec
+        properties["fadeOutSpec"] = fadeOutSpec
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
index 4b4394b..2848666 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
@@ -19,6 +19,8 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
+import androidx.compose.foundation.lazy.layout.estimatedLazyMaxScrollOffset
+import androidx.compose.foundation.lazy.layout.estimatedLazyScrollOffset
 import androidx.compose.ui.semantics.CollectionInfo
 
 internal fun LazyLayoutSemanticState(
@@ -26,16 +28,19 @@
     isVertical: Boolean
 ): LazyLayoutSemanticState = object : LazyLayoutSemanticState {
 
-    override val firstVisibleItemScrollOffset: Int
-        get() = state.firstVisibleItemScrollOffset
-    override val firstVisibleItemIndex: Int
-        get() = state.firstVisibleItemIndex
-    override val canScrollForward: Boolean
-        get() = state.canScrollForward
+    override val scrollOffset: Float
+        get() = estimatedLazyScrollOffset(
+            state.firstVisibleItemIndex,
+            state.firstVisibleItemScrollOffset
+        )
+    override val maxScrollOffset: Float
+        get() = estimatedLazyMaxScrollOffset(
+            state.firstVisibleItemIndex,
+            state.firstVisibleItemScrollOffset,
+            state.canScrollForward
+        )
 
-    override suspend fun animateScrollBy(delta: Float) {
-        state.animateScrollBy(delta)
-    }
+    override suspend fun animateScrollBy(delta: Float): Float = state.animateScrollBy(delta)
 
     override suspend fun scrollToItem(index: Int) {
         state.scrollToItem(index)
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 9bdd775..0849263 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
@@ -36,8 +36,10 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.platform.LocalGraphicsContext
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
@@ -80,6 +82,7 @@
 
     val semanticState = rememberLazyListSemanticState(state, isVertical)
     val coroutineScope = rememberCoroutineScope()
+    val graphicsContext = LocalGraphicsContext.current
 
     val measurePolicy = rememberLazyListMeasurePolicy(
         itemProviderLambda,
@@ -92,7 +95,8 @@
         verticalAlignment,
         horizontalArrangement,
         verticalArrangement,
-        coroutineScope
+        coroutineScope,
+        graphicsContext
     )
 
     val orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal
@@ -118,6 +122,7 @@
                 orientation = orientation,
                 enabled = userScrollEnabled
             )
+            .then(state.itemAnimator.modifier)
             .scrollingContainer(
                 state = state,
                 orientation = orientation,
@@ -156,7 +161,8 @@
     /** The vertical arrangement for items */
     verticalArrangement: Arrangement.Vertical?,
     /** Scope for animations */
-    coroutineScope: CoroutineScope
+    coroutineScope: CoroutineScope,
+    graphicsContext: GraphicsContext
 ) = remember<LazyLayoutMeasureScope.(Constraints) -> MeasureResult>(
     state,
     contentPadding,
@@ -165,7 +171,8 @@
     horizontalAlignment,
     verticalAlignment,
     horizontalArrangement,
-    verticalArrangement
+    verticalArrangement,
+    graphicsContext
 ) {
     { containerConstraints ->
         // Tracks if the lookahead pass has occurred
@@ -298,41 +305,44 @@
             state.scrollDeltaBetweenPasses
         }
 
-        measureLazyList(
-            itemsCount = itemsCount,
-            measuredItemProvider = measuredItemProvider,
-            mainAxisAvailableSize = mainAxisAvailableSize,
-            beforeContentPadding = beforeContentPadding,
-            afterContentPadding = afterContentPadding,
-            spaceBetweenItems = spaceBetweenItems,
-            firstVisibleItemIndex = firstVisibleItemIndex,
-            firstVisibleItemScrollOffset = firstVisibleScrollOffset,
-            scrollToBeConsumed = scrollToBeConsumed,
-            constraints = contentConstraints,
-            isVertical = isVertical,
-            headerIndexes = itemProvider.headerIndexes,
-            verticalArrangement = verticalArrangement,
-            horizontalArrangement = horizontalArrangement,
-            reverseLayout = reverseLayout,
-            density = this,
-            itemAnimator = state.itemAnimator,
-            beyondBoundsItemCount = beyondBoundsItemCount,
-            pinnedItems = pinnedItems,
-            hasLookaheadPassOccurred = hasLookaheadPassOccurred,
-            isLookingAhead = isLookingAhead,
-            postLookaheadLayoutInfo = state.postLookaheadLayoutInfo,
-            coroutineScope = coroutineScope,
-            placementScopeInvalidator = state.placementScopeInvalidator,
-            layout = { width, height, placement ->
-                layout(
-                    containerConstraints.constrainWidth(width + totalHorizontalPadding),
-                    containerConstraints.constrainHeight(height + totalVerticalPadding),
-                    emptyMap(),
-                    placement
-                )
-            }
-        ).also {
-            state.applyMeasureResult(it, isLookingAhead)
+        val measureResult = Snapshot.withMutableSnapshot {
+            measureLazyList(
+                itemsCount = itemsCount,
+                measuredItemProvider = measuredItemProvider,
+                mainAxisAvailableSize = mainAxisAvailableSize,
+                beforeContentPadding = beforeContentPadding,
+                afterContentPadding = afterContentPadding,
+                spaceBetweenItems = spaceBetweenItems,
+                firstVisibleItemIndex = firstVisibleItemIndex,
+                firstVisibleItemScrollOffset = firstVisibleScrollOffset,
+                scrollToBeConsumed = scrollToBeConsumed,
+                constraints = contentConstraints,
+                isVertical = isVertical,
+                headerIndexes = itemProvider.headerIndexes,
+                verticalArrangement = verticalArrangement,
+                horizontalArrangement = horizontalArrangement,
+                reverseLayout = reverseLayout,
+                density = this,
+                itemAnimator = state.itemAnimator,
+                beyondBoundsItemCount = beyondBoundsItemCount,
+                pinnedItems = pinnedItems,
+                hasLookaheadPassOccurred = hasLookaheadPassOccurred,
+                isLookingAhead = isLookingAhead,
+                postLookaheadLayoutInfo = state.postLookaheadLayoutInfo,
+                coroutineScope = coroutineScope,
+                placementScopeInvalidator = state.placementScopeInvalidator,
+                graphicsContext = graphicsContext,
+                layout = { width, height, placement ->
+                    layout(
+                        containerConstraints.constrainWidth(width + totalHorizontalPadding),
+                        containerConstraints.constrainHeight(height + totalVerticalPadding),
+                        emptyMap(),
+                        placement
+                    )
+                }
+            )
         }
+        state.applyMeasureResult(measureResult, isLookingAhead)
+        measureResult
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemAnimator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemAnimator.kt
index 94d69da..20e7149 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemAnimator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemAnimator.kt
@@ -19,18 +19,29 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimation
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimationSpecsNode
 import androidx.compose.foundation.lazy.layout.LazyLayoutKeyIndexMap
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.GraphicsContext
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateDraw
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import kotlinx.coroutines.CoroutineScope
 
 /**
- * Handles the item animations when it is set via [LazyItemScope.animateItemPlacement].
+ * Handles the item animations when it is set via [LazyItemScope.animateItem].
  *
  * This class is responsible for:
  * - animating item appearance for the new items.
  * - detecting when item position changed, figuring our start/end offsets and starting the
  * animations for placement animations.
+ * - animating item disappearance for the removed items.
  */
 internal class LazyListItemAnimator {
     // state containing relevant info for active items.
@@ -48,6 +59,8 @@
     private val movingInFromEndBound = mutableListOf<LazyListMeasuredItem>()
     private val movingAwayToStartBound = mutableListOf<LazyListMeasuredItem>()
     private val movingAwayToEndBound = mutableListOf<LazyListMeasuredItem>()
+    private val disappearingItems = mutableListOf<LazyLayoutAnimation>()
+    private var displayingNode: DrawModifierNode? = null
 
     /**
      * Should be called after the measuring so we can detect position changes and start animations.
@@ -63,9 +76,10 @@
         isVertical: Boolean,
         isLookingAhead: Boolean,
         hasLookaheadOccurred: Boolean,
-        coroutineScope: CoroutineScope
+        coroutineScope: CoroutineScope,
+        graphicsContext: GraphicsContext
     ) {
-        val previousKeyToIndexMap = this.keyIndexMap
+        val previousKeyToIndexMap = keyIndexMap
         val keyIndexMap = itemProvider.keyIndexMap
         this.keyIndexMap = keyIndexMap
 
@@ -99,12 +113,13 @@
             movingAwayKeys.remove(item.key)
             if (item.hasAnimations) {
                 val itemInfo = keyToItemInfoMap[item.key]
+                val previousIndex = previousKeyToIndexMap?.getIndex(item.key) ?: -1
+                val shouldAnimateAppearance = previousIndex == -1 && previousKeyToIndexMap != null
                 // there is no state associated with this item yet
                 if (itemInfo == null) {
                     val newItemInfo = ItemInfo()
-                    newItemInfo.updateAnimation(item, coroutineScope)
+                    newItemInfo.updateAnimation(item, coroutineScope, graphicsContext)
                     keyToItemInfoMap[item.key] = newItemInfo
-                    val previousIndex = previousKeyToIndexMap?.getIndex(item.key) ?: -1
                     if (item.index != previousIndex && previousIndex != -1) {
                         if (previousIndex < previousFirstVisibleIndex) {
                             // the larger index will be in the start of the list
@@ -118,7 +133,7 @@
                             item.getOffset(0).let { if (item.isVertical) it.y else it.x },
                             newItemInfo
                         )
-                        if (previousIndex == -1 && previousKeyToIndexMap != null) {
+                        if (shouldAnimateAppearance) {
                             newItemInfo.animations.forEach {
                                 it?.animateAppearance()
                             }
@@ -126,7 +141,7 @@
                     }
                 } else {
                     if (shouldSetupAnimation) {
-                        itemInfo.updateAnimation(item, coroutineScope)
+                        itemInfo.updateAnimation(item, coroutineScope, graphicsContext)
                         itemInfo.animations.forEach { animation ->
                             if (animation != null &&
                                 animation.rawOffset != LazyLayoutAnimation.NotInitialized
@@ -134,12 +149,23 @@
                                 animation.rawOffset += scrollOffset
                             }
                         }
+                        if (shouldAnimateAppearance) {
+                            itemInfo.animations.forEach {
+                                if (it != null) {
+                                    if (it.isDisappearanceAnimationInProgress) {
+                                        disappearingItems.remove(it)
+                                        displayingNode?.invalidateDraw()
+                                    }
+                                    it.animateAppearance()
+                                }
+                            }
+                        }
                         startPlacementAnimationsIfNeeded(item)
                     }
                 }
             } else {
                 // no animation, clean up if needed
-                keyToItemInfoMap.remove(item.key)
+                removeInfoForKey(item.key)
             }
         }
 
@@ -168,7 +194,35 @@
             val newIndex = keyIndexMap.getIndex(key)
 
             if (newIndex == -1) {
-                keyToItemInfoMap.remove(key)
+                val info = keyToItemInfoMap.getValue(key)
+                var isProgress = false
+                info.animations.forEachIndexed { index, animation ->
+                    if (animation != null) {
+                        if (animation.isDisappearanceAnimationInProgress) {
+                            isProgress = true
+                        } else if (animation.isDisappearanceAnimationFinished) {
+                            animation.release()
+                            info.animations[index] = null
+                            disappearingItems.remove(animation)
+                            displayingNode?.invalidateDraw()
+                        } else {
+                            if (animation.layer != null) {
+                                animation.animateDisappearance()
+                            }
+                            if (animation.isDisappearanceAnimationInProgress) {
+                                disappearingItems.add(animation)
+                                displayingNode?.invalidateDraw()
+                                isProgress = true
+                            } else {
+                                animation.release()
+                                info.animations[index] = null
+                            }
+                        }
+                    }
+                }
+                if (!isProgress) {
+                    removeInfoForKey(key)
+                }
             } else {
                 val item = itemProvider.getAndMeasure(newIndex)
                 item.nonScrollableItem = true
@@ -177,7 +231,7 @@
                 val inProgress =
                     itemInfo.animations.any { it?.isPlacementAnimationInProgress == true }
                 if ((!inProgress && newIndex == previousKeyToIndexMap?.getIndex(key))) {
-                    keyToItemInfoMap.remove(key)
+                    removeInfoForKey(key)
                 } else {
                     if (newIndex < firstVisibleIndex) {
                         movingAwayToStartBound.add(item)
@@ -230,12 +284,25 @@
         movingAwayKeys.clear()
     }
 
+    private fun removeInfoForKey(key: Any) {
+        keyToItemInfoMap.remove(key)?.animations?.forEach {
+            it?.release()
+        }
+    }
+
     /**
      * Should be called when the animations are not needed for the next positions change,
      * for example when we snap to a new position.
      */
     fun reset() {
-        keyToItemInfoMap.clear()
+        if (keyToItemInfoMap.isNotEmpty()) {
+            keyToItemInfoMap.forEach {
+                it.value.animations.forEach {
+                    it?.release()
+                }
+            }
+            keyToItemInfoMap.clear()
+        }
         keyIndexMap = LazyLayoutKeyIndexMap.Empty
         firstVisibleIndex = -1
     }
@@ -282,6 +349,22 @@
     fun getAnimation(key: Any, placeableIndex: Int): LazyLayoutAnimation? =
         keyToItemInfoMap[key]?.animations?.get(placeableIndex)
 
+    val minSizeToFitDisappearingItems: IntSize get() {
+        var size = IntSize.Zero
+        disappearingItems.fastForEach {
+            val layer = it.layer
+            if (layer != null) {
+                size = IntSize(
+                    width = maxOf(size.width, it.rawOffset.x + layer.size.width),
+                    height = maxOf(size.height, it.rawOffset.y + layer.size.height)
+                )
+            }
+        }
+        return size
+    }
+
+    val modifier: Modifier = DisplayingDisappearingItemsElement(this)
+
     private val LazyListMeasuredItem.hasAnimations: Boolean
         get() {
             repeat(placeablesCount) { index ->
@@ -293,7 +376,7 @@
             return false
         }
 
-    private class ItemInfo {
+    private inner class ItemInfo {
         /**
          * This array will have the same amount of elements as there are placeables on the item.
          * If the element is not null this means there are specs associated with the given placeable.
@@ -301,9 +384,13 @@
         var animations = EmptyArray
             private set
 
-        fun updateAnimation(positionedItem: LazyListMeasuredItem, coroutineScope: CoroutineScope) {
+        fun updateAnimation(
+            positionedItem: LazyListMeasuredItem,
+            coroutineScope: CoroutineScope,
+            graphicsContext: GraphicsContext
+        ) {
             for (i in positionedItem.placeablesCount until animations.size) {
-                animations[i]?.stopAnimations()
+                animations[i]?.release()
             }
             if (animations.size != positionedItem.placeablesCount) {
                 animations = animations.copyOf(positionedItem.placeablesCount)
@@ -311,14 +398,69 @@
             repeat(positionedItem.placeablesCount) { index ->
                 val specs = positionedItem.getParentData(index).specs
                 if (specs == null) {
-                    animations[index]?.stopAnimations()
+                    animations[index]?.release()
                     animations[index] = null
                 } else {
-                    val animation = animations[index] ?: LazyLayoutAnimation(coroutineScope).also {
+                    val animation = animations[index] ?: LazyLayoutAnimation(
+                        coroutineScope = coroutineScope,
+                        graphicsContext = graphicsContext,
+                        // until b/329417380 is fixed we have to trigger any invalidation in
+                        // order for the layer properties change to be applied:
+                        onLayerPropertyChanged = { displayingNode?.invalidateDraw() }
+                    ).also {
                         animations[index] = it
                     }
-                    animation.appearanceSpec = specs.appearanceSpec
+                    animation.fadeInSpec = specs.fadeInSpec
                     animation.placementSpec = specs.placementSpec
+                    animation.fadeOutSpec = specs.fadeOutSpec
+                }
+            }
+        }
+    }
+
+    private data class DisplayingDisappearingItemsElement(
+        private val animator: LazyListItemAnimator
+    ) : ModifierNodeElement<DisplayingDisappearingItemsNode>() {
+        override fun create() = DisplayingDisappearingItemsNode(animator)
+
+        override fun update(node: DisplayingDisappearingItemsNode) {
+            node.setAnimator(animator)
+        }
+
+        override fun InspectorInfo.inspectableProperties() {
+            name = "DisplayingDisappearingItemsElement"
+        }
+    }
+
+    private data class DisplayingDisappearingItemsNode(
+        private var animator: LazyListItemAnimator
+    ) : Modifier.Node(), DrawModifierNode {
+        override fun ContentDrawScope.draw() {
+            animator.disappearingItems.fastForEach {
+                val layer = it.layer ?: return@fastForEach
+                val x = it.finalOffset.x.toFloat()
+                val y = it.finalOffset.y.toFloat()
+                translate(x - layer.topLeft.x.toFloat(), y - layer.topLeft.y.toFloat()) {
+                    drawLayer(layer)
+                }
+            }
+            drawContent()
+        }
+
+        override fun onAttach() {
+            animator.displayingNode = this
+        }
+
+        override fun onDetach() {
+            animator.reset()
+        }
+
+        fun setAnimator(animator: LazyListItemAnimator) {
+            if (this.animator != animator) {
+                if (node.isAttached) {
+                    this.animator.reset()
+                    animator.displayingNode = this
+                    this.animator = animator
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 7945210..da56802 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -19,10 +19,12 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
+import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
@@ -65,14 +67,15 @@
     postLookaheadLayoutInfo: LazyListLayoutInfo?,
     coroutineScope: CoroutineScope,
     placementScopeInvalidator: ObservableScopeInvalidator,
+    graphicsContext: GraphicsContext,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyListMeasureResult {
     require(beforeContentPadding >= 0) { "invalid beforeContentPadding" }
     require(afterContentPadding >= 0) { "invalid afterContentPadding" }
     if (itemsCount <= 0) {
         // empty data set. reset the current scroll and report zero size
-        val layoutWidth = constraints.minWidth
-        val layoutHeight = constraints.minHeight
+        var layoutWidth = constraints.minWidth
+        var layoutHeight = constraints.minHeight
         itemAnimator.onMeasured(
             consumedScroll = 0,
             layoutWidth = layoutWidth,
@@ -82,8 +85,16 @@
             isVertical = isVertical,
             isLookingAhead = isLookingAhead,
             hasLookaheadOccurred = hasLookaheadPassOccurred,
-            coroutineScope = coroutineScope
+            coroutineScope = coroutineScope,
+            graphicsContext = graphicsContext
         )
+        if (!isLookingAhead) {
+            val disappearingItemsSize = itemAnimator.minSizeToFitDisappearingItems
+            if (disappearingItemsSize != IntSize.Zero) {
+                layoutWidth = constraints.constrainWidth(disappearingItemsSize.width)
+                layoutHeight = constraints.constrainHeight(disappearingItemsSize.height)
+            }
+        }
         return LazyListMeasureResult(
             firstVisibleItem = null,
             firstVisibleItemScrollOffset = 0,
@@ -313,9 +324,9 @@
             extraItemsBefore.isEmpty() &&
             extraItemsAfter.isEmpty()
 
-        val layoutWidth =
+        var layoutWidth =
             constraints.constrainWidth(if (isVertical) maxCrossAxis else currentMainAxisOffset)
-        val layoutHeight =
+        var layoutHeight =
             constraints.constrainHeight(if (isVertical) currentMainAxisOffset else maxCrossAxis)
 
         val positionedItems = calculateItemsOffsets(
@@ -343,9 +354,27 @@
             isVertical = isVertical,
             isLookingAhead = isLookingAhead,
             hasLookaheadOccurred = hasLookaheadPassOccurred,
-            coroutineScope = coroutineScope
+            coroutineScope = coroutineScope,
+            graphicsContext = graphicsContext
         )
 
+        if (!isLookingAhead) {
+            val disappearingItemsSize = itemAnimator.minSizeToFitDisappearingItems
+            if (disappearingItemsSize != IntSize.Zero) {
+                val oldMainAxisSize = if (isVertical) layoutHeight else layoutWidth
+                layoutWidth =
+                    constraints.constrainWidth(maxOf(layoutWidth, disappearingItemsSize.width))
+                layoutHeight =
+                    constraints.constrainHeight(maxOf(layoutHeight, disappearingItemsSize.height))
+                val newMainAxisSize = if (isVertical) layoutHeight else layoutWidth
+                if (newMainAxisSize != oldMainAxisSize) {
+                    positionedItems.fastForEach {
+                        it.updateMainAxisLayoutSize(newMainAxisSize)
+                    }
+                }
+            }
+        }
+
         val headerItem = if (headerIndexes.isNotEmpty()) {
             findOrComposeLazyListHeader(
                 composedVisibleItems = positionedItems,
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 19e3980..80c07d7 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
@@ -17,10 +17,9 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.lazy.layout.DefaultLayerBlock
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimation.Companion.NotInitialized
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.graphics.GraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
@@ -138,6 +137,15 @@
         maxMainAxisOffset = mainAxisLayoutSize + afterContentPadding
     }
 
+    /**
+     * Update a [mainAxisLayoutSize] when the size did change after last [position] call.
+     * Knowing the final size is important for calculating the final position in reverse layout.
+     */
+    fun updateMainAxisLayoutSize(mainAxisLayoutSize: Int) {
+        this.mainAxisLayoutSize = mainAxisLayoutSize
+        maxMainAxisOffset = mainAxisLayoutSize + afterContentPadding
+    }
+
     fun getOffset(index: Int) =
         IntOffset(placeableOffsets[index * 2], placeableOffsets[index * 2 + 1])
 
@@ -174,7 +182,7 @@
             val maxOffset = maxMainAxisOffset
             var offset = getOffset(index)
             val animation = animator.getAnimation(key, index)
-            val layerBlock: GraphicsLayerScope.() -> Unit
+            val layer: GraphicsLayer?
             if (animation != null) {
                 if (isLookingAhead) {
                     // Skip animation in lookahead pass
@@ -196,9 +204,9 @@
                     }
                     offset = animatedOffset
                 }
-                layerBlock = animation.layerBlock
+                layer = animation.layer
             } else {
-                layerBlock = DefaultLayerBlock
+                layer = null
             }
             if (reverseLayout) {
                 offset = offset.copy { mainAxisOffset ->
@@ -206,10 +214,21 @@
                 }
             }
             offset += visualOffset
+            if (!isLookingAhead) {
+                animation?.finalOffset = offset
+            }
             if (isVertical) {
-                placeable.placeWithLayer(offset, layerBlock = layerBlock)
+                if (layer != null) {
+                    placeable.placeWithLayer(offset, layer)
+                } else {
+                    placeable.placeWithLayer(offset)
+                }
             } else {
-                placeable.placeRelativeWithLayer(offset, layerBlock = layerBlock)
+                if (layer != null) {
+                    placeable.placeRelativeWithLayer(offset, layer)
+                } else {
+                    placeable.placeRelativeWithLayer(offset)
+                }
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
index f67624d..73fa5e7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrollPosition.kt
@@ -81,7 +81,7 @@
      * c) there will be not enough items to fill the viewport after the requested index, so we
      * would have to compose few elements before the asked index, changing the first visible item.
      */
-    fun requestPosition(index: Int, scrollOffset: Int) {
+    fun requestPositionAndForgetLastKnownKey(index: Int, scrollOffset: Int) {
         update(index, scrollOffset)
         // clear the stored key as we have a direct request to scroll to [index] position and the
         // next [checkIfFirstVisibleItemWasMoved] shouldn't override this.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index f743c88..310af7f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -305,9 +305,20 @@
     }
 
     internal fun snapToItemIndexInternal(index: Int, scrollOffset: Int) {
-        scrollPosition.requestPosition(index, scrollOffset)
-        // placement animation is not needed because we snap into a new position.
-        itemAnimator.reset()
+        val positionChanged = scrollPosition.index != index ||
+            scrollPosition.scrollOffset != scrollOffset
+        // sometimes this method is called not to scroll, but to stay on the same index when
+        // the data changes, as by default we maintain the scroll position by key, not index.
+        // when this happens we don't need to reset the animations as from the user perspective
+        // we didn't scroll anywhere and if there is an offset change for an item, this change
+        // should be animated.
+        // however, when the request is to really scroll to a different position, we have to
+        // reset previously known item positions as we don't want offset changes to be animated.
+        // this offset should be considered as a scroll, not the placement change.
+        if (positionChanged) {
+            itemAnimator.reset()
+        }
+        scrollPosition.requestPositionAndForgetLastKnownKey(index, scrollOffset)
         remeasurement?.forceRemeasure()
     }
 
@@ -411,7 +422,7 @@
         }
     }
 
-    fun notifyPrefetchOnScroll(delta: Float, layoutInfo: LazyListLayoutInfo) {
+    private fun notifyPrefetchOnScroll(delta: Float, layoutInfo: LazyListLayoutInfo) {
         if (prefetchingEnabled) {
             with(prefetchStrategy) {
                 prefetchScope.onScroll(
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 feff117..332f12c 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
@@ -321,39 +321,41 @@
             state.beyondBoundsInfo
         )
 
-        measureLazyGrid(
-            itemsCount = itemsCount,
-            measuredLineProvider = measuredLineProvider,
-            measuredItemProvider = measuredItemProvider,
-            mainAxisAvailableSize = mainAxisAvailableSize,
-            beforeContentPadding = beforeContentPadding,
-            afterContentPadding = afterContentPadding,
-            spaceBetweenLines = spaceBetweenLines,
-            firstVisibleLineIndex = firstVisibleLineIndex,
-            firstVisibleLineScrollOffset = firstVisibleLineScrollOffset,
-            scrollToBeConsumed = state.scrollToBeConsumed,
-            constraints = contentConstraints,
-            isVertical = isVertical,
-            verticalArrangement = verticalArrangement,
-            horizontalArrangement = horizontalArrangement,
-            reverseLayout = reverseLayout,
-            density = this,
-            placementAnimator = state.placementAnimator,
-            spanLayoutProvider = spanLayoutProvider,
-            pinnedItems = pinnedItems,
-            coroutineScope = coroutineScope,
-            placementScopeInvalidator = state.placementScopeInvalidator,
-            prefetchInfoRetriever = prefetchInfoRetriever,
-            layout = { width, height, placement ->
-                layout(
-                    containerConstraints.constrainWidth(width + totalHorizontalPadding),
-                    containerConstraints.constrainHeight(height + totalVerticalPadding),
-                    emptyMap(),
-                    placement
-                )
-            }
-        ).also {
-            state.applyMeasureResult(it)
+        val measureResult = Snapshot.withMutableSnapshot {
+            measureLazyGrid(
+                itemsCount = itemsCount,
+                measuredLineProvider = measuredLineProvider,
+                measuredItemProvider = measuredItemProvider,
+                mainAxisAvailableSize = mainAxisAvailableSize,
+                beforeContentPadding = beforeContentPadding,
+                afterContentPadding = afterContentPadding,
+                spaceBetweenLines = spaceBetweenLines,
+                firstVisibleLineIndex = firstVisibleLineIndex,
+                firstVisibleLineScrollOffset = firstVisibleLineScrollOffset,
+                scrollToBeConsumed = state.scrollToBeConsumed,
+                constraints = contentConstraints,
+                isVertical = isVertical,
+                verticalArrangement = verticalArrangement,
+                horizontalArrangement = horizontalArrangement,
+                reverseLayout = reverseLayout,
+                density = this,
+                placementAnimator = state.placementAnimator,
+                spanLayoutProvider = spanLayoutProvider,
+                pinnedItems = pinnedItems,
+                coroutineScope = coroutineScope,
+                placementScopeInvalidator = state.placementScopeInvalidator,
+                prefetchInfoRetriever = prefetchInfoRetriever,
+                layout = { width, height, placement ->
+                    layout(
+                        containerConstraints.constrainWidth(width + totalHorizontalPadding),
+                        containerConstraints.constrainHeight(height + totalVerticalPadding),
+                        emptyMap(),
+                        placement
+                    )
+                }
+            )
         }
+        state.applyMeasureResult(measureResult)
+        measureResult
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemPlacementAnimator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemPlacementAnimator.kt
index 4d8dc32..3cbc6fd 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemPlacementAnimator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemPlacementAnimator.kt
@@ -341,7 +341,7 @@
 
     fun updateAnimation(positionedItem: LazyGridMeasuredItem, coroutineScope: CoroutineScope) {
         for (i in positionedItem.placeablesCount until animations.size) {
-            animations[i]?.stopAnimations()
+            animations[i]?.release()
         }
         if (animations.size != positionedItem.placeablesCount) {
             animations = animations.copyOf(positionedItem.placeablesCount)
@@ -349,13 +349,13 @@
         repeat(positionedItem.placeablesCount) { index ->
             val specs = positionedItem.getParentData(index).specs
             if (specs == null) {
-                animations[index]?.stopAnimations()
+                animations[index]?.release()
                 animations[index] = null
             } else {
                 val item = animations[index] ?: LazyLayoutAnimation(coroutineScope).also {
                     animations[index] = it
                 }
-                item.appearanceSpec = specs.appearanceSpec
+                item.fadeInSpec = specs.fadeInSpec
                 item.placementSpec = specs.placementSpec
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemScopeImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemScopeImpl.kt
index bfda57e..04803cc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemScopeImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemScopeImpl.kt
@@ -36,7 +36,7 @@
 ) : ModifierNodeElement<LazyLayoutAnimationSpecsNode>() {
 
     override fun create(): LazyLayoutAnimationSpecsNode =
-        LazyLayoutAnimationSpecsNode(null, placementSpec)
+        LazyLayoutAnimationSpecsNode(null, placementSpec, null)
 
     override fun update(node: LazyLayoutAnimationSpecsNode) {
         node.placementSpec = placementSpec
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
index 30b61cc..250d956 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridScrollPosition.kt
@@ -83,7 +83,7 @@
      * c) there will be not enough items to fill the viewport after the requested index, so we
      * would have to compose few elements before the asked index, changing the first visible item.
      */
-    fun requestPosition(index: Int, scrollOffset: Int) {
+    fun requestPositionAndForgetLastKnownKey(index: Int, scrollOffset: Int) {
         update(index, scrollOffset)
         // clear the stored key as we have a direct request to scroll to [index] position and the
         // next [checkIfFirstVisibleItemWasMoved] shouldn't override this.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index 12a5382..eff47f8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -250,9 +250,20 @@
     }
 
     internal fun snapToItemIndexInternal(index: Int, scrollOffset: Int) {
-        scrollPosition.requestPosition(index, scrollOffset)
-        // placement animation is not needed because we snap into a new position.
-        placementAnimator.reset()
+        val positionChanged = scrollPosition.index != index ||
+            scrollPosition.scrollOffset != scrollOffset
+        // sometimes this method is called not to scroll, but to stay on the same index when
+        // the data changes, as by default we maintain the scroll position by key, not index.
+        // when this happens we don't need to reset the animations as from the user perspective
+        // we didn't scroll anywhere and if there is an offset change for an item, this change
+        // should be animated.
+        // however, when the request is to really scroll to a different position, we have to
+        // reset previously known item positions as we don't want offset changes to be animated.
+        // this offset should be considered as a scroll, not the placement change.
+        if (positionChanged) {
+            placementAnimator.reset()
+        }
+        scrollPosition.requestPositionAndForgetLastKnownKey(index, scrollOffset)
         remeasurement?.forceRemeasure()
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
index 834e3da..537b368 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
@@ -20,6 +20,8 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
+import androidx.compose.foundation.lazy.layout.estimatedLazyMaxScrollOffset
+import androidx.compose.foundation.lazy.layout.estimatedLazyScrollOffset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.semantics.CollectionInfo
@@ -32,16 +34,20 @@
 ): LazyLayoutSemanticState =
     remember(state, reverseScrolling) {
         object : LazyLayoutSemanticState {
-            override val firstVisibleItemScrollOffset: Int
-                get() = state.firstVisibleItemScrollOffset
-            override val firstVisibleItemIndex: Int
-                get() = state.firstVisibleItemIndex
-            override val canScrollForward: Boolean
-                get() = state.canScrollForward
+            override val scrollOffset: Float
+                get() = estimatedLazyScrollOffset(
+                    state.firstVisibleItemIndex,
+                    state.firstVisibleItemScrollOffset
+                )
+            override val maxScrollOffset: Float
+                get() = estimatedLazyMaxScrollOffset(
+                    state.firstVisibleItemIndex,
+                    state.firstVisibleItemScrollOffset,
+                    state.canScrollForward
+                )
 
-            override suspend fun animateScrollBy(delta: Float) {
+            override suspend fun animateScrollBy(delta: Float): Float =
                 state.animateScrollBy(delta)
-            }
 
             override suspend fun scrollToItem(index: Int) {
                 state.scrollToItem(index)
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 3a08b20..f1d49fd 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
@@ -21,7 +21,6 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.SubcomposeLayout
@@ -111,15 +110,13 @@
             modifier,
             remember(itemContentFactory, measurePolicy) {
                 { constraints ->
-                    Snapshot.withMutableSnapshot {
-                        with(
-                            LazyLayoutMeasureScopeImpl(
-                                itemContentFactory,
-                                this
-                            )
-                        ) {
-                            measurePolicy(constraints)
-                        }
+                    with(
+                        LazyLayoutMeasureScopeImpl(
+                            itemContentFactory,
+                            this
+                        )
+                    ) {
+                        measurePolicy(constraints)
                     }
                 }
             }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimation.kt
index 098f8674..4fc1333 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutAnimation.kt
@@ -24,11 +24,11 @@
 import androidx.compose.animation.core.VisibilityThreshold
 import androidx.compose.animation.core.spring
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.GraphicsLayerScope
+import androidx.compose.ui.graphics.GraphicsContext
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.node.ParentDataModifierNode
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
@@ -37,11 +37,13 @@
 import kotlinx.coroutines.launch
 
 internal class LazyLayoutAnimation(
-    val coroutineScope: CoroutineScope
+    private val coroutineScope: CoroutineScope,
+    private val graphicsContext: GraphicsContext? = null,
+    private val onLayerPropertyChanged: () -> Unit = {}
 ) {
-
-    var appearanceSpec: FiniteAnimationSpec<Float>? = null
+    var fadeInSpec: FiniteAnimationSpec<Float>? = null
     var placementSpec: FiniteAnimationSpec<IntOffset>? = null
+    var fadeOutSpec: FiniteAnimationSpec<Float>? = null
 
     /**
      * Returns true when the placement animation is currently in progress so the parent
@@ -57,6 +59,18 @@
         private set
 
     /**
+     * Returns true when the disappearance animation is currently in progress.
+     */
+    var isDisappearanceAnimationInProgress by mutableStateOf(false)
+        private set
+
+    /**
+     * Returns true when the disappearance animation has been finished.
+     */
+    var isDisappearanceAnimationFinished by mutableStateOf(false)
+        private set
+
+    /**
      * This property is managed by the animation manager and is not directly used by this class.
      * It represents the last known offset of this item in the lazy layout coordinate space.
      * It will be updated on every scroll and is allowing the manager to track when the item
@@ -65,6 +79,18 @@
      */
     var rawOffset: IntOffset = NotInitialized
 
+    /**
+     * The final offset the placeable associated with this animations was placed at. Unlike
+     * [rawOffset] it takes into account things like reverse layout and content padding.
+     */
+    var finalOffset: IntOffset = IntOffset.Zero
+
+    /**
+     * Current [GraphicsLayer]. It will be set to null in [release].
+     */
+    var layer: GraphicsLayer? = graphicsContext?.createGraphicsLayer()
+        private set
+
     private val placementDeltaAnimation = Animatable(IntOffset.Zero, IntOffset.VectorConverter)
 
     private val visibilityAnimation = Animatable(1f, Float.VectorConverter)
@@ -76,13 +102,6 @@
     var placementDelta by mutableStateOf(IntOffset.Zero)
         private set
 
-    var visibility by mutableFloatStateOf(1f)
-        private set
-
-    val layerBlock: GraphicsLayerScope.() -> Unit = {
-        alpha = visibility
-    }
-
     /**
      * Cancels the ongoing placement animation if there is one.
      */
@@ -125,6 +144,7 @@
                 if (!placementDeltaAnimation.isRunning) {
                     // if not running we can snap to the initial value and animate to zero
                     placementDeltaAnimation.snapTo(totalDelta)
+                    onLayerPropertyChanged()
                 }
                 // if animation is not currently running the target will be zero, otherwise
                 // we have to continue the animation from the current value, but keep the needed
@@ -133,6 +153,7 @@
                 placementDeltaAnimation.animateTo(animationTarget, finalSpec) {
                     // placementDelta is calculated as if we always animate to target equal to zero
                     placementDelta = value - animationTarget
+                    onLayerPropertyChanged()
                 }
 
                 isPlacementAnimationInProgress = false
@@ -144,17 +165,32 @@
     }
 
     fun animateAppearance() {
-        val spec = appearanceSpec
-        if (isAppearanceAnimationInProgress || spec == null) {
+        val layer = layer
+        val spec = fadeInSpec
+        if (isAppearanceAnimationInProgress || spec == null || layer == null) {
+            if (isDisappearanceAnimationInProgress) {
+                // we have an active disappearance, and then appearance was requested, but the user
+                // provided null spec for the appearance. we need to immediately switch to 1f
+                layer?.alpha = 1f
+                coroutineScope.launch {
+                    visibilityAnimation.snapTo(1f)
+                }
+            }
             return
         }
         isAppearanceAnimationInProgress = true
-        visibility = 0f
+        val shouldResetValue = !isDisappearanceAnimationInProgress
+        if (shouldResetValue) {
+            layer.alpha = 0f
+        }
         coroutineScope.launch {
             try {
-                visibilityAnimation.snapTo(0f)
+                if (shouldResetValue) {
+                    visibilityAnimation.snapTo(0f)
+                }
                 visibilityAnimation.animateTo(1f, spec) {
-                    visibility = value
+                    layer.alpha = value
+                    onLayerPropertyChanged()
                 }
             } finally {
                 isAppearanceAnimationInProgress = false
@@ -162,7 +198,27 @@
         }
     }
 
-    fun stopAnimations() {
+    fun animateDisappearance() {
+        val layer = layer
+        val spec = fadeOutSpec
+        if (layer == null || isDisappearanceAnimationInProgress || spec == null) {
+            return
+        }
+        isDisappearanceAnimationInProgress = true
+        coroutineScope.launch {
+            try {
+                visibilityAnimation.animateTo(0f, spec) {
+                    layer.alpha = value
+                    onLayerPropertyChanged()
+                }
+                isDisappearanceAnimationFinished = true
+            } finally {
+                isDisappearanceAnimationInProgress = false
+            }
+        }
+    }
+
+    fun release() {
         if (isPlacementAnimationInProgress) {
             isPlacementAnimationInProgress = false
             coroutineScope.launch {
@@ -175,9 +231,19 @@
                 visibilityAnimation.stop()
             }
         }
+        if (isDisappearanceAnimationInProgress) {
+            isDisappearanceAnimationInProgress = false
+            coroutineScope.launch {
+                visibilityAnimation.stop()
+            }
+        }
         placementDelta = IntOffset.Zero
         rawOffset = NotInitialized
-        visibility = 1f
+        layer?.let { graphicsContext?.releaseGraphicsLayer(it) }
+        layer = null
+        fadeInSpec = null
+        fadeOutSpec = null
+        placementSpec = null
     }
 
     companion object {
@@ -186,9 +252,11 @@
 }
 
 internal class LazyLayoutAnimationSpecsNode(
-    var appearanceSpec: FiniteAnimationSpec<Float>?,
-    var placementSpec: FiniteAnimationSpec<IntOffset>?
+    var fadeInSpec: FiniteAnimationSpec<Float>?,
+    var placementSpec: FiniteAnimationSpec<IntOffset>?,
+    var fadeOutSpec: FiniteAnimationSpec<Float>?
 ) : Modifier.Node(), ParentDataModifierNode {
+
     override fun Density.modifyParentData(parentData: Any?): Any = this@LazyLayoutAnimationSpecsNode
 }
 
@@ -199,8 +267,3 @@
     stiffness = Spring.StiffnessMediumLow,
     visibilityThreshold = IntOffset.VisibilityThreshold
 )
-
-/**
- * Block on [GraphicsLayerScope] which applies the default layer parameters.
- */
-internal val DefaultLayerBlock: GraphicsLayerScope.() -> Unit = {}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index e77a308..18a65d3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.SemanticsModifierNode
 import androidx.compose.ui.node.invalidateSemantics
@@ -33,6 +34,7 @@
 import androidx.compose.ui.semantics.indexForKey
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.scrollBy
+import androidx.compose.ui.semantics.scrollByOffset
 import androidx.compose.ui.semantics.scrollToIndex
 import androidx.compose.ui.semantics.verticalScrollAxisRange
 import kotlinx.coroutines.launch
@@ -135,6 +137,7 @@
 
     private var scrollByAction: ((x: Float, y: Float) -> Boolean)? = null
     private var scrollToIndexAction: ((Int) -> Boolean)? = null
+    private var scrollByOffsetAction: (suspend (Offset) -> Offset)? = null
 
     init {
         updateCachedSemanticsValues()
@@ -188,6 +191,10 @@
             scrollToIndex(action = it)
         }
 
+        scrollByOffsetAction?.let {
+            scrollByOffset(action = it)
+        }
+
         getScrollViewportLength {
             it.add((state.viewport - state.contentPadding).toFloat())
             true
@@ -198,8 +205,8 @@
 
     private fun updateCachedSemanticsValues() {
         scrollAxisRange = ScrollAxisRange(
-            value = { state.pseudoScrollOffset() },
-            maxValue = { state.pseudoMaxScrollOffset() },
+            value = { state.scrollOffset },
+            maxValue = { state.maxScrollOffset },
             reverseScrolling = reverseScrolling
         )
 
@@ -217,6 +224,20 @@
             null
         }
 
+        scrollByOffsetAction = if (userScrollEnabled) {
+            { offset ->
+                if (isVertical) {
+                    val consumed = state.animateScrollBy(offset.y)
+                    Offset(0f, consumed)
+                } else {
+                    val consumed = state.animateScrollBy(offset.x)
+                    Offset(consumed, 0f)
+                }
+            }
+        } else {
+            null
+        }
+
         scrollToIndexAction = if (userScrollEnabled) {
             { index ->
                 val itemProvider = itemProviderLambda()
@@ -236,41 +257,49 @@
 }
 
 internal interface LazyLayoutSemanticState {
-    val firstVisibleItemScrollOffset: Int
-    val firstVisibleItemIndex: Int
-    val canScrollForward: Boolean
     val viewport: Int
     val contentPadding: Int
+    val scrollOffset: Float
+    val maxScrollOffset: Float
 
     fun collectionInfo(): CollectionInfo
-    suspend fun animateScrollBy(delta: Float)
+    suspend fun animateScrollBy(delta: Float): Float
     suspend fun scrollToItem(index: Int)
+}
 
-    // It is impossible for lazy lists to provide an absolute scroll offset because the size of the
-    // items above the viewport is not known, but the AccessibilityEvent system API expects one
-    // anyway. So this provides a best-effort pseudo-offset that avoids breaking existing behavior.
-    //
-    // The key properties that A11y services are known to actually rely on are:
-    // A) each scroll change generates a TYPE_VIEW_SCROLLED AccessibilityEvent
-    // B) the integer offset in the AccessibilityEvent is different than the last one (note that the
-    // magnitude and direction of the change does not matter for the known use cases)
-    // C) scrollability is indicated by whether the scroll position is exactly 0 or exactly
-    // maxScrollOffset
-    //
-    // To preserve property B) as much as possible, the constant 500 is chosen to be larger than a
-    // single scroll delta would realistically be, while small enough to avoid losing precision due
-    // to the 24-bit float significand of ScrollAxisRange with realistic list sizes (if there are
-    // fewer than ~16000 items, the integer value is exactly preserved).
-    fun pseudoScrollOffset() =
-        (firstVisibleItemScrollOffset + firstVisibleItemIndex * 500).toFloat()
+// It is impossible for lazy lists to provide an absolute scroll offset because the size of the
+// items above the viewport is not known, but the AccessibilityEvent system API expects one
+// anyway. So this provides a best-effort pseudo-offset that avoids breaking existing behavior.
+//
+// The key properties that A11y services are known to actually rely on are:
+// A) each scroll change generates a TYPE_VIEW_SCROLLED AccessibilityEvent
+// B) the integer offset in the AccessibilityEvent is different than the last one (note that the
+// magnitude and direction of the change does not matter for the known use cases)
+// C) scrollability is indicated by whether the scroll position is exactly 0 or exactly
+// maxScrollOffset
+//
+// To preserve property B) as much as possible, the constant 500 is chosen to be larger than a
+// single scroll delta would realistically be, while small enough to avoid losing precision due
+// to the 24-bit float significand of ScrollAxisRange with realistic list sizes (if there are
+// fewer than ~16000 items, the integer value is exactly preserved).
+internal fun estimatedLazyScrollOffset(
+    firstVisibleItemIndex: Int,
+    firstVisibleItemScrollOffset: Int
+): Float {
+    return (firstVisibleItemScrollOffset + firstVisibleItemIndex * 500).toFloat()
+}
 
-    fun pseudoMaxScrollOffset() =
-        if (canScrollForward) {
-            // If we can scroll further, indicate that by setting it slightly higher than
-            // the current value
-            pseudoScrollOffset() + 100
-        } else {
-            // If we can't scroll further, the current value is the max
-            pseudoScrollOffset()
-        }.toFloat()
+internal fun estimatedLazyMaxScrollOffset(
+    firstVisibleItemIndex: Int,
+    firstVisibleItemScrollOffset: Int,
+    canScrollForward: Boolean
+): Float {
+    return if (canScrollForward) {
+        // If we can scroll further, indicate that by setting it slightly higher than
+        // the current value
+        estimatedLazyScrollOffset(firstVisibleItemIndex, firstVisibleItemScrollOffset) + 100
+    } else {
+        // If we can't scroll further, the current value is the max
+        estimatedLazyScrollOffset(firstVisibleItemIndex, firstVisibleItemScrollOffset)
+    }.toFloat()
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemPlacementAnimator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemPlacementAnimator.kt
index c6a4058..d2539b8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemPlacementAnimator.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemPlacementAnimator.kt
@@ -302,7 +302,7 @@
         coroutineScope: CoroutineScope
     ) {
         for (i in positionedItem.placeablesCount until animations.size) {
-            animations[i]?.stopAnimations()
+            animations[i]?.release()
         }
         if (animations.size != positionedItem.placeablesCount) {
             animations = animations.copyOf(positionedItem.placeablesCount)
@@ -310,13 +310,13 @@
         repeat(positionedItem.placeablesCount) { index ->
             val specs = positionedItem.getParentData(index).specs
             if (specs == null) {
-                animations[index]?.stopAnimations()
+                animations[index]?.release()
                 animations[index] = null
             } else {
                 val item = animations[index] ?: LazyLayoutAnimation(coroutineScope).also {
                     animations[index] = it
                 }
-                item.appearanceSpec = specs.appearanceSpec
+                item.fadeInSpec = specs.fadeInSpec
                 item.placementSpec = specs.placementSpec
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemScope.kt
index 6f35395..919d313 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemScope.kt
@@ -67,7 +67,7 @@
 ) : ModifierNodeElement<LazyLayoutAnimationSpecsNode>() {
 
     override fun create(): LazyLayoutAnimationSpecsNode =
-        LazyLayoutAnimationSpecsNode(null, placementSpec)
+        LazyLayoutAnimationSpecsNode(null, placementSpec, null)
 
     override fun update(node: LazyLayoutAnimationSpecsNode) {
         node.placementSpec = placementSpec
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
index 1488567..1590e42 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.lazy.layout.calculateLazyLayoutPinnedIndices
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
@@ -96,26 +97,28 @@
             state.beyondBoundsInfo
         )
 
-        measureStaggeredGrid(
-            state = state,
-            pinnedItems = pinnedItems,
-            itemProvider = itemProvider,
-            resolvedSlots = resolvedSlots,
-            constraints = constraints.copy(
-                minWidth = constraints.constrainWidth(horizontalPadding),
-                minHeight = constraints.constrainHeight(verticalPadding)
-            ),
-            mainAxisSpacing = mainAxisSpacing.roundToPx(),
-            contentOffset = contentOffset,
-            mainAxisAvailableSize = mainAxisAvailableSize,
-            isVertical = isVertical,
-            reverseLayout = reverseLayout,
-            beforeContentPadding = beforeContentPadding,
-            afterContentPadding = afterContentPadding,
-            coroutineScope = coroutineScope
-        ).also {
-            state.applyMeasureResult(it)
+        val measureResult = Snapshot.withMutableSnapshot {
+            measureStaggeredGrid(
+                state = state,
+                pinnedItems = pinnedItems,
+                itemProvider = itemProvider,
+                resolvedSlots = resolvedSlots,
+                constraints = constraints.copy(
+                    minWidth = constraints.constrainWidth(horizontalPadding),
+                    minHeight = constraints.constrainHeight(verticalPadding)
+                ),
+                mainAxisSpacing = mainAxisSpacing.roundToPx(),
+                contentOffset = contentOffset,
+                mainAxisAvailableSize = mainAxisAvailableSize,
+                isVertical = isVertical,
+                reverseLayout = reverseLayout,
+                beforeContentPadding = beforeContentPadding,
+                afterContentPadding = afterContentPadding,
+                coroutineScope = coroutineScope
+            )
         }
+        state.applyMeasureResult(measureResult)
+        measureResult
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollPosition.kt
index 4e2e705..19591fc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollPosition.kt
@@ -116,7 +116,7 @@
      * c) there will be not enough items to fill the viewport after the requested index, so we
      * would have to compose few elements before the asked index, changing the first visible item.
      */
-    fun requestPosition(index: Int, scrollOffset: Int) {
+    fun requestPositionAndForgetLastKnownKey(index: Int, scrollOffset: Int) {
         val newIndices = fillIndices(index, indices.size)
         val newOffsets = IntArray(newIndices.size) { scrollOffset }
         update(newIndices, newOffsets)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt
index 009f1e1..72f0894 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemantics.kt
@@ -20,6 +20,8 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
+import androidx.compose.foundation.lazy.layout.estimatedLazyMaxScrollOffset
+import androidx.compose.foundation.lazy.layout.estimatedLazyScrollOffset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.semantics.CollectionInfo
@@ -32,16 +34,20 @@
 ): LazyLayoutSemanticState =
     remember(state, reverseScrolling) {
         object : LazyLayoutSemanticState {
-            override val firstVisibleItemScrollOffset: Int
-                get() = state.firstVisibleItemScrollOffset
-            override val firstVisibleItemIndex: Int
-                get() = state.firstVisibleItemIndex
-            override val canScrollForward: Boolean
-                get() = state.canScrollForward
+            override val scrollOffset: Float
+                get() = estimatedLazyScrollOffset(
+                    state.firstVisibleItemIndex,
+                    state.firstVisibleItemScrollOffset
+                )
+            override val maxScrollOffset: Float
+                get() = estimatedLazyMaxScrollOffset(
+                    state.firstVisibleItemIndex,
+                    state.firstVisibleItemScrollOffset,
+                    state.canScrollForward
+                )
 
-            override suspend fun animateScrollBy(delta: Float) {
+            override suspend fun animateScrollBy(delta: Float): Float =
                 state.animateScrollBy(delta)
-            }
 
             override suspend fun scrollToItem(index: Int) {
                 state.scrollToItem(index)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index 6b48d88..003f83b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -324,9 +324,22 @@
     }
 
     internal fun ScrollScope.snapToItemInternal(index: Int, scrollOffset: Int) {
+        val positionChanged = scrollPosition.index != index ||
+            scrollPosition.scrollOffset != scrollOffset
+        // sometimes this method is called not to scroll, but to stay on the same index when
+        // the data changes, as by default we maintain the scroll position by key, not index.
+        // when this happens we don't need to reset the animations as from the user perspective
+        // we didn't scroll anywhere and if there is an offset change for an item, this change
+        // should be animated.
+        // however, when the request is to really scroll to a different position, we have to
+        // reset previously known item positions as we don't want offset changes to be animated.
+        // this offset should be considered as a scroll, not the placement change.
+        if (positionChanged) {
+            placementAnimator.reset()
+        }
         val layoutInfo = layoutInfo
         val visibleItem = layoutInfo.findVisibleItem(index)
-        if (visibleItem != null) {
+        if (visibleItem != null && positionChanged) {
             val currentOffset = if (layoutInfo.orientation == Orientation.Vertical) {
                 visibleItem.offset.y
             } else {
@@ -335,9 +348,9 @@
             val delta = currentOffset + scrollOffset
             scrollBy(delta.toFloat())
         } else {
-            scrollPosition.requestPosition(index, scrollOffset)
-            remeasurement?.forceRemeasure()
+            scrollPosition.requestPositionAndForgetLastKnownKey(index, scrollOffset)
         }
+        remeasurement?.forceRemeasure()
     }
 
     /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index e4de927..97f97a6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -127,7 +127,6 @@
 
     val semanticState = rememberPagerSemanticState(
         state,
-        reverseLayout,
         orientation == Orientation.Vertical
     )
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
index 2d93543..13ec0b5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
@@ -16,27 +16,21 @@
 
 package androidx.compose.foundation.pager
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
 import androidx.compose.ui.semantics.CollectionInfo
 
-@OptIn(ExperimentalFoundationApi::class)
 internal fun LazyLayoutSemanticState(
     state: PagerState,
     isVertical: Boolean
 ): LazyLayoutSemanticState = object : LazyLayoutSemanticState {
-    override val firstVisibleItemScrollOffset: Int
-        get() = state.firstVisiblePageOffset
-    override val firstVisibleItemIndex: Int
-        get() = state.firstVisiblePage
-    override val canScrollForward: Boolean
-        get() = state.canScrollForward
+    override val scrollOffset: Float
+        get() = state.currentAbsoluteScrollOffset().toFloat()
+    override val maxScrollOffset: Float
+        get() = state.layoutInfo.calculateNewMaxScrollOffset(state.pageCount).toFloat()
 
-    override suspend fun animateScrollBy(delta: Float) {
-        state.animateScrollBy(delta)
-    }
+    override suspend fun animateScrollBy(delta: Float): Float = state.animateScrollBy(delta)
 
     override suspend fun scrollToItem(index: Int) {
         state.scrollToPage(index)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageSize.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageSize.kt
index 4c14c2e..2157c1f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageSize.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PageSize.kt
@@ -33,8 +33,8 @@
 
     /**
      * Based on [availableSpace] pick a size for the pages
-     * @param availableSpace The amount of space the pages in this Pager can use.
-     * @param pageSpacing The amount of space used to separate pages.
+     * @param availableSpace The amount of space in pixels the pages in this Pager can use.
+     * @param pageSpacing The amount of space in pixels used to separate pages.
      */
     fun Density.calculateMainAxisPageSize(availableSpace: Int, pageSpacing: Int): Int
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
index 26dce70..490f86b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
@@ -22,7 +22,6 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.rememberSplineBasedDecay
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.TargetedFlingBehavior
@@ -116,9 +115,10 @@
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
     key: ((index: Int) -> Any)? = null,
-    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
-        PagerDefaults.pageNestedScrollConnection(state, Orientation.Horizontal)
-    },
+    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
+        state,
+        Orientation.Horizontal
+    ),
     snapPosition: SnapPosition = SnapPosition.Start,
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
@@ -202,9 +202,10 @@
     userScrollEnabled: Boolean = true,
     reverseLayout: Boolean = false,
     key: ((index: Int) -> Any)? = null,
-    pageNestedScrollConnection: NestedScrollConnection = remember(state) {
-        PagerDefaults.pageNestedScrollConnection(state, Orientation.Vertical)
-    },
+    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
+        state,
+        Orientation.Vertical
+    ),
     snapPosition: SnapPosition = SnapPosition.Start,
     pageContent: @Composable PagerScope.(page: Int) -> Unit
 ) {
@@ -287,7 +288,6 @@
      * position. If the velocity is high enough, the Pager will use the logic described in
      * [decayAnimationSpec] and [snapAnimationSpec].
      */
-    @OptIn(ExperimentalFoundationApi::class)
     @Composable
     fun flingBehavior(
         state: PagerState,
@@ -341,11 +341,14 @@
      * @param orientation The orientation of the pager. This will be used to determine which
      * direction the nested scroll connection will operate and react on.
      */
+    @Composable
     fun pageNestedScrollConnection(
         state: PagerState,
         orientation: Orientation
     ): NestedScrollConnection {
-        return DefaultPagerNestedScrollConnection(state, orientation)
+        return remember(state, orientation) {
+            DefaultPagerNestedScrollConnection(state, orientation)
+        }
     }
 
     /**
@@ -391,14 +394,6 @@
         }
     }
 
-    fun Offset.consumeOnOrientation(orientation: Orientation): Offset {
-        return if (orientation == Orientation.Vertical) {
-            copy(x = 0f)
-        } else {
-            copy(y = 0f)
-        }
-    }
-
     override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
         return if (
         // rounding error and drag only
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt
index e1589eb..eb0103c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerBeyondBoundsModifier.kt
@@ -15,12 +15,10 @@
  */
 package androidx.compose.foundation.pager
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 internal fun rememberPagerBeyondBoundsState(
     state: PagerState,
@@ -31,7 +29,6 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 internal class PagerBeyondBoundsState(
     private val state: PagerState,
     private val outOfBoundsPageCount: Int
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt
index cf6a1da..1739224 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLayoutInfo.kt
@@ -33,12 +33,13 @@
     val visiblePagesInfo: List<PageInfo>
 
     /**
-     * The size of the Pages in this [Pager] provided by the [PageSize] API in the Pager definition.
+     * The main axis size of the Pages in this [Pager] provided by the [PageSize] API in the
+     * Pager definition. This is provided in pixels.
      */
     val pageSize: Int
 
     /**
-     * The spacing provided in the [Pager] creation.
+     * The spacing in pixels provided in the [Pager] creation.
      */
     val pageSpacing: Int
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
index e708661..7989ea8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
@@ -63,6 +63,7 @@
     pageSize,
     snapPosition,
     pageCount,
+    outOfBoundsPageCount
 ) {
     { containerConstraints ->
         val isVertical = orientation == Orientation.Vertical
@@ -163,36 +164,38 @@
             beyondBoundsInfo = state.beyondBoundsInfo
         )
 
-        measurePager(
-            beforeContentPadding = beforeContentPadding,
-            afterContentPadding = afterContentPadding,
-            constraints = contentConstraints,
-            pageCount = pageCount(),
-            spaceBetweenPages = spaceBetweenPages,
-            mainAxisAvailableSize = mainAxisAvailableSize,
-            visualPageOffset = visualItemOffset,
-            pageAvailableSize = pageAvailableSize,
-            outOfBoundsPageCount = outOfBoundsPageCount,
-            orientation = orientation,
-            currentPage = currentPage,
-            currentPageOffset = currentPageOffset,
-            horizontalAlignment = horizontalAlignment,
-            verticalAlignment = verticalAlignment,
-            pagerItemProvider = itemProvider,
-            reverseLayout = reverseLayout,
-            pinnedPages = pinnedPages,
-            snapPosition = snapPosition,
-            placementScopeInvalidator = state.placementScopeInvalidator,
-            layout = { width, height, placement ->
-                layout(
-                    containerConstraints.constrainWidth(width + totalHorizontalPadding),
-                    containerConstraints.constrainHeight(height + totalVerticalPadding),
-                    emptyMap(),
-                    placement
-                )
-            }
-        ).also {
-            state.applyMeasureResult(it)
+        val measureResult = Snapshot.withMutableSnapshot {
+            measurePager(
+                beforeContentPadding = beforeContentPadding,
+                afterContentPadding = afterContentPadding,
+                constraints = contentConstraints,
+                pageCount = pageCount(),
+                spaceBetweenPages = spaceBetweenPages,
+                mainAxisAvailableSize = mainAxisAvailableSize,
+                visualPageOffset = visualItemOffset,
+                pageAvailableSize = pageAvailableSize,
+                outOfBoundsPageCount = outOfBoundsPageCount,
+                orientation = orientation,
+                currentPage = currentPage,
+                currentPageOffset = currentPageOffset,
+                horizontalAlignment = horizontalAlignment,
+                verticalAlignment = verticalAlignment,
+                pagerItemProvider = itemProvider,
+                reverseLayout = reverseLayout,
+                pinnedPages = pinnedPages,
+                snapPosition = snapPosition,
+                placementScopeInvalidator = state.placementScopeInvalidator,
+                layout = { width, height, placement ->
+                    layout(
+                        containerConstraints.constrainWidth(width + totalHorizontalPadding),
+                        containerConstraints.constrainHeight(height + totalVerticalPadding),
+                        emptyMap(),
+                        placement
+                    )
+                }
+            )
         }
+        state.applyMeasureResult(measureResult)
+        measureResult
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
index bf6ab3b..ff344a5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
@@ -86,10 +86,8 @@
             return false
         }
 
-        val first =
-            if (extraPagesBefore.isEmpty()) visiblePagesInfo.first() else extraPagesBefore.first()
-        val last =
-            if (extraPagesAfter.isEmpty()) visiblePagesInfo.last() else extraPagesAfter.last()
+        val first = visiblePagesInfo.first()
+        val last = visiblePagesInfo.last()
 
         val canApply = if (delta < 0) {
             // scrolling forward
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
index 5a027a1..c182417 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
@@ -110,12 +110,6 @@
         currentPageOffsetFraction = offsetFraction
     }
 
-    fun currentAbsoluteScrollOffset(): Long {
-        val currentPageOffset = currentPage.toLong() * state.pageSizeWithSpacing
-        val offsetFraction = (currentPageOffsetFraction * state.pageSizeWithSpacing).roundToLong()
-        return currentPageOffset + offsetFraction
-    }
-
     fun applyScrollDelta(delta: Int) {
         debugLog { "Applying Delta=$delta" }
         val fractionUpdate = if (state.pageSizeWithSpacing == 0) {
@@ -143,3 +137,9 @@
         println("PagerScrollPosition: ${generateMsg()}")
     }
 }
+
+internal fun PagerState.currentAbsoluteScrollOffset(): Long {
+    val currentPageOffset = currentPage.toLong() * pageSizeWithSpacing
+    val offsetFraction = (currentPageOffsetFraction * pageSizeWithSpacing).roundToLong()
+    return currentPageOffset + offsetFraction
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSemantics.kt
index 4647096..073834d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSemantics.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSemantics.kt
@@ -16,19 +16,16 @@
 
 package androidx.compose.foundation.pager
 
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.lazy.layout.LazyLayoutSemanticState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 internal fun rememberPagerSemanticState(
     state: PagerState,
-    reverseScrolling: Boolean,
     isVertical: Boolean
 ): LazyLayoutSemanticState {
-    return remember(state, reverseScrolling, isVertical) {
+    return remember(state, isVertical) {
         LazyLayoutSemanticState(state, isVertical)
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
index bba81d7..a7cfe05 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerSnapDistance.kt
@@ -34,8 +34,8 @@
      * will be the page that will be correctly positioned (snapped) after naturally decaying with
      * [velocity] using a [DecayAnimationSpec].
      * @param velocity The initial fling velocity.
-     * @param pageSize The page size for this [Pager].
-     * @param pageSpacing The spacing used between pages.
+     * @param pageSize The page size for this [Pager] in pixels.
+     * @param pageSpacing The spacing used between pages in pixels.
      *
      * @return An updated target page where to settle. Note that this value needs to be between 0
      * and the total count of pages in this pager. If an invalid value is passed, the pager will
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index cce7c8f..1535b73 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -171,7 +171,7 @@
     internal var upDownDifference: Offset by mutableStateOf(Offset.Zero)
     private val animatedScrollScope = PagerLazyAnimateScrollScope(this)
 
-    internal val scrollPosition = PagerScrollPosition(currentPage, currentPageOffsetFraction, this)
+    private val scrollPosition = PagerScrollPosition(currentPage, currentPageOffsetFraction, this)
 
     internal var firstVisiblePage = currentPage
         private set
@@ -180,10 +180,8 @@
         private set
 
     private var maxScrollOffset: Long = Long.MAX_VALUE
-        private set
 
     private var minScrollOffset: Long = 0L
-        private set
 
     private var accumulator: Float = 0.0f
 
@@ -204,7 +202,7 @@
      * determine scroll deltas and max min scrolling.
      */
     private fun performScroll(delta: Float): Float {
-        val currentScrollPosition = scrollPosition.currentAbsoluteScrollOffset()
+        val currentScrollPosition = currentAbsoluteScrollOffset()
         debugLog {
             "\nDelta=$delta " +
                 "\ncurrentScrollPosition=$currentScrollPosition " +
@@ -276,8 +274,7 @@
     internal var layoutWithMeasurement: Int = 0
         private set
 
-    internal var layoutWithoutMeasurement: Int = 0
-        private set
+    private var layoutWithoutMeasurement: Int = 0
 
     /**
      * Only used for testing to disable prefetching when needed to test the main logic.
@@ -776,12 +773,10 @@
     ): Int = scrollPosition.matchPageWithKey(itemProvider, currentPage)
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 internal suspend fun PagerState.animateToNextPage() {
     if (currentPage + 1 < pageCount) animateScrollToPage(currentPage + 1)
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 internal suspend fun PagerState.animateToPreviousPage() {
     if (currentPage - 1 >= 0) animateScrollToPage(currentPage - 1)
 }
@@ -830,7 +825,7 @@
     }
 }
 
-private fun PagerMeasureResult.calculateNewMaxScrollOffset(pageCount: Int): Long {
+internal fun PagerLayoutInfo.calculateNewMaxScrollOffset(pageCount: Int): Long {
     val pageSizeWithSpacing = pageSpacing + pageSize
     val maxScrollPossible =
         (pageCount.toLong()) * pageSizeWithSpacing + beforeContentPadding + afterContentPadding
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
index ed4711b..caa756a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Selectable.kt
@@ -140,7 +140,6 @@
     role: Role? = null,
     onClick: () -> Unit
 ) = clickableWithIndicationIfNeeded(
-    enabled = enabled,
     interactionSource = interactionSource,
     indication = indication
 ) { intSource, indicationNodeFactory ->
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
index c98b15a..b75e3c1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/Toggleable.kt
@@ -134,7 +134,6 @@
     role: Role? = null,
     onValueChange: (Boolean) -> Unit
 ) = clickableWithIndicationIfNeeded(
-    enabled = enabled,
     interactionSource = interactionSource,
     indication = indication
 ) { intSource, indicationNodeFactory ->
@@ -370,7 +369,6 @@
     role: Role? = null,
     onClick: () -> Unit
 ) = clickableWithIndicationIfNeeded(
-    enabled = enabled,
     interactionSource = interactionSource,
     indication = indication
 ) { intSource, indicationNodeFactory ->
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
index 1d4daa6..0b4f6a4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicSecureTextField.kt
@@ -17,11 +17,9 @@
 package androidx.compose.foundation.text
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.text.input.ImeActionHandler
 import androidx.compose.foundation.text.input.InputTransformation
 import androidx.compose.foundation.text.input.TextFieldBuffer
@@ -35,10 +33,10 @@
 import androidx.compose.foundation.text.input.then
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.onFocusChanged
@@ -58,12 +56,10 @@
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.Density
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.consumeAsFlow
-import kotlinx.coroutines.launch
 
 /**
  * BasicSecureTextField is specifically designed for password entry fields and is a preconfigured
@@ -74,21 +70,15 @@
  *
  * @param state [TextFieldState] object that holds the internal state of a [BasicTextField].
  * @param modifier optional [Modifier] for this text field.
- * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable.
  * @param onSubmit Called when the user submits a form either by pressing the action button in the
  * input method editor (IME), or by pressing the enter key on a hardware keyboard. If the user
  * submits the form by pressing the action button in the IME, the provided IME action is passed to
  * the function. If the user submits the form by pressing the enter key on a hardware keyboard,
- * the defined [imeAction] parameter is passed to the function. Return true to indicate that the
- * action has been handled completely, which will skip the default behavior, such as hiding the
- * keyboard for the [ImeAction.Done] action.
- * @param imeAction The IME action. This IME action is honored by keyboard and may show specific
- * icons on the keyboard.
- * @param textObfuscationMode Determines the method used to obscure the input text.
- * @param keyboardType The keyboard type to be used in this text field. It is set to
- * [KeyboardType.Password] by default. Use [KeyboardType.NumberPassword] for numerical password
- * fields.
+ * the defined [KeyboardOptions.imeAction] parameter is passed to the function. Return true to
+ * indicate that the action has been handled completely, which will skip the default behavior,
+ * such as hiding the keyboard for the [ImeAction.Done] action.
+ * @param enabled controls the enabled state of the [BasicTextField]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable.
  * @param inputTransformation Optional [InputTransformation] that will be used to transform changes
  * to the [TextFieldState] made by the user. The transformation will be applied to changes made by
  * hardware and software keyboard events, pasting or dropping text, accessibility services, and
@@ -97,12 +87,10 @@
  * it will be applied to the next user edit. The transformation will not immediately affect the
  * current [state].
  * @param textStyle Style configuration for text content that's displayed in the editor.
- * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
- * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
- * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
- * for different [Interaction]s.
- * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
- * provided, there will be no cursor drawn.
+ * @param keyboardOptions Software keyboard options that contain configurations such as
+ * [KeyboardType] and [ImeAction]. This composable by default configures [KeyboardOptions] for a
+ * secure text field by disabling auto correct and setting [KeyboardType] to
+ * [KeyboardType.Password].
  * @param onTextLayout Callback that is executed when the text layout becomes queryable. The
  * callback receives a function that returns a [TextLayoutResult] if the layout can be calculated,
  * or null if it cannot. The function reads the layout result from a snapshot state object, and will
@@ -111,10 +99,15 @@
  * add additional decoration or functionality to the text. For example, to draw a cursor or
  * selection around the text. [Density] scope is the one that was used while creating the given text
  * layout.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
+ * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
+ * for different [Interaction]s.
+ * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
+ * provided, there will be no cursor drawn.
  * @param decorator Allows to add decorations around text field, such as icon, placeholder, helper
  * messages or similar, and automatically increase the hit target area of the text field.
- * @param scrollState Used to manage the horizontal scroll when the input content exceeds the
- * bounds of the text field. It controls the state of the scroll for the text field.
+ * @param textObfuscationMode Determines the method used to obscure the input text.
  */
 @ExperimentalFoundationApi
 // This takes a composable lambda, but it is not primarily a container.
@@ -125,23 +118,22 @@
     modifier: Modifier = Modifier,
     // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
     onSubmit: ImeActionHandler? = null,
-    imeAction: ImeAction = ImeAction.Default,
-    textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
-    keyboardType: KeyboardType = KeyboardType.Password,
     enabled: Boolean = true,
     inputTransformation: InputTransformation? = null,
     textStyle: TextStyle = TextStyle.Default,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.SecureTextField,
+    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
     interactionSource: MutableInteractionSource? = null,
     cursorBrush: Brush = SolidColor(Color.Black),
-    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
     decorator: TextFieldDecorator? = null,
-    scrollState: ScrollState = rememberScrollState(),
     // Last parameter must not be a function unless it's intended to be commonly used as a trailing
     // lambda.
+    textObfuscationMode: TextObfuscationMode = TextObfuscationMode.RevealLastTyped,
 ) {
-    val coroutineScope = rememberCoroutineScope()
-    val secureTextFieldController = remember(coroutineScope) {
-        SecureTextFieldController(coroutineScope)
+    val secureTextFieldController = remember { SecureTextFieldController() }
+    LaunchedEffect(secureTextFieldController) {
+        // start a coroutine that listens for scheduled hide events.
+        secureTextFieldController.observeHideEvents()
     }
 
     // revealing last typed character depends on two conditions;
@@ -151,7 +143,7 @@
 
     // while toggling between obfuscation methods if the revealing gets disabled, reset the reveal.
     if (!revealLastTypedEnabled) {
-        secureTextFieldController.passwordRevealFilter.hide()
+        secureTextFieldController.passwordInputTransformation.hide()
     }
 
     val codepointTransformation = when {
@@ -160,7 +152,7 @@
         }
 
         textObfuscationMode == TextObfuscationMode.Hidden -> {
-            CodepointTransformation.mask('\u2022')
+            PasswordCodepointTransformation
         }
 
         else -> null
@@ -187,72 +179,61 @@
             enabled = enabled,
             readOnly = false,
             inputTransformation = if (revealLastTypedEnabled) {
-                inputTransformation.then(secureTextFieldController.passwordRevealFilter)
+                inputTransformation.then(secureTextFieldController.passwordInputTransformation)
             } else inputTransformation,
             textStyle = textStyle,
-            interactionSource = interactionSource,
-            cursorBrush = cursorBrush,
-            lineLimits = TextFieldLineLimits.SingleLine,
-            scrollState = scrollState,
-            keyboardOptions = KeyboardOptions(
-                autoCorrect = false,
-                keyboardType = keyboardType,
-                imeAction = imeAction
-            ),
+            keyboardOptions = keyboardOptions,
             keyboardActions = onSubmit?.let { KeyboardActions(onSubmit = it::onImeAction) }
                 ?: KeyboardActions.Default,
+            lineLimits = TextFieldLineLimits.SingleLine,
             onTextLayout = onTextLayout,
+            interactionSource = interactionSource,
+            cursorBrush = cursorBrush,
             codepointTransformation = codepointTransformation,
             decorator = decorator,
         )
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
-internal class SecureTextFieldController(
-    coroutineScope: CoroutineScope
-) {
+internal class SecureTextFieldController {
     /**
      * A special [InputTransformation] that tracks changes to the content to identify the last typed
      * character to reveal. `scheduleHide` lambda is delegated to a member function to be able to
-     * use [passwordRevealFilter] instance.
+     * use [passwordInputTransformation] instance.
      */
-    val passwordRevealFilter = PasswordRevealFilter(::scheduleHide)
+    val passwordInputTransformation = PasswordInputTransformation(::scheduleHide)
 
     /**
      * Pass to [BasicTextField] for obscuring text input.
      */
     val codepointTransformation = CodepointTransformation { codepointIndex, codepoint ->
-        if (codepointIndex == passwordRevealFilter.revealCodepointIndex) {
+        if (codepointIndex == passwordInputTransformation.revealCodepointIndex) {
             // reveal the last typed character by not obscuring it
             codepoint
         } else {
-            0x2022
+            DEFAULT_OBFUSCATION_MASK.code
         }
     }
 
     val focusChangeModifier = Modifier.onFocusChanged {
-        if (!it.isFocused) passwordRevealFilter.hide()
+        if (!it.isFocused) passwordInputTransformation.hide()
     }
 
     private val resetTimerSignal = Channel<Unit>(Channel.UNLIMITED)
 
-    init {
-        // start a coroutine that listens for scheduled hide events.
-        coroutineScope.launch {
-            resetTimerSignal.consumeAsFlow()
-                .collectLatest {
-                    delay(LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS)
-                    passwordRevealFilter.hide()
-                }
-        }
+    suspend fun observeHideEvents() {
+        resetTimerSignal.consumeAsFlow()
+            .collectLatest {
+                delay(LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS)
+                passwordInputTransformation.hide()
+            }
     }
 
     private fun scheduleHide() {
         // signal the listener that a new hide call is scheduled.
         val result = resetTimerSignal.trySend(Unit)
-        if (!result.isSuccess) {
-            passwordRevealFilter.hide()
+        if (result.isFailure) {
+            passwordInputTransformation.hide()
         }
     }
 }
@@ -265,7 +246,7 @@
  * typed.
  */
 @OptIn(ExperimentalFoundationApi::class)
-internal class PasswordRevealFilter(
+internal class PasswordInputTransformation(
     val scheduleHide: () -> Unit
 ) : InputTransformation {
     // TODO: Consider setting this as a tracking annotation in AnnotatedString.
@@ -306,6 +287,11 @@
 // adopted from PasswordTransformationMethod from Android platform.
 private const val LAST_TYPED_CHARACTER_REVEAL_DURATION_MILLIS = 1500L
 
+private const val DEFAULT_OBFUSCATION_MASK = '\u2022'
+
+@OptIn(ExperimentalFoundationApi::class)
+private val PasswordCodepointTransformation = CodepointTransformation.mask(DEFAULT_OBFUSCATION_MASK)
+
 // TODO(b/297425359) Investigate cleaning up the IME action handling APIs.
 @OptIn(ExperimentalFoundationApi::class)
 private fun KeyboardActions(onSubmit: ImeActionHandler) = KeyboardActions(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
index 058feba..3f0fa7b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicTextField.kt
@@ -81,6 +81,10 @@
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 
+private object BasicTextFieldDefaults {
+    val CursorBrush = SolidColor(Color.Black)
+}
+
 /**
  * Basic text composable that provides an interactive box that accepts text input through software
  * or hardware keyboard, but provides no decorations like hint or placeholder.
@@ -90,13 +94,11 @@
  * Similarly, all the programmatic updates made to [state] also reflect on this composable.
  *
  * If you want to add decorations to your text field, such as icon or similar, and increase the
- * hit target area, use the decorator:
- * @sample androidx.compose.foundation.samples.BasicTextFieldDecoratorSample
+ * hit target area, use the decorator.
  *
  * In order to filter (e.g. only allow digits, limit the number of characters), or change (e.g.
  * convert every character to uppercase) the input received from the user, use an
  * [InputTransformation].
- * @sample androidx.compose.foundation.samples.BasicTextFieldCustomInputTransformationSample
  *
  * Limiting the height of the [BasicTextField] in terms of line count and choosing a scroll
  * direction can be achieved by using [TextFieldLineLimits].
@@ -108,7 +110,6 @@
  * It's also possible to internally wrap around an existing TextFieldState and expose a more
  * lightweight state hoisting mechanism through a value that dictates the content of the TextField
  * and an onValueChange callback that communicates the changes to this value.
- * @sample androidx.compose.foundation.samples.BasicTextFieldWithValueOnValueChangeSample
  *
  * @param state [TextFieldState] object that holds the internal editing state of [BasicTextField].
  * @param modifier optional [Modifier] for this text field.
@@ -156,6 +157,12 @@
  * @param scrollState Scroll state that manages either horizontal or vertical scroll of TextField.
  * If [lineLimits] is [SingleLine], this text field is treated as single line with horizontal
  * scroll behavior. In other cases the text field becomes vertically scrollable.
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextFieldDecoratorSample
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextFieldCustomInputTransformationSample
+ *
+ * @sample androidx.compose.foundation.samples.BasicTextFieldWithValueOnValueChangeSample
  */
 // This takes a composable lambda, but it is not primarily a container.
 @Suppress("ComposableLambdaParameterPosition")
@@ -172,7 +179,7 @@
     lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
     onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
     interactionSource: MutableInteractionSource? = null,
-    cursorBrush: Brush = SolidColor(Color.Black),
+    cursorBrush: Brush = BasicTextFieldDefaults.CursorBrush,
     outputTransformation: OutputTransformation? = null,
     decorator: TextFieldDecorator? = null,
     scrollState: ScrollState = rememberScrollState(),
@@ -221,7 +228,7 @@
     lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
     onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
     interactionSource: MutableInteractionSource? = null,
-    cursorBrush: Brush = SolidColor(Color.Black),
+    cursorBrush: Brush = BasicTextFieldDefaults.CursorBrush,
     codepointTransformation: CodepointTransformation? = null,
     outputTransformation: OutputTransformation? = null,
     decorator: TextFieldDecorator? = null,
@@ -534,7 +541,7 @@
  * for entering a credit card number:
  * @sample androidx.compose.foundation.samples.CreditCardSample
  *
- * Note: This overload does not support [KeyboardOptions.shouldShowKeyboardOnFocus].
+ * Note: This overload does not support [KeyboardOptions.showKeyboardOnFocus].
  *
  * @param value the input [String] text to be shown in the text field
  * @param onValueChange the callback that is triggered when the input service updates the text. An
@@ -686,7 +693,7 @@
  * hit target area, use the decoration box:
  * @sample androidx.compose.foundation.samples.TextFieldWithIconSample
  *
- * Note: This overload does not support [KeyboardOptions.shouldShowKeyboardOnFocus].
+ * Note: This overload does not support [KeyboardOptions.showKeyboardOnFocus].
  *
  * @param value The [androidx.compose.ui.text.input.TextFieldValue] to be shown in the
  * [BasicTextField].
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
index b7938b5..07cae9a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreTextField.kt
@@ -27,6 +27,8 @@
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.relocation.BringIntoViewRequester
 import androidx.compose.foundation.relocation.bringIntoViewRequester
+import androidx.compose.foundation.text.handwriting.detectStylusHandwriting
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
 import androidx.compose.foundation.text.input.internal.createLegacyPlatformTextInputServiceAdapter
 import androidx.compose.foundation.text.input.internal.legacyTextInputAdapter
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
@@ -401,6 +403,29 @@
             textDragObserver = manager.touchSelectionObserver,
         )
         .pointerHoverIcon(textPointerIcon)
+        .then(
+            if (isStylusHandwritingSupported) {
+                Modifier.pointerInput(enabled, readOnly) {
+                    if (enabled && !readOnly) {
+                        detectStylusHandwriting {
+                            if (!state.hasFocus) {
+                                focusRequester.requestFocus()
+                            }
+                            // TextInputService is calling LegacyTextInputServiceAdapter under the
+                            // hood.  And because it's a public API, startStylusHandwriting is added
+                            // to legacyTextInputServiceAdapter instead.
+                            // startStylusHandwriting may be called before the actual input
+                            // session starts when the editor is not focused, this is handled
+                            // internally by the LegacyTextInputServiceAdapter.
+                            legacyTextInputServiceAdapter.startStylusHandwriting()
+                            true
+                        }
+                    }
+                }
+            } else {
+                Modifier
+            }
+        )
 
     val drawModifier = Modifier.drawBehind {
         state.layoutResult?.let { layoutResult ->
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
index 6e68dc8..34e9e5f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/KeyboardOptions.kt
@@ -45,8 +45,9 @@
  * When [ImeOptions.singleLine] is false, the keyboard might show return key rather than the action
  * requested here.
  * @param platformImeOptions defines the platform specific IME options.
- * @param shouldShowKeyboardOnFocus when true, software keyboard will show on focus gain. When
- * false, the user must interact (e.g. tap) before the keyboard is shown.
+ * @param showKeyboardOnFocus when true, software keyboard will show on focus gain. When
+ * false, the user must interact (e.g. tap) before the keyboard is shown. A null value (the default
+ * parameter value) means the keyboard will be shown on focus.
  * @param hintLocales List of the languages that the user is supposed to switch to no matter what
  * input method subtype is currently used. This special "hint" can be used mainly for, but not
  * limited to, multilingual users who want IMEs to switch language based on editor's context.
@@ -59,7 +60,9 @@
     val keyboardType: KeyboardType = KeyboardType.Text,
     val imeAction: ImeAction = ImeAction.Default,
     val platformImeOptions: PlatformImeOptions? = null,
-    val shouldShowKeyboardOnFocus: Boolean = true,
+    @Suppress("AutoBoxing")
+    @get:Suppress("AutoBoxing")
+    val showKeyboardOnFocus: Boolean? = null,
     @get:Suppress("NullableCollection")
     val hintLocales: LocaleList? = null
 ) {
@@ -70,6 +73,15 @@
          */
         @Stable
         val Default = KeyboardOptions()
+
+        /**
+         * Default [KeyboardOptions] for [BasicSecureTextField].
+         */
+        @Stable
+        internal val SecureTextField = KeyboardOptions(
+            autoCorrect = false,
+            keyboardType = KeyboardType.Password
+        )
     }
 
     @Deprecated(
@@ -102,7 +114,7 @@
         keyboardType,
         imeAction,
         platformImeOptions,
-        shouldShowKeyboardOnFocus = true
+        showKeyboardOnFocus = Default.showKeyboardOnFocusOrDefault
     )
 
     @Deprecated("Maintained for binary compat", level = DeprecationLevel.HIDDEN)
@@ -112,17 +124,20 @@
         keyboardType: KeyboardType = KeyboardType.Text,
         imeAction: ImeAction = ImeAction.Default,
         platformImeOptions: PlatformImeOptions? = null,
-        shouldShowKeyboardOnFocus: Boolean = true
+        @Suppress("AutoBoxing")
+        showKeyboardOnFocus: Boolean? = null
     ) : this(
         capitalization,
         autoCorrect,
         keyboardType,
         imeAction,
         platformImeOptions,
-        shouldShowKeyboardOnFocus,
+        showKeyboardOnFocus,
         hintLocales = null
     )
 
+    internal val showKeyboardOnFocusOrDefault get() = showKeyboardOnFocus ?: true
+
     /**
      * Returns a new [ImeOptions] with the values that are in this [KeyboardOptions] and provided
      * params.
@@ -139,13 +154,20 @@
         hintLocales = hintLocales
     )
 
+    /**
+     * Returns a copy of this object with the values passed to this method.
+     *
+     * Note that if an unspecified (null) value is passed explicitly to this method, it will replace
+     * any actually-specified value.
+     */
     fun copy(
         capitalization: KeyboardCapitalization = this.capitalization,
         autoCorrect: Boolean = this.autoCorrect,
         keyboardType: KeyboardType = this.keyboardType,
         imeAction: ImeAction = this.imeAction,
         platformImeOptions: PlatformImeOptions? = this.platformImeOptions,
-        showKeyboardOnFocus: Boolean = this.shouldShowKeyboardOnFocus,
+        @Suppress("AutoBoxing")
+        showKeyboardOnFocus: Boolean? = this.showKeyboardOnFocus,
         hintLocales: LocaleList? = this.hintLocales
     ): KeyboardOptions {
         return KeyboardOptions(
@@ -154,7 +176,7 @@
             keyboardType = keyboardType,
             imeAction = imeAction,
             platformImeOptions = platformImeOptions,
-            shouldShowKeyboardOnFocus = showKeyboardOnFocus,
+            showKeyboardOnFocus = showKeyboardOnFocus,
             hintLocales = hintLocales
         )
     }
@@ -169,7 +191,8 @@
         keyboardType: KeyboardType = this.keyboardType,
         imeAction: ImeAction = this.imeAction,
         platformImeOptions: PlatformImeOptions? = this.platformImeOptions,
-        shouldShowKeyboardOnFocus: Boolean = this.shouldShowKeyboardOnFocus
+        @Suppress("AutoBoxing")
+        showKeyboardOnFocus: Boolean? = this.showKeyboardOnFocus
     ): KeyboardOptions {
         return KeyboardOptions(
             capitalization = capitalization,
@@ -177,7 +200,7 @@
             keyboardType = keyboardType,
             imeAction = imeAction,
             platformImeOptions = platformImeOptions,
-            shouldShowKeyboardOnFocus = shouldShowKeyboardOnFocus,
+            showKeyboardOnFocus = showKeyboardOnFocus,
             hintLocales = this.hintLocales
             // New properties must be added here even though this is deprecated. The deprecated copy
             // constructors should still work on instances created with newer library versions.
@@ -201,7 +224,7 @@
             keyboardType = keyboardType,
             imeAction = imeAction,
             platformImeOptions = platformImeOptions,
-            shouldShowKeyboardOnFocus = this.shouldShowKeyboardOnFocus,
+            showKeyboardOnFocus = this.showKeyboardOnFocus,
             hintLocales = this.hintLocales
             // New properties must be added here even though this is deprecated. The deprecated copy
             // constructors should still work on instances created with newer library versions.
@@ -224,7 +247,7 @@
             keyboardType = keyboardType,
             imeAction = imeAction,
             platformImeOptions = this.platformImeOptions,
-            shouldShowKeyboardOnFocus = this.shouldShowKeyboardOnFocus,
+            showKeyboardOnFocus = this.showKeyboardOnFocus,
             hintLocales = this.hintLocales
             // New properties must be added here even though this is deprecated. The deprecated copy
             // constructors should still work on instances created with newer library versions.
@@ -240,7 +263,7 @@
         if (keyboardType != other.keyboardType) return false
         if (imeAction != other.imeAction) return false
         if (platformImeOptions != other.platformImeOptions) return false
-        if (shouldShowKeyboardOnFocus != other.shouldShowKeyboardOnFocus) return false
+        if (showKeyboardOnFocus != other.showKeyboardOnFocus) return false
         if (hintLocales != other.hintLocales) return false
 
         return true
@@ -252,7 +275,7 @@
         result = 31 * result + keyboardType.hashCode()
         result = 31 * result + imeAction.hashCode()
         result = 31 * result + platformImeOptions.hashCode()
-        result = 31 * result + shouldShowKeyboardOnFocus.hashCode()
+        result = 31 * result + showKeyboardOnFocus.hashCode()
         result = 31 * result + hintLocales.hashCode()
         return result
     }
@@ -261,7 +284,7 @@
         return "KeyboardOptions(capitalization=$capitalization, autoCorrect=$autoCorrect, " +
             "keyboardType=$keyboardType, imeAction=$imeAction, " +
             "platformImeOptions=$platformImeOptions, " +
-            "shouldShowKeyboardOnFocus=$shouldShowKeyboardOnFocus, " +
+            "showKeyboardOnFocus=$showKeyboardOnFocus, " +
             "hintLocales=$hintLocales)"
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
new file mode 100644
index 0000000..3d4ba3b
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.handwriting
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.util.fastFirstOrNull
+
+/**
+ * A utility function that detects stylus movements and calls the [onHandwritingSlopExceeded] when
+ * it detects that stylus movement has exceeds the handwriting slop.
+ * If [onHandwritingSlopExceeded] returns true, this method will consume the events and consider
+ * that the handwriting has successfully started. Otherwise, it'll stop monitoring the current
+ * gesture.
+ */
+internal suspend inline fun PointerInputScope.detectStylusHandwriting(
+    crossinline onHandwritingSlopExceeded: () -> Boolean
+) {
+    awaitEachGesture {
+        val firstDown =
+            awaitFirstDown(requireUnconsumed = true, pass = PointerEventPass.Initial)
+
+        val isStylus =
+            firstDown.type == PointerType.Stylus || firstDown.type == PointerType.Eraser
+        if (!isStylus) {
+            return@awaitEachGesture
+        }
+        // Await the touch slop before long press timeout.
+        var exceedsTouchSlop: PointerInputChange? = null
+        // The stylus move must exceeds touch slop before long press timeout.
+        while (true) {
+            val pointerEvent = awaitPointerEvent(pass = PointerEventPass.Main)
+            // The tracked pointer is consumed or lifted, stop tracking.
+            val change = pointerEvent.changes.fastFirstOrNull {
+                !it.isConsumed && it.id == firstDown.id && it.pressed
+            }
+            if (change == null) {
+                break
+            }
+
+            val time = change.uptimeMillis - firstDown.uptimeMillis
+            if (time >= viewConfiguration.longPressTimeoutMillis) {
+                break
+            }
+
+            val offset = change.position - firstDown.position
+            if (offset.getDistance() > viewConfiguration.handwritingSlop) {
+                exceedsTouchSlop = change
+                break
+            }
+        }
+
+        if (exceedsTouchSlop == null || !onHandwritingSlopExceeded.invoke()) {
+            return@awaitEachGesture
+        }
+        exceedsTouchSlop.consume()
+
+        // Consume the remaining changes of this pointer.
+        while (true) {
+            val pointerEvent = awaitPointerEvent(pass = PointerEventPass.Initial)
+            val pointerChange = pointerEvent.changes.fastFirstOrNull {
+                !it.isConsumed && it.id == firstDown.id && it.pressed
+            } ?: return@awaitEachGesture
+            pointerChange.consume()
+        }
+    }
+}
+
+/**
+ *  Whether the platform supports the stylus handwriting or not. This is for platform level support
+ *  and NOT for checking whether the IME supports handwriting.
+ */
+internal expect val isStylusHandwritingSupported: Boolean
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt
index 5b32a59..14daca4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldCharSequence.kt
@@ -77,6 +77,16 @@
 ): TextFieldCharSequence = TextFieldCharSequenceWrapper(text, selection, composition)
 
 /**
+ * Returns the backing CharSequence object that this TextFieldCharSequence is wrapping. This is
+ * useful for external equality comparisons that cannot use [TextFieldCharSequence.contentEquals].
+ */
+internal fun TextFieldCharSequence.getBackingCharSequence(): CharSequence {
+    return when (this) {
+        is TextFieldCharSequenceWrapper -> this.text
+    }
+}
+
+/**
  * Copies the contents of this sequence from [[sourceStartIndex], [sourceEndIndex]) into
  * [destination] starting at [destinationOffset].
  */
@@ -94,11 +104,22 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 private class TextFieldCharSequenceWrapper(
-    private val text: CharSequence,
+    text: CharSequence,
     selection: TextRange,
     composition: TextRange?
 ) : TextFieldCharSequence {
 
+    /**
+     * If this TextFieldCharSequence is actually a copy of another, make sure to use the backing
+     * CharSequence object to stop unnecessary nesting and logic that depends on exact equality of
+     * CharSequence comparison that's using [CharSequence.equals].
+     */
+    val text: CharSequence = if (text is TextFieldCharSequenceWrapper) {
+        text.text
+    } else {
+        text
+    }
+
     override val length: Int
         get() = text.length
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
index f63d0e7b..2e879b1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/TextFieldState.kt
@@ -35,7 +35,6 @@
 import androidx.compose.ui.text.coerceIn
 import androidx.compose.ui.text.input.TextFieldValue
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
 
 internal fun TextFieldState(initialValue: TextFieldValue): TextFieldState {
     return TextFieldState(
@@ -49,8 +48,11 @@
  * cursor or selection.
  *
  * To change the text field contents programmatically, call [edit], [setTextAndSelectAll],
- * [setTextAndPlaceCursorAtEnd], or [clearText]. To observe the value of the field over time, call
- * [forEachTextValue] or [textAsFlow].
+ * [setTextAndPlaceCursorAtEnd], or [clearText]. Individual parts of the state like [text],
+ * [selection], or [composition] can be read from any snapshot restart scope like Composable
+ * functions. To observe these members from outside a restart scope, use
+ * `snapshotFlow { textFieldState.text }` or `snapshotFlow { textFieldState.selection }`. To
+ * observe the entirety of state including text, selection, and composition, call [valueAsFlow].
  *
  * When instantiating this class from a composable, use [rememberTextFieldState] to automatically
  * save and restore the field state. For more advanced use cases, pass [TextFieldState.Saver] to
@@ -86,46 +88,102 @@
     )
 
     /**
-     * The current text and selection. This value will automatically update when the user enters
-     * text or otherwise changes the text field contents. To change it programmatically, call
-     * [edit].
+     * [TextFieldState] does not synchronize calls to [edit] but requires main thread access. It
+     * also has no way to disallow reentrant behavior (nested calls to [edit]) through the API.
+     * Instead we keep track of whether an edit session is currently running. If [edit] is called
+     * concurrently or reentered, it should throw an exception. The only exception is if
+     * [TextFieldState] is being modified in two different snapshots. Hence, this value is backed
+     * by a snapshot state.
+     */
+    private var isEditing: Boolean by mutableStateOf(false)
+
+    /**
+     * The current text, selection, and composing region. This value will automatically update when
+     * the user enters text or otherwise changes the text field contents. To change it
+     * programmatically, call [edit].
      *
      * This is backed by snapshot state, so reading this property in a restartable function (e.g.
      * a composable function) will cause the function to restart when the text field's value
      * changes.
      *
-     * To observe changes to this property outside a restartable function, see [forEachTextValue]
-     * and [textAsFlow].
+     * To observe changes to this property outside a restartable function, see [valueAsFlow].
      *
      * @sample androidx.compose.foundation.samples.BasicTextFieldTextDerivedStateSample
      *
      * @see edit
-     * @see forEachTextValue
-     * @see textAsFlow
+     * @see valueAsFlow
      */
-    var text: TextFieldCharSequence by mutableStateOf(
+    internal var value: TextFieldCharSequence by mutableStateOf(
         TextFieldCharSequence(initialText, initialSelection)
     )
         private set
 
     /**
+     * The current text content. This value will automatically update when the user enters text or
+     * otherwise changes the text field contents. To change it programmatically, call [edit].
+     *
+     * To observe changes to this property outside a restartable function, use
+     * `snapshotFlow { text }`.
+     *
+     * @sample androidx.compose.foundation.samples.BasicTextFieldTextValuesSample
+     *
+     * @see edit
+     * @see snapshotFlow
+     */
+    val text: CharSequence get() = value.getBackingCharSequence()
+
+    /**
+     * The current selection range. If the selection is collapsed, it represents cursor location.
+     * This value will automatically update when the user enters text or otherwise changes the text
+     * field selection range. To change it programmatically, call [edit].
+     *
+     * To observe changes to this property outside a restartable function, use
+     * `snapshotFlow { selection }`.
+     *
+     * @see edit
+     * @see snapshotFlow
+     * @see TextFieldCharSequence.selection
+     */
+    val selection: TextRange get() = value.selection
+
+    /**
+     * The current composing range dictated by the IME. If null, there is no composing region.
+     *
+     * To observe changes to this property outside a restartable function, use
+     * `snapshotFlow { composition }`.
+     *
+     * @see edit
+     * @see snapshotFlow
+     * @see TextFieldCharSequence.composition
+     */
+    val composition: TextRange? get() = value.composition
+
+    /**
      * Runs [block] with a mutable version of the current state. The block can make changes to the
      * text and cursor/selection. See the documentation on [TextFieldBuffer] for a more detailed
      * description of the available operations.
      *
+     * Make sure that you do not make concurrent calls to this function or call it again inside
+     * [block]'s scope. Doing either of these actions will result in triggering an
+     * [IllegalStateException].
+     *
      * @sample androidx.compose.foundation.samples.BasicTextFieldStateEditSample
      *
      * @see setTextAndPlaceCursorAtEnd
      * @see setTextAndSelectAll
      */
     inline fun edit(block: TextFieldBuffer.() -> Unit) {
-        val mutableValue = startEdit(text)
-        mutableValue.block()
-        commitEdit(mutableValue)
+        val mutableValue = startEdit()
+        try {
+            mutableValue.block()
+            commitEdit(mutableValue)
+        } finally {
+            finishEditing()
+        }
     }
 
     override fun toString(): String =
-        "TextFieldState(selection=${text.selection}, text=\"$text\")"
+        "TextFieldState(selection=$selection, text=\"$text\")"
 
     /**
      * Undo history controller for this TextFieldState.
@@ -141,8 +199,13 @@
 
     @Suppress("ShowingMemberInHiddenClass")
     @PublishedApi
-    internal fun startEdit(value: TextFieldCharSequence): TextFieldBuffer =
-        TextFieldBuffer(value)
+    internal fun startEdit(): TextFieldBuffer {
+        check(!isEditing) {
+            "TextFieldState does not support concurrent or nested editing."
+        }
+        isEditing = true
+        return TextFieldBuffer(value)
+    }
 
     /**
      * If the text or selection in [newValue] was actually modified, updates this state's internal
@@ -163,6 +226,12 @@
         textUndoManager.clearHistory()
     }
 
+    @Suppress("ShowingMemberInHiddenClass")
+    @PublishedApi
+    internal fun finishEditing() {
+        isEditing = false
+    }
+
     /**
      * An edit block that updates [TextFieldState] on behalf of user actions such as gestures,
      * IME commands, hardware keyboard events, clipboard actions, and more. These modifications
@@ -189,7 +258,7 @@
         undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
         block: EditingBuffer.() -> Unit
     ) {
-        val previousValue = text
+        val previousValue = value
 
         mainBuffer.changeTracker.clearChanges()
         mainBuffer.block()
@@ -220,7 +289,7 @@
      * a public API.
      */
     internal inline fun editWithNoSideEffects(block: EditingBuffer.() -> Unit) {
-        val previousValue = text
+        val previousValue = value
 
         mainBuffer.changeTracker.clearChanges()
         mainBuffer.block()
@@ -231,7 +300,7 @@
             composition = mainBuffer.composition
         )
 
-        text = afterEditValue
+        value = afterEditValue
         sendChangesToIme(
             oldValue = previousValue,
             newValue = afterEditValue,
@@ -252,24 +321,24 @@
         )
 
         if (inputTransformation == null) {
-            val oldValue = text
-            text = afterEditValue
+            val oldValue = value
+            value = afterEditValue
             sendChangesToIme(
                 oldValue = oldValue,
                 newValue = afterEditValue,
                 restartImeIfContentChanges = restartImeIfContentChanges
             )
-            recordEditForUndo(previousValue, text, mainBuffer.changeTracker, undoBehavior)
+            recordEditForUndo(previousValue, value, mainBuffer.changeTracker, undoBehavior)
             return
         }
 
-        val oldValue = text
+        val oldValue = value
 
         // if only difference is composition, don't run filter, don't send it to undo manager
         if (afterEditValue.contentEquals(oldValue) &&
             afterEditValue.selection == oldValue.selection
         ) {
-            text = afterEditValue
+            value = afterEditValue
             sendChangesToIme(
                 oldValue = oldValue,
                 newValue = afterEditValue,
@@ -278,22 +347,22 @@
             return
         }
 
-        val mutableValue = TextFieldBuffer(
+        val textFieldBuffer = TextFieldBuffer(
             initialValue = afterEditValue,
             sourceValue = oldValue,
             initialChanges = mainBuffer.changeTracker
         )
         inputTransformation.transformInput(
             originalValue = oldValue,
-            valueWithChanges = mutableValue
+            valueWithChanges = textFieldBuffer
         )
         // If neither the text nor the selection changed, we want to preserve the composition.
         // Otherwise, the IME will reset it anyway.
-        val afterFilterValue = mutableValue.toTextFieldCharSequence(
+        val afterFilterValue = textFieldBuffer.toTextFieldCharSequence(
             composition = afterEditValue.composition
         )
         if (afterFilterValue == afterEditValue) {
-            text = afterFilterValue
+            value = afterFilterValue
             sendChangesToIme(
                 oldValue = oldValue,
                 newValue = afterEditValue,
@@ -303,7 +372,7 @@
             resetStateAndNotifyIme(afterFilterValue)
         }
         // mutableValue contains all the changes from user and the filter.
-        recordEditForUndo(previousValue, text, mutableValue.changes, undoBehavior)
+        recordEditForUndo(previousValue, value, textFieldBuffer.changes, undoBehavior)
     }
 
     /**
@@ -416,15 +485,15 @@
         }
 
         val finalValue = TextFieldCharSequence(
-            if (textChanged) newValue else bufferState,
-            mainBuffer.selection,
-            mainBuffer.composition
+            text = if (textChanged) newValue else bufferState,
+            selection = mainBuffer.selection,
+            composition = mainBuffer.composition
         )
 
         // value must be set before notifyImeListeners are called. Even though we are sending the
         // previous and current values, a system callback may request the latest state e.g. IME
         // restartInput call is handled before notifyImeListeners return.
-        text = finalValue
+        value = finalValue
 
         sendChangesToIme(
             oldValue = bufferState,
@@ -460,8 +529,8 @@
         override fun SaverScope.save(value: TextFieldState): Any? {
             return listOf(
                 value.text.toString(),
-                value.text.selection.start,
-                value.text.selection.end,
+                value.selection.start,
+                value.selection.end,
                 with(TextUndoManager.Companion.Saver) {
                     save(value.textUndoManager)
                 }
@@ -485,13 +554,14 @@
 }
 
 /**
- * Returns a [Flow] of the values of [TextFieldState.text] as seen from the global snapshot.
+ * Returns a [Flow] of the values of [TextFieldState.text], [TextFieldState.selection], and
+ * [TextFieldState.composition] as seen from the global snapshot.
  * The initial value is emitted immediately when the flow is collected.
  *
  * @sample androidx.compose.foundation.samples.BasicTextFieldTextValuesSample
  */
 @ExperimentalFoundationApi
-fun TextFieldState.textAsFlow(): Flow<TextFieldCharSequence> = snapshotFlow { text }
+fun TextFieldState.valueAsFlow(): Flow<TextFieldCharSequence> = snapshotFlow { value }
 
 /**
  * Create and remember a [TextFieldState]. The state is remembered using [rememberSaveable] and so
@@ -500,7 +570,6 @@
  * If you need to store a [TextFieldState] in another object, use the [TextFieldState.Saver] object
  * to manually save and restore the state.
  */
-@ExperimentalFoundationApi
 @Composable
 fun rememberTextFieldState(
     initialText: String = "",
@@ -581,28 +650,3 @@
         placeCursorAtEnd()
     }
 }
-
-/**
- * Invokes [block] with the value of [TextFieldState.text], and every time the value is changed.
- *
- * The caller will be suspended until its coroutine is cancelled. If the text is changed while
- * [block] is suspended, [block] will be cancelled and re-executed with the new value immediately.
- * [block] will never be executed concurrently with itself.
- *
- * To get access to a [Flow] of [TextFieldState.text] over time, use [textAsFlow].
- *
- * Warning: Do not update the value of the [TextFieldState] from [block]. If you want to perform
- * either a side effect when text is changed, or filter it in some way, use an
- * [InputTransformation].
- *
- * @sample androidx.compose.foundation.samples.BasicTextFieldForEachTextValueSample
- *
- * @see textAsFlow
- */
-@ExperimentalFoundationApi
-suspend fun TextFieldState.forEachTextValue(
-    block: suspend (TextFieldCharSequence) -> Unit
-): Nothing {
-    textAsFlow().collectLatest(block)
-    error("textAsFlow expected not to complete without exception")
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
index d676e39..c4617590 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.kt
@@ -63,6 +63,8 @@
         textInputModifierNode?.softwareKeyboardController?.hide()
     }
 
+    abstract fun startStylusHandwriting()
+
     interface LegacyPlatformTextInputNode {
         val softwareKeyboardController: SoftwareKeyboardController?
         val layoutCoordinates: LayoutCoordinates?
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
index 098f3ec..1dc1363 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextFieldDecoratorModifier.kt
@@ -23,8 +23,6 @@
 import androidx.compose.foundation.content.internal.dragAndDropRequestPermission
 import androidx.compose.foundation.content.internal.getReceiveContentConfiguration
 import androidx.compose.foundation.content.readPlainText
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.interaction.HoverInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.text.BasicTextField
@@ -32,6 +30,8 @@
 import androidx.compose.foundation.text.KeyboardActionScope
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.handwriting.detectStylusHandwriting
+import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
 import androidx.compose.foundation.text.input.InputTransformation
 import androidx.compose.foundation.text.input.internal.selection.TextFieldSelectionState
 import androidx.compose.foundation.text.input.internal.selection.TextToolbarState
@@ -45,9 +45,6 @@
 import androidx.compose.ui.input.key.KeyInputModifierNode
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputChange
-import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.modifier.ModifierLocalModifierNode
@@ -65,7 +62,6 @@
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.LocalWindowInfo
 import androidx.compose.ui.platform.PlatformTextInputModifierNode
 import androidx.compose.ui.platform.PlatformTextInputSession
@@ -94,7 +90,6 @@
 import androidx.compose.ui.text.input.KeyboardCapitalization
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.util.fastFirstOrNull
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
@@ -189,15 +184,17 @@
     LayoutAwareModifierNode {
 
     private val editable get() = enabled && !readOnly
-    private var _stylusHandwritingTrigger: MutableSharedFlow<Unit>? = null
-    private val stylusHandwritingTrigger: MutableSharedFlow<Unit>
+
+    private var backingStylusHandwritingTrigger: MutableSharedFlow<Unit>? = null
+    private val stylusHandwritingTrigger: MutableSharedFlow<Unit>?
         get() {
-            val handwritingTrigger = _stylusHandwritingTrigger
-            if (handwritingTrigger != null) return handwritingTrigger
+            val finalStylusHandwritingTrigger = backingStylusHandwritingTrigger
+            if (finalStylusHandwritingTrigger != null) return finalStylusHandwritingTrigger
+            if (!isStylusHandwritingSupported) return null
             return MutableSharedFlow<Unit>(
                 replay = 1,
                 onBufferOverflow = BufferOverflow.DROP_LATEST
-            ).also { _stylusHandwritingTrigger = it }
+            ).also { backingStylusHandwritingTrigger = it }
         }
 
     private val pointerInputNode = delegate(SuspendingPointerInputModifierNode {
@@ -228,80 +225,35 @@
                     detectTextFieldLongPressAndAfterDrag(requestFocus)
                 }
             }
-            launch(start = CoroutineStart.UNDISPATCHED) {
-                detectStylusHandwriting()
+            // Note: when editable changes (enabled or readOnly changes), this pointerInputModifier
+            // is reset. And we don't need to worry about cancel or launch the stylus handwriting
+            // detecting job.
+            if (isStylusHandwritingSupported && editable) {
+                 launch(start = CoroutineStart.UNDISPATCHED) {
+                    detectStylusHandwriting {
+                        if (!isFocused) {
+                            requestFocus()
+                        }
+
+                        // Send the handwriting start signal to platform.
+                        // The editor should send the signal when it is focused or is about
+                        // to gain focus, Here are more details:
+                        //   1) if the editor already has an active input session, the
+                        //   platform handwriting service should already listen to this flow
+                        //   and it'll start handwriting right away.
+                        //
+                        //   2) if the editor is not focused, but it'll be focused and
+                        //   create a new input session, one handwriting signal will be
+                        //   replayed when the platform collect this flow. And the platform
+                        //   should trigger handwriting accordingly.
+                        stylusHandwritingTrigger?.tryEmit(Unit)
+                        return@detectStylusHandwriting true
+                    }
+                }
             }
         }
     })
 
-    private suspend fun PointerInputScope.detectStylusHandwriting() {
-        awaitEachGesture {
-            val firstDown =
-                awaitFirstDown(requireUnconsumed = true, pass = PointerEventPass.Initial)
-
-            val isStylus =
-                firstDown.type == PointerType.Stylus || firstDown.type == PointerType.Eraser
-            if (!editable || !isStylus) {
-                return@awaitEachGesture
-            }
-
-            val viewConfiguration = currentValueOf(LocalViewConfiguration)
-
-            // Await the touch slop before long press timeout.
-            var exceedsTouchSlop: PointerInputChange? = null
-            // The stylus move must exceeds touch slop before long press timeout.
-            while (true) {
-                val pointerEvent = awaitPointerEvent(pass = PointerEventPass.Main)
-                // The tracked pointer is consumed or lifted, stop tracking.
-                val change = pointerEvent.changes.fastFirstOrNull {
-                    !it.isConsumed && it.id == firstDown.id && it.pressed
-                }
-                if (change == null) {
-                    break
-                }
-
-                val time = change.uptimeMillis - firstDown.uptimeMillis
-                if (time >= viewConfiguration.longPressTimeoutMillis) {
-                    break
-                }
-
-                val offset = change.position - firstDown.position
-                if (offset.getDistance() > viewConfiguration.handwritingSlop) {
-                    exceedsTouchSlop = change
-                    break
-                }
-            }
-
-            if (exceedsTouchSlop == null) return@awaitEachGesture
-
-            exceedsTouchSlop.consume()
-
-            if (!isFocused) {
-                requestFocus()
-            }
-
-            // Send the handwriting start signal to platform.
-            // The editor should send the signal when it is focused or is about to gain focused,
-            // Here are more details:
-            //   1) if the editor already has an active input session, the platform handwriting
-            //   service should already listen to this flow and it'll start handwriting right away.
-            //
-            //   2) if the editor is not focused, but it'll be focused and create a new input
-            //   session, one handwriting signal will be replayed when the platform collect this
-            //   flow. And the platform should trigger handwriting accordingly.
-            stylusHandwritingTrigger.tryEmit(Unit)
-
-            // Consume the remaining changes of this pointer.
-            while (true) {
-                val pointerEvent = awaitPointerEvent(pass = PointerEventPass.Initial)
-                val pointerChange = pointerEvent.changes.fastFirstOrNull {
-                    !it.isConsumed && it.id == firstDown.id && it.pressed
-                } ?: return@awaitEachGesture
-                pointerChange.consume()
-            }
-        }
-    }
-
     /**
      * The last enter event that was submitted to [interactionSource] from [dragAndDropNode]. We
      * need to keep a reference to this event to send a follow-up exit event.
@@ -323,8 +275,8 @@
         textFieldDragAndDropNode(
             hintMediaTypes = {
                 val receiveContentConfiguration = getReceiveContentConfiguration()
-                // if receiveContent configuration is set, all drag operations should be
-                // accepted. ReceiveContent handler should evaluate the incoming content.
+                // if ReceiveContentConfiguration is set, all drag events should be accepted.
+                // ContentReceiver handler should evaluate the incoming content.
                 if (receiveContentConfiguration != null) {
                     MediaTypesAll
                 } else {
@@ -684,9 +636,11 @@
         // because the resize happens after the text state change, and the resize moves the cursor
         // under the keyboard. This also covers the case where the field shrinks while focused.
         val selection = textFieldState.visualText.selection
+        val layoutResult = textLayoutState.layoutResult ?: return
         if (selection.collapsed) {
             coroutineScope.launch {
-                textLayoutState.bringCursorIntoView(cursorIndex = selection.start)
+                val rect = layoutResult.getCursorRect(selection.start)
+                textLayoutState.bringIntoViewRequester.bringIntoView(rect)
             }
         }
     }
@@ -734,7 +688,7 @@
     }
 
     private fun startInputSession(fromTap: Boolean) {
-        if (!fromTap && !keyboardOptions.shouldShowKeyboardOnFocus) return
+        if (!fromTap && !keyboardOptions.showKeyboardOnFocusOrDefault) return
 
         val receiveContentConfiguration = getReceiveContentConfiguration()
 
@@ -763,7 +717,7 @@
     private fun disposeInputSession() {
         inputSessionJob?.cancel()
         inputSessionJob = null
-        stylusHandwritingTrigger.resetReplayCache()
+        stylusHandwritingTrigger?.resetReplayCache()
     }
 
     private fun startInputSessionOnWindowFocusChange() {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt
index 2ff9d9b..feace9f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TextLayoutState.kt
@@ -250,14 +250,3 @@
         }
     } ?: offset
 }
-
-/**
- * Asks [TextLayoutState.bringIntoViewRequester] to bring the bounds of the cursor at [cursorIndex]
- * into view.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal suspend fun TextLayoutState.bringCursorIntoView(cursorIndex: Int) {
-    val layoutResult = layoutResult ?: return
-    val rect = layoutResult.getCursorRect(cursorIndex)
-    bringIntoViewRequester.bringIntoView(rect)
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
index c5b1415..990e981 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/TransformedTextFieldState.kt
@@ -116,7 +116,7 @@
             derivedStateOf {
                 // text is a state read. transformation may also perform state reads when ran.
                 calculateTransformedText(
-                    untransformedText = textFieldState.text,
+                    untransformedValue = textFieldState.value,
                     outputTransformation = transformation,
                     wedgeAffinity = selectionWedgeAffinity
                 )
@@ -129,7 +129,7 @@
                 calculateTransformedText(
                     // These are state reads. codepointTransformation may also perform state reads
                     // when ran.
-                    untransformedText = outputTransformedText?.value?.text ?: textFieldState.text,
+                    untransformedValue = outputTransformedText?.value?.text ?: textFieldState.value,
                     codepointTransformation = transformation,
                     wedgeAffinity = selectionWedgeAffinity
                 )
@@ -141,7 +141,7 @@
      * [CodepointTransformation] applied.
      */
     val untransformedText: TextFieldCharSequence
-        get() = textFieldState.text
+        get() = textFieldState.value
 
     /**
      * The text that should be presented to the user in most cases. If an [OutputTransformation] is
@@ -433,22 +433,22 @@
 
         /**
          * Applies an [OutputTransformation] to a [TextFieldCharSequence], returning the
-         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
+         * transformed text content, the selection/cursor from the [untransformedValue] mapped to the
          * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
          * offsets in both directions between the transformed and untransformed text.
          *
-         * This function is relatively expensive, since it creates a copy of [untransformedText], so
+         * This function is relatively expensive, since it creates a copy of [untransformedValue], so
          * its result should be cached.
          */
         @kotlin.jvm.JvmStatic
         private fun calculateTransformedText(
-            untransformedText: TextFieldCharSequence,
+            untransformedValue: TextFieldCharSequence,
             outputTransformation: OutputTransformation,
             wedgeAffinity: SelectionWedgeAffinity
         ): TransformedText? {
             val offsetMappingCalculator = OffsetMappingCalculator()
             val buffer = TextFieldBuffer(
-                initialValue = untransformedText,
+                initialValue = untransformedValue,
                 offsetMappingCalculator = offsetMappingCalculator
             )
 
@@ -464,11 +464,11 @@
                 // Pass the calculator explicitly since the one on transformedText won't be updated
                 // yet.
                 selection = mapToTransformed(
-                    range = untransformedText.selection,
+                    range = untransformedValue.selection,
                     mapping = offsetMappingCalculator,
                     wedgeAffinity = wedgeAffinity
                 ),
-                composition = untransformedText.composition?.let {
+                composition = untransformedValue.composition?.let {
                     mapToTransformed(
                         range = it,
                         mapping = offsetMappingCalculator,
@@ -481,16 +481,16 @@
 
         /**
          * Applies a [CodepointTransformation] to a [TextFieldCharSequence], returning the
-         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
+         * transformed text content, the selection/cursor from the [untransformedValue] mapped to the
          * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
          * offsets in both directions between the transformed and untransformed text.
          *
-         * This function is relatively expensive, since it creates a copy of [untransformedText], so
+         * This function is relatively expensive, since it creates a copy of [untransformedValue], so
          * its result should be cached.
          */
         @kotlin.jvm.JvmStatic
         private fun calculateTransformedText(
-            untransformedText: TextFieldCharSequence,
+            untransformedValue: TextFieldCharSequence,
             codepointTransformation: CodepointTransformation,
             wedgeAffinity: SelectionWedgeAffinity
         ): TransformedText? {
@@ -498,10 +498,10 @@
 
             // This is the call to external code. Returns same instance if no codepoints change.
             val transformedText =
-                untransformedText.toVisualText(codepointTransformation, offsetMappingCalculator)
+                untransformedValue.toVisualText(codepointTransformation, offsetMappingCalculator)
 
             // Avoid allocations + mapping if there weren't actually any transformations.
-            if (transformedText === untransformedText) {
+            if (transformedText === untransformedValue) {
                 return null
             }
 
@@ -510,11 +510,11 @@
                 // Pass the calculator explicitly since the one on transformedText won't be updated
                 // yet.
                 selection = mapToTransformed(
-                    untransformedText.selection,
+                    untransformedValue.selection,
                     offsetMappingCalculator,
                     wedgeAffinity
                 ),
-                composition = untransformedText.composition?.let {
+                composition = untransformedValue.composition?.let {
                     mapToTransformed(it, offsetMappingCalculator, wedgeAffinity)
                 }
             )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt
index 99014f8..5d422b9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt
@@ -1157,8 +1157,8 @@
         val receiveContentConfiguration = receiveContentConfiguration?.invoke()
             ?: return pasteAsPlainText()
 
-        val clipEntry = clipboardManager?.getClip() ?: return
-        val clipMetadata = clipboardManager?.getClipMetadata() ?: return pasteAsPlainText()
+        val clipEntry = clipboardManager?.getClip() ?: return pasteAsPlainText()
+        val clipMetadata = clipEntry.getMetadata()
 
         val remaining = receiveContentConfiguration.receiveContentListener.onReceive(
             TransferableContent(
@@ -1208,7 +1208,7 @@
 
         // if receive content is configured, hasClip should be enough to show the paste option
         val canPasteContent = receiveContentConfiguration?.invoke() != null &&
-            clipboardManager?.hasClip() == true
+            clipboardManager?.getClip() != null
         // if receive content is not configured, we expect at least a text item to be present
         val canPasteText = clipboardManager?.hasText() == true
         val canPaste = editable && (canPasteContent || canPasteText)
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.desktop.kt
similarity index 82%
rename from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
rename to compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.desktop.kt
index 3e91597..5667100 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.desktop.kt
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.compose.foundation.text.handwriting
+
+internal actual val isStylusHandwritingSupported: Boolean = false
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.desktop.kt
index 5f7b203..079e229 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.desktop.kt
@@ -95,6 +95,10 @@
             input.focusedRect = rect
         }
     }
+
+    override fun startStylusHandwriting() {
+        // Noop for desktop
+    }
 }
 
 internal class LegacyTextInputMethodRequest(
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/PreferenceAsState.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/PreferenceAsState.kt
index 12b0a9d..417e47f 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/PreferenceAsState.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/PreferenceAsState.kt
@@ -25,9 +25,9 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.preference.PreferenceManager
 
 /**
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/SoftInputModeSetting.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/SoftInputModeSetting.kt
index da20cd1..ca9dffc 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/SoftInputModeSetting.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/settings/SoftInputModeSetting.kt
@@ -27,8 +27,8 @@
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.runtime.withFrameMillis
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle.State.RESUMED
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.preference.DropDownPreference
 import androidx.preference.Preference.SummaryProvider
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
index 4d978e4..b79175d 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/SmallListStartupBenchmark.kt
@@ -17,9 +17,7 @@
 package androidx.compose.integration.macrobenchmark
 
 import androidx.benchmark.macro.CompilationMode
-import androidx.benchmark.macro.ExperimentalMetricApi
 import androidx.benchmark.macro.StartupMode
-import androidx.benchmark.macro.TraceSectionMetric
 import androidx.benchmark.macro.junit4.MacrobenchmarkRule
 import androidx.test.filters.LargeTest
 import androidx.testutils.createStartupCompilationParams
@@ -30,7 +28,6 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
-@OptIn(ExperimentalMetricApi::class)
 @LargeTest
 @RunWith(Parameterized::class)
 class SmallListStartupBenchmark(
@@ -45,14 +42,7 @@
      *
      * Note that this tracing only exists on more recent API levels
      */
-    private val metrics = getStartupMetrics() + if (startupMode == StartupMode.COLD) {
-        listOf(
-            TraceSectionMetric("cache_hit", "shaderCacheHit"),
-            TraceSectionMetric("cache_miss", "shaderCacheMiss")
-        )
-    } else {
-        emptyList()
-    }
+    private val metrics = getStartupMetrics()
 
     @Test
     fun startup() = benchmarkRule.measureStartup(
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialPerfettoSdkBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialPerfettoSdkBenchmark.kt
index 9a53ef5..06ee17a 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialPerfettoSdkBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialPerfettoSdkBenchmark.kt
@@ -52,7 +52,7 @@
         val metrics = listOf(
             TraceSectionMetric(
                 "%$PACKAGE_NAME.$composableName %$FILE_NAME:%",
-                mode = TraceSectionMetric.Mode.First
+                TraceSectionMetric.Mode.First
             )
         )
         benchmarkRule.measureRepeated(
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialStartupPerfettoSdkBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialStartupPerfettoSdkBenchmark.kt
index 8dd236a..47a7b64f 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialStartupPerfettoSdkBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/TrivialStartupPerfettoSdkBenchmark.kt
@@ -53,7 +53,7 @@
             val perfettoSdkTraceSection = TraceSectionMetric(
                 "%TrivialStartupTracingActivity.onCreate%" +
                     " (TrivialStartupTracingActivity.kt:%)",
-                    mode = TraceSectionMetric.Mode.First
+                    TraceSectionMetric.Mode.First
             )
             benchmarkRule.measureStartup(
                 compilationMode = compilationMode,
diff --git a/compose/material/material-icons-core/build.gradle b/compose/material/material-icons-core/build.gradle
index d51d9d4..8e2366a 100644
--- a/compose/material/material-icons-core/build.gradle
+++ b/compose/material/material-icons-core/build.gradle
@@ -111,6 +111,7 @@
 androidx {
     name = "Compose Material Icons Core"
     type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2020"
     description = "Compose Material Design core icons. This module contains the most commonly used set of Material icons."
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/material/material-icons-core/samples/build.gradle b/compose/material/material-icons-core/samples/build.gradle
index 2d7f7f1..c2120d47 100644
--- a/compose/material/material-icons-core/samples/build.gradle
+++ b/compose/material/material-icons-core/samples/build.gradle
@@ -44,6 +44,7 @@
 androidx {
     name = "Compose UI Core Material Icons Samples"
     type = LibraryType.SAMPLES
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Core Material Icons"
 }
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index e13629d..568f7f0 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -125,6 +125,7 @@
 androidx {
     name = "Compose Material Icons Extended"
     type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+    mavenVersion = LibraryVersions.COMPOSE
     // This module has a large number (5000+) of generated source files and so doc generation /
     // API tracking will simply take too long
     runApiTasks = new RunApiTasks.No("Five thousand generated source files")
diff --git a/compose/material/material-navigation/build.gradle b/compose/material/material-navigation/build.gradle
index b32975c..179c44c 100644
--- a/compose/material/material-navigation/build.gradle
+++ b/compose/material/material-navigation/build.gradle
@@ -42,6 +42,7 @@
 androidx {
     name = "Compose Material Navigation"
     publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2024"
     description = "Compose Material integration with Navigation"
     samples(projectOrArtifact(":compose:material:material-navigation-samples"))
diff --git a/compose/material/material-navigation/samples/build.gradle b/compose/material/material-navigation/samples/build.gradle
index 7e3acd6..b0a9deb 100644
--- a/compose/material/material-navigation/samples/build.gradle
+++ b/compose/material/material-navigation/samples/build.gradle
@@ -42,6 +42,7 @@
 androidx {
     name = "Compose Material Navigation Integration Samples"
     type = LibraryType.SAMPLES
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2024"
     description = "Samples for Compose Material integration with Navigation"
 }
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index e06e787..7cbf897 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -69,7 +69,6 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
-                implementation(project(":compose:ui:ui-graphics"))
             }
         }
 
@@ -119,6 +118,7 @@
 androidx {
     name = "Compose Material Ripple"
     type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2020"
     description = "Material ripple used to build interactive components"
     // Disable strict API mode for MPP builds as it will fail to compile androidAndroidTest
diff --git a/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleModifierNodeTest.kt b/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleModifierNodeTest.kt
index a108a27..cb70f48 100644
--- a/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleModifierNodeTest.kt
+++ b/compose/material/material-ripple/src/androidInstrumentedTest/kotlin/androidx/compose/material/ripple/RippleModifierNodeTest.kt
@@ -50,6 +50,8 @@
 import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.captureToImage
@@ -62,6 +64,8 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
@@ -153,6 +157,120 @@
         )
     }
 
+    /**
+     * Regression test for b/329693006
+     */
+    @Test
+    fun pressed_rippleCreatedBeforeDraw() {
+        // Add a static press interaction so that when the ripple is added, it will add a ripple
+        // immediately before the node is drawn
+        val interactionSource = object : MutableInteractionSource {
+            override val interactions: Flow<Interaction> =
+                flowOf(PressInteraction.Press(Offset.Zero))
+            override suspend fun emit(interaction: Interaction) {}
+            override fun tryEmit(interaction: Interaction): Boolean { return true }
+        }
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+                RippleBoxWithBackground(
+                    interactionSource,
+                    TestRipple,
+                    bounded = true
+                )
+            }
+        }
+
+        val expectedColor = calculateResultingRippleColor(
+            TestRippleColor,
+            rippleOpacity = TestRippleAlpha.pressedAlpha
+        )
+
+        assertRippleMatches(
+            scope!!,
+            interactionSource,
+            // Unused
+            PressInteraction.Press(Offset(10f, 10f)),
+            expectedColor
+        )
+    }
+
+    /**
+     * Regression test for b/329693006, similar to [pressed_rippleCreatedBeforeDraw], but delegating
+     * to the ripple node later in time to simulate clickable behavior.
+     */
+    @Test
+    fun pressed_rippleLazilyDelegatedTo() {
+        // Add a static press interaction so that when the ripple is added, it will add a ripple
+        // immediately before the node is drawn
+        val interactionSource = object : MutableInteractionSource {
+            override val interactions: Flow<Interaction> =
+                flowOf(PressInteraction.Press(Offset.Zero))
+            override suspend fun emit(interaction: Interaction) {}
+            override fun tryEmit(interaction: Interaction): Boolean { return true }
+        }
+
+        class TestRippleNode : DelegatingNode() {
+            fun attachRipple() {
+                delegate(TestRipple.create(interactionSource))
+            }
+        }
+
+        val node = TestRippleNode()
+
+        val element = object : ModifierNodeElement<TestRippleNode>() {
+            override fun create(): TestRippleNode = node
+            override fun update(node: TestRippleNode) {}
+            override fun equals(other: Any?): Boolean = other === this
+            override fun hashCode(): Int = -1
+        }
+
+        var scope: CoroutineScope? = null
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+                Box(Modifier.semantics(mergeDescendants = true) {}.testTag(Tag)) {
+                    Box(
+                        Modifier.padding(25.dp).background(RippleBoxBackgroundColor)
+                    ) {
+                        val shape = RoundedCornerShape(20)
+                        val clip = Modifier.clip(shape)
+                        Box(
+                            Modifier.padding(25.dp).width(40.dp).height(40.dp)
+                                .border(BorderStroke(2.dp, Color.Black), shape)
+                                .background(color = RippleBoxBackgroundColor, shape = shape)
+                                .then(clip)
+                                .then(element)
+                        ) {}
+                    }
+                }
+            }
+        }
+
+        val expectedColor = calculateResultingRippleColor(
+            TestRippleColor,
+            rippleOpacity = TestRippleAlpha.pressedAlpha
+        )
+
+        // Add the ripple node to the hierarchy, which should then create a ripple before the node
+        // has been drawn
+        rule.runOnIdle {
+            node.attachRipple()
+        }
+
+        assertRippleMatches(
+            scope!!,
+            interactionSource,
+            // Unused
+            PressInteraction.Press(Offset(10f, 10f)),
+            expectedColor
+        )
+    }
+
     @Test
     fun hovered() {
         val interactionSource = MutableInteractionSource()
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/Ripple.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/Ripple.android.kt
index 6eaccdb..766e2de5 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/Ripple.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/Ripple.android.kt
@@ -126,18 +126,7 @@
             invalidateDraw()
         }
 
-    /**
-     * Cache the size of the canvas we will draw the ripple into - this is updated each time
-     * [draw] is called. This is needed as before we start animating the ripple, we
-     * need to know its size (changing the bounds mid-animation will cause us to continue the
-     * animation on the UI thread, not the render thread), but the size is only known inside the
-     * draw scope.
-     */
-    private var rippleSize: Size = Size.Zero
-
     override fun DrawScope.drawRipples() {
-        rippleSize = size
-
         drawIntoCanvas { canvas ->
             rippleHostView?.run {
                 // We set these inside addRipple() already, but they may change during the ripple
@@ -146,9 +135,14 @@
                 // currently drawn ripples if the ripples are being drawn on the RenderThread,
                 // since only the software paint is updated, not the hardware paint used in
                 // RippleForeground.
-                updateRippleProperties(
-                    size = size,
-                    radius = targetRadius.roundToInt(),
+                // Radius updates will not take effect until the next ripple, so if the size changes
+                // the only way to update the calculated radius is by using
+                // RippleDrawable.RADIUS_AUTO to calculate the radius from the bounds automatically.
+                // But in this case, if the bounds change, the animation will switch to the UI
+                // thread instead of render thread, so this isn't clearly desired either.
+                // b/183019123
+                setRippleProperties(
+                    size = rippleSize,
                     color = rippleColor,
                     alpha = rippleAlpha().pressedAlpha
                 )
@@ -158,13 +152,13 @@
         }
     }
 
-    override fun addRipple(interaction: PressInteraction.Press) {
+    override fun addRipple(interaction: PressInteraction.Press, size: Size, targetRadius: Float) {
         rippleHostView = with(getOrCreateRippleContainer()) {
             getRippleHostView().apply {
                 addRipple(
                     interaction = interaction,
                     bounded = bounded,
-                    size = rippleSize,
+                    size = size,
                     radius = targetRadius.roundToInt(),
                     color = rippleColor,
                     alpha = rippleAlpha().pressedAlpha,
@@ -280,9 +274,8 @@
                 // currently drawn ripples if the ripples are being drawn on the RenderThread,
                 // since only the software paint is updated, not the hardware paint used in
                 // RippleForeground.
-                updateRippleProperties(
+                setRippleProperties(
                     size = size,
-                    radius = rippleRadius,
                     color = color,
                     alpha = alpha
                 )
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
index 7bd6362..30c28cb 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleContainer.android.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.view.ViewGroup
-import androidx.compose.ui.graphics.R
+import androidx.compose.ui.R
 
 internal interface RippleHostKey {
     /**
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt
index fa4fa68..aa462e2 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt
@@ -135,7 +135,8 @@
         }
         val ripple = ripple!!
         this.onInvalidateRipple = onInvalidateRipple
-        updateRippleProperties(size, radius, color, alpha)
+        ripple.trySetRadius(radius)
+        setRippleProperties(size, color, alpha)
         if (bounded) {
             // Bounded ripples should animate from the press position
             ripple.setHotspot(interaction.pressPosition.x, interaction.pressPosition.y)
@@ -161,13 +162,10 @@
     }
 
     /**
-     * Update the underlying [RippleDrawable] with the new properties. Note that changes to
-     * [size] or [radius] while a ripple is animating will cause the animation to move to the UI
-     * thread, so it is important to also provide the correct values in [addRipple].
+     * Update the underlying [RippleDrawable] with the new properties.
      */
-    fun updateRippleProperties(
+    fun setRippleProperties(
         size: Size,
-        radius: Int,
         color: Color,
         alpha: Float
     ) {
@@ -176,7 +174,6 @@
         // (either here or internally in RippleDrawable). Many properties invalidate the ripple when
         // changed, which will lead to a call to updateRippleProperties again, which will cause
         // another invalidation, etc.
-        ripple.trySetRadius(radius)
         ripple.setColor(color, alpha)
         val newBounds = Rect(
             0,
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/CommonRipple.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/CommonRipple.kt
index 112c2be..7dfe12a 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/CommonRipple.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/CommonRipple.kt
@@ -25,6 +25,7 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.mutableStateMapOf
 import androidx.compose.runtime.remember
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorProducer
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -72,7 +73,7 @@
 ) : RippleNode(interactionSource, bounded, radius, color, rippleAlpha) {
     private val ripples = MutableScatterMap<PressInteraction.Press, RippleAnimation>()
 
-    override fun addRipple(interaction: PressInteraction.Press) {
+    override fun addRipple(interaction: PressInteraction.Press, size: Size, targetRadius: Float) {
         // Finish existing ripples
         ripples.forEach { _, ripple -> ripple.finish() }
         val origin = if (bounded) interaction.pressPosition else null
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
index 483a851..d747be5f 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.material.ripple
 
+import androidx.collection.mutableObjectListOf
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.LinearEasing
@@ -34,6 +35,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorProducer
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -44,9 +46,13 @@
 import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.invalidateDraw
+import androidx.compose.ui.node.requireDensity
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.isUnspecified
+import androidx.compose.ui.unit.toSize
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
@@ -320,40 +326,80 @@
     private val radius: Dp,
     private val color: ColorProducer,
     protected val rippleAlpha: () -> RippleAlpha
-) : Modifier.Node(), CompositionLocalConsumerModifierNode, DrawModifierNode {
+) : Modifier.Node(),
+    CompositionLocalConsumerModifierNode,
+    DrawModifierNode,
+    LayoutAwareModifierNode {
     final override val shouldAutoInvalidate: Boolean = false
 
     private var stateLayer: StateLayer? = null
 
-    // Calculated inside draw(). This won't happen in Robolectric, so default to 0f to avoid crashes
-    var targetRadius: Float = 0f
+    // The following are calculated inside onRemeasured(). These must be initialized before adding
+    // a ripple.
+
+    // Target radius updating over time for existing ripples isn't supported for Android, and
+    // isn't implemented in common, so for now it can be private.
+    private var targetRadius: Float = 0f
+    // The size is needed for Android to update ripple bounds if the size changes
+    protected var rippleSize: Size = Size.Zero
         private set
 
     val rippleColor: Color
         get() = color()
 
-    final override fun onAttach() {
+    // Track interactions that were emitted before we have been placed - we need to wait until we
+    // have a valid size in order to set the radius and size correctly.
+    private var hasValidSize = false
+    private val pendingInteractions = mutableObjectListOf<PressInteraction>()
+
+    override fun onRemeasured(size: IntSize) {
+        hasValidSize = true
+        val density = requireDensity()
+        rippleSize = size.toSize()
+        targetRadius = with(density) {
+            if (radius.isUnspecified) {
+                // Explicitly calculate the radius instead of using RippleDrawable.RADIUS_AUTO on
+                // Android since the latest spec does not match with the existing radius calculation
+                // in the framework.
+                getRippleEndRadius(bounded, rippleSize)
+            } else {
+                radius.toPx()
+            }
+        }
+        // Flush any pending interactions that were waiting for measurement
+        pendingInteractions.forEach {
+            handlePressInteraction(it)
+        }
+        pendingInteractions.clear()
+    }
+
+    override fun onAttach() {
         coroutineScope.launch {
             interactionSource.interactions.collect { interaction ->
                 when (interaction) {
-                    is PressInteraction.Press -> addRipple(interaction)
-                    is PressInteraction.Release -> removeRipple(interaction.press)
-                    is PressInteraction.Cancel -> removeRipple(interaction.press)
+                    is PressInteraction -> {
+                        if (hasValidSize) {
+                            handlePressInteraction(interaction)
+                        } else {
+                            // Handle these later when we have a valid size
+                            pendingInteractions += interaction
+                        }
+                    }
                     else -> updateStateLayer(interaction, this)
                 }
             }
         }
     }
 
-    override fun ContentDrawScope.draw() {
-        targetRadius = if (radius.isUnspecified) {
-            // Explicitly calculate the radius instead of using RippleDrawable.RADIUS_AUTO on
-            // Android since the latest spec does not match with the existing radius calculation in
-            // the framework.
-            getRippleEndRadius(bounded, size)
-        } else {
-            radius.toPx()
+    private fun handlePressInteraction(pressInteraction: PressInteraction) {
+        when (pressInteraction) {
+            is PressInteraction.Press -> addRipple(pressInteraction, rippleSize, targetRadius)
+            is PressInteraction.Release -> removeRipple(pressInteraction.press)
+            is PressInteraction.Cancel -> removeRipple(pressInteraction.press)
         }
+    }
+
+    override fun ContentDrawScope.draw() {
         drawContent()
         stateLayer?.run {
             drawStateLayer(targetRadius, rippleColor)
@@ -363,7 +409,7 @@
 
     abstract fun DrawScope.drawRipples()
 
-    abstract fun addRipple(interaction: PressInteraction.Press)
+    abstract fun addRipple(interaction: PressInteraction.Press, size: Size, targetRadius: Float)
     abstract fun removeRipple(interaction: PressInteraction.Press)
     private fun updateStateLayer(interaction: Interaction, scope: CoroutineScope) {
         val stateLayer = stateLayer ?: StateLayer(bounded, rippleAlpha).also { instance ->
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index def4217..6b52d49 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -374,10 +374,16 @@
   }
 
   public final class DrawerDefaults {
+    method public androidx.compose.animation.core.TweenSpec<java.lang.Float> getAnimationSpec();
+    method @androidx.compose.runtime.Composable public long getBackgroundColor();
     method public float getElevation();
     method @androidx.compose.runtime.Composable public long getScrimColor();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+    property public final androidx.compose.animation.core.TweenSpec<java.lang.Float> AnimationSpec;
     property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long backgroundColor;
     property @androidx.compose.runtime.Composable public final long scrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     field public static final androidx.compose.material.DrawerDefaults INSTANCE;
     field public static final float ScrimOpacity = 0.32f;
   }
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index def4217..6b52d49 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -374,10 +374,16 @@
   }
 
   public final class DrawerDefaults {
+    method public androidx.compose.animation.core.TweenSpec<java.lang.Float> getAnimationSpec();
+    method @androidx.compose.runtime.Composable public long getBackgroundColor();
     method public float getElevation();
     method @androidx.compose.runtime.Composable public long getScrimColor();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+    property public final androidx.compose.animation.core.TweenSpec<java.lang.Float> AnimationSpec;
     property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long backgroundColor;
     property @androidx.compose.runtime.Composable public final long scrimColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     field public static final androidx.compose.material.DrawerDefaults INSTANCE;
     field public static final float ScrimOpacity = 0.32f;
   }
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 335d309..8ccafcf 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -149,6 +149,7 @@
 androidx {
     name = "Compose Material Components"
     type = LibraryType.PUBLISHED_KOTLIN_ONLY_LIBRARY
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2018"
     description = "Compose Material Design Components library"
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/material/material/samples/build.gradle b/compose/material/material/samples/build.gradle
index 649d091..daf6d1e 100644
--- a/compose/material/material/samples/build.gradle
+++ b/compose/material/material/samples/build.gradle
@@ -48,6 +48,7 @@
 androidx {
     name = "Compose Material Components Samples"
     type = LibraryType.SAMPLES
+    mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2019"
     description = "Contains the sample code for the AndroidX Compose Material components."
 }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 7cd36cd..d2f9de7 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -257,8 +257,9 @@
  *
  * @param initialValue The initial value of the state.
  * @param density The density that this state can use to convert values to and from dp.
- * @param animationSpec The animation spec to be used for animations.
  * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ * @param animationSpec The animation spec to be used for open/close animations, as well as
+ * settling when a user lets go.
  */
 @OptIn(ExperimentalMaterialApi::class)
 @Suppress("NotCloseable")
@@ -266,7 +267,7 @@
     initialValue: BottomDrawerValue,
     density: Density,
     confirmStateChange: (BottomDrawerValue) -> Boolean = { true },
-    animationSpec: AnimationSpec<Float> = AnimationSpec
+    animationSpec: AnimationSpec<Float> = DrawerDefaults.AnimationSpec
 ) {
     internal val anchoredDraggableState = AnchoredDraggableState(
         initialValue = initialValue,
@@ -284,7 +285,7 @@
         get() = anchoredDraggableState.targetValue
 
     /**
-     * The current offset, or [Float.NaN] if it has not been initialized yet.
+     * The current offset in pixels, or [Float.NaN] if it has not been initialized yet.
      */
     val offset: Float
         get() = anchoredDraggableState.offset
@@ -435,14 +436,15 @@
  * Create and [remember] a [BottomDrawerState].
  *
  * @param initialValue The initial value of the state.
- * @param animationSpec The animation spec to be used for animations.
  * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ * @param animationSpec The animation spec to be used for open/close animations, as well as
+ * settling when a user lets go.
  */
 @Composable
 fun rememberBottomDrawerState(
     initialValue: BottomDrawerValue,
     confirmStateChange: (BottomDrawerValue) -> Boolean = { true },
-    animationSpec: AnimationSpec<Float> = AnimationSpec,
+    animationSpec: AnimationSpec<Float> = DrawerDefaults.AnimationSpec,
 ): BottomDrawerState {
     val density = LocalDensity.current
     return rememberSaveable(
@@ -489,9 +491,9 @@
     modifier: Modifier = Modifier,
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
     gesturesEnabled: Boolean = true,
-    drawerShape: Shape = MaterialTheme.shapes.large,
+    drawerShape: Shape = DrawerDefaults.shape,
     drawerElevation: Dp = DrawerDefaults.Elevation,
-    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
+    drawerBackgroundColor: Color = DrawerDefaults.backgroundColor,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
     scrimColor: Color = DrawerDefaults.scrimColor,
     content: @Composable () -> Unit
@@ -599,13 +601,13 @@
  *
  * @sample androidx.compose.material.samples.BottomDrawerSample
  *
- * @param drawerState state of the drawer
+ * @param drawerContent composable that represents content inside the drawer
  * @param modifier optional [Modifier] for the entire component
+ * @param drawerState state of the drawer
  * @param gesturesEnabled whether or not drawer can be interacted by gestures
  * @param drawerShape shape of the drawer sheet
  * @param drawerElevation drawer sheet elevation. This controls the size of the shadow below the
  * drawer sheet
- * @param drawerContent composable that represents content inside the drawer
  * @param drawerBackgroundColor background color to be used for the drawer sheet
  * @param drawerContentColor color of the content to use inside the drawer sheet. Defaults to
  * either the matching content color for [drawerBackgroundColor], or, if it is not a color from
@@ -614,7 +616,6 @@
  * color passed is [Color.Unspecified], then a scrim will no longer be applied and the bottom
  * drawer will not block interaction with the rest of the screen when visible.
  * @param content content of the rest of the UI
- *
  */
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
@@ -623,9 +624,9 @@
     modifier: Modifier = Modifier,
     drawerState: BottomDrawerState = rememberBottomDrawerState(Closed),
     gesturesEnabled: Boolean = true,
-    drawerShape: Shape = MaterialTheme.shapes.large,
+    drawerShape: Shape = DrawerDefaults.shape,
     drawerElevation: Dp = DrawerDefaults.Elevation,
-    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
+    drawerBackgroundColor: Color = DrawerDefaults.backgroundColor,
     drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
     scrimColor: Color = DrawerDefaults.scrimColor,
     content: @Composable () -> Unit
@@ -744,10 +745,33 @@
 object DrawerDefaults {
 
     /**
-     * Default Elevation for drawer sheet as specified in material specs
+     * Default animation spec used for [ModalDrawer] and [BottomDrawer] open and close animations,
+     * as well as settling when a user lets go.
+     */
+    val AnimationSpec = TweenSpec<Float>(durationMillis = 256)
+
+    /**
+     * Default background color for drawer sheets
+     */
+    val backgroundColor: Color
+        @Composable
+        get() = MaterialTheme.colors.surface
+
+    /**
+     * Default elevation for drawer sheet as specified in material specs
      */
     val Elevation = 16.dp
 
+    /**
+     * Default shape for drawer sheets
+     */
+    val shape: Shape
+        @Composable
+        get() = MaterialTheme.shapes.large
+
+    /**
+     * Default color of the scrim that obscures content when the drawer is open
+     */
     val scrimColor: Color
         @Composable
         get() = MaterialTheme.colors.onSurface.copy(alpha = ScrimOpacity)
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 6d85541..7925121 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -28,13 +28,11 @@
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
     field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults INSTANCE;
   }
 
   public final class ListDetailPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
@@ -59,14 +57,12 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
-    ctor public PaneScaffoldDirective(androidx.compose.foundation.layout.PaddingValues contentPadding, int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
-    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    ctor public PaneScaffoldDirective(int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
     method public java.util.List<androidx.compose.ui.geometry.Rect> getExcludedBounds();
     method public float getHorizontalPartitionSpacerSize();
     method public int getMaxHorizontalPartitions();
     method public int getMaxVerticalPartitions();
     method public float getVerticalPartitionSpacerSize();
-    property public final androidx.compose.foundation.layout.PaddingValues contentPadding;
     property public final java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds;
     property public final float horizontalPartitionSpacerSize;
     property public final int maxHorizontalPartitions;
@@ -75,8 +71,8 @@
   }
 
   public final class PaneScaffoldDirectiveKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateDensePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateStandardPaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneScaffoldScope {
@@ -85,13 +81,11 @@
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
     field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults INSTANCE;
   }
 
   public final class SupportingPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 6d85541..7925121 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -28,13 +28,11 @@
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldDefaults {
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy detailPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy listPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
     field public static final androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults INSTANCE;
   }
 
   public final class ListDetailPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
@@ -59,14 +57,12 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class PaneScaffoldDirective {
-    ctor public PaneScaffoldDirective(androidx.compose.foundation.layout.PaddingValues contentPadding, int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
-    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    ctor public PaneScaffoldDirective(int maxHorizontalPartitions, float horizontalPartitionSpacerSize, int maxVerticalPartitions, float verticalPartitionSpacerSize, java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds);
     method public java.util.List<androidx.compose.ui.geometry.Rect> getExcludedBounds();
     method public float getHorizontalPartitionSpacerSize();
     method public int getMaxHorizontalPartitions();
     method public int getMaxVerticalPartitions();
     method public float getVerticalPartitionSpacerSize();
-    property public final androidx.compose.foundation.layout.PaddingValues contentPadding;
     property public final java.util.List<androidx.compose.ui.geometry.Rect> excludedBounds;
     property public final float horizontalPartitionSpacerSize;
     property public final int maxHorizontalPartitions;
@@ -75,8 +71,8 @@
   }
 
   public final class PaneScaffoldDirectiveKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateDensePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculateStandardPaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirective(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.layout.PaneScaffoldDirective calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(androidx.compose.material3.adaptive.WindowAdaptiveInfo windowAdaptiveInfo, optional int verticalHingePolicy);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneScaffoldScope {
@@ -85,13 +81,11 @@
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldDefaults {
     method public androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies(optional androidx.compose.material3.adaptive.layout.AdaptStrategy mainPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy supportingPaneAdaptStrategy, optional androidx.compose.material3.adaptive.layout.AdaptStrategy extraPaneAdaptStrategy);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
     field public static final androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults INSTANCE;
   }
 
   public final class SupportingPaneScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
index e22fcb5..bc9db43 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
@@ -19,7 +19,6 @@
 import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material3.MaterialTheme
@@ -132,48 +131,6 @@
     }
 
     @Test
-    fun threePaneScaffold_insets_compact_size_window() {
-        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
-        rule.setContent {
-            SampleThreePaneScaffoldWithInsets(mockInsets)
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_compact")
-    }
-
-    @Test
-    fun threePaneScaffold_insets_medium_size_window() {
-        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 700.dp,
-            simulatedHeight = 500.dp
-        ) {
-            SampleThreePaneScaffoldWithInsets(mockInsets)
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_medium")
-    }
-
-    @Test
-    fun threePaneScaffold_insets_expanded_size_window() {
-        val mockInsets = WindowInsets(100.dp, 10.dp, 20.dp, 50.dp)
-        rule.setContentWithSimulatedSize(
-            simulatedWidth = 1024.dp,
-            simulatedHeight = 800.dp
-        ) {
-            SampleThreePaneScaffoldWithInsets(mockInsets)
-        }
-
-        rule.onNodeWithTag(ThreePaneScaffoldTestTag)
-            .captureToImage()
-            .assertAgainstGolden(screenshotRule, "threePaneScaffold_insets_expanded")
-    }
-
-    @Test
     fun threePaneScaffold_paneExpansion_fixedFirstPaneWidth() {
         rule.setContentWithSimulatedSize(
             simulatedWidth = 1024.dp,
@@ -419,7 +376,7 @@
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Composable
 private fun SampleThreePaneScaffoldStandardMode() {
-    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+    val scaffoldDirective = calculatePaneScaffoldDirective(
         currentWindowAdaptiveInfo()
     )
     val scaffoldValue = calculateThreePaneScaffoldValue(
@@ -437,7 +394,7 @@
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Composable
 private fun SampleThreePaneScaffoldDenseMode() {
-    val scaffoldDirective = calculateDensePaneScaffoldDirective(
+    val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
         currentWindowAdaptiveInfo()
     )
     val scaffoldValue = calculateThreePaneScaffoldValue(
@@ -454,32 +411,11 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Composable
-private fun SampleThreePaneScaffoldWithInsets(
-    windowInsets: WindowInsets
-) {
-    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
-        currentWindowAdaptiveInfo()
-    )
-    val scaffoldValue = calculateThreePaneScaffoldValue(
-        scaffoldDirective.maxHorizontalPartitions,
-        ThreePaneScaffoldDefaults.adaptStrategies(),
-        null
-    )
-    SampleThreePaneScaffold(
-        scaffoldDirective = scaffoldDirective,
-        scaffoldValue = scaffoldValue,
-        paneOrder = ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder,
-        windowInsets = windowInsets
-    )
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-@Composable
 private fun SampleThreePaneScaffoldWithPaneExpansion(
     paneExpansionState: PaneExpansionState,
     paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
 ) {
-    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+    val scaffoldDirective = calculatePaneScaffoldDirective(
         currentWindowAdaptiveInfo()
     )
     val scaffoldValue = calculateThreePaneScaffoldValue(
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
index 47a4cd9..015fc59 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
@@ -16,12 +16,7 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.union
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
@@ -174,7 +169,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
     maxHorizontalPartitions = 1,
     horizontalPartitionSpacerSize = 0.dp,
     maxVerticalPartitions = 1,
@@ -202,7 +196,6 @@
     paneOrder: ThreePaneScaffoldHorizontalOrder,
     paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
     paneExpansionState: PaneExpansionState = PaneExpansionState(),
-    windowInsets: WindowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout)
 ) {
     ThreePaneScaffold(
         modifier = Modifier.fillMaxSize().testTag(ThreePaneScaffoldTestTag),
@@ -211,7 +204,6 @@
         paneOrder = paneOrder,
         paneExpansionState = paneExpansionState,
         paneExpansionDragHandle = paneExpansionDragHandle,
-        windowInsets = windowInsets,
         secondaryPane = {
             AnimatedPane(
                 modifier = Modifier.testTag(tag = "SecondaryPane")
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
index 2813adb..668f867 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirectiveTest.kt
@@ -21,7 +21,6 @@
 import androidx.compose.material3.adaptive.Posture
 import androidx.compose.material3.adaptive.WindowAdaptiveInfo
 import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.window.core.layout.WindowSizeClass
 import com.google.common.truth.Truth.assertThat
@@ -34,7 +33,7 @@
 class PaneScaffoldDirectiveTest {
     @Test
     fun test_calculateStandardPaneScaffoldDirective_compactWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(400, 800),
                 Posture()
@@ -43,21 +42,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(16.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
     }
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_mediumWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(750, 900),
                 Posture()
@@ -66,21 +57,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
     }
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_expandedWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(1200, 800),
                 Posture()
@@ -89,21 +72,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
     }
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_tabletop() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(isTabletop = true)
@@ -112,21 +87,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(2)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(24.dp)
     }
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_compactWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(400, 800),
                 Posture()
@@ -135,21 +102,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(1)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(16.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(0.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(16.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(16.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
     }
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_mediumWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(750, 900),
                 Posture()
@@ -158,21 +117,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
     }
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_expandedWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(1200, 800),
                 Posture()
@@ -181,21 +132,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(1)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(0.dp)
     }
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_tabletop() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(isTabletop = true)
@@ -204,21 +147,13 @@
 
         assertThat(scaffoldDirective.maxHorizontalPartitions).isEqualTo(2)
         assertThat(scaffoldDirective.maxVerticalPartitions).isEqualTo(2)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateLeftPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
-        assertThat(
-            scaffoldDirective.contentPadding.calculateRightPadding(LayoutDirection.Ltr)
-        ).isEqualTo(24.dp)
         assertThat(scaffoldDirective.horizontalPartitionSpacerSize).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateTopPadding()).isEqualTo(24.dp)
-        assertThat(scaffoldDirective.contentPadding.calculateBottomPadding()).isEqualTo(24.dp)
         assertThat(scaffoldDirective.verticalPartitionSpacerSize).isEqualTo(24.dp)
     }
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_alwaysAvoidHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -231,7 +166,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_avoidOccludingHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -244,7 +179,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_avoidSeparatingHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -257,7 +192,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_neverAvoidHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -270,7 +205,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_alwaysAvoidHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -283,7 +218,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_avoidOccludingHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -296,7 +231,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_avoidSeparatingHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -309,7 +244,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_neverAvoidHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
index aa58ec5..74f39ac 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -16,11 +16,7 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.union
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -47,7 +43,6 @@
  * @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info
  *        besides the list and the detail panes, for example, a task list or a mini-calendar view of
  *        a mail app. See [ListDetailPaneScaffoldRole.Extra].
- * @param windowInsets window insets that the scaffold will respect.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -58,14 +53,12 @@
     detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    windowInsets: WindowInsets = ListDetailPaneScaffoldDefaults.windowInsets,
 ) {
     ThreePaneScaffold(
         modifier = modifier.fillMaxSize(),
         scaffoldDirective = directive,
         scaffoldValue = value,
         paneOrder = ThreePaneScaffoldDefaults.ListDetailLayoutPaneOrder,
-        windowInsets = windowInsets,
         secondaryPane = listPane,
         tertiaryPane = extraPane,
         primaryPane = detailPane
@@ -78,13 +71,6 @@
 @ExperimentalMaterial3AdaptiveApi
 object ListDetailPaneScaffoldDefaults {
     /**
-     * Default insets that will be used and consumed by [ListDetailPaneScaffold]. By default it will
-     * be the union of [WindowInsets.Companion.systemBars] and
-     * [WindowInsets.Companion.displayCutout].
-     */
-    val windowInsets @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
-
-    /**
      * Creates a default [ThreePaneScaffoldAdaptStrategies] for [ListDetailPaneScaffold].
      *
      * @param detailPaneAdaptStrategy the adapt strategy of the primary pane
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
index 18bc602..42a842b 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.Posture
 import androidx.compose.material3.adaptive.WindowAdaptiveInfo
@@ -31,7 +30,7 @@
 import androidx.window.core.layout.WindowWidthSizeClass
 
 /**
- * Calculates the standard [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
+ * Calculates the recommended [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
  * method with [currentWindowAdaptiveInfo] to acquire Material-recommended adaptive layout
  * settings of the current activity window.
  *
@@ -44,29 +43,24 @@
  *        vertical hinges.
  * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
  */
-// TODO(b/285144647): Add more details regarding the use scenarios of this function.
 @ExperimentalMaterial3AdaptiveApi
-fun calculateStandardPaneScaffoldDirective(
+fun calculatePaneScaffoldDirective(
     windowAdaptiveInfo: WindowAdaptiveInfo,
     verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
 ): PaneScaffoldDirective {
     val maxHorizontalPartitions: Int
-    val contentPadding: PaddingValues
     val verticalSpacerSize: Dp
     when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
         WindowWidthSizeClass.COMPACT -> {
             maxHorizontalPartitions = 1
-            contentPadding = PaddingValues(16.dp)
             verticalSpacerSize = 0.dp
         }
         WindowWidthSizeClass.MEDIUM -> {
             maxHorizontalPartitions = 1
-            contentPadding = PaddingValues(24.dp)
             verticalSpacerSize = 0.dp
         }
         else -> {
             maxHorizontalPartitions = 2
-            contentPadding = PaddingValues(24.dp)
             verticalSpacerSize = 24.dp
         }
     }
@@ -83,7 +77,6 @@
     }
 
     return PaneScaffoldDirective(
-        contentPadding,
         maxHorizontalPartitions,
         verticalSpacerSize,
         maxVerticalPartitions,
@@ -93,9 +86,13 @@
 }
 
 /**
- * Calculates the dense-mode [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
+ * Calculates the recommended [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this
  * method with [currentWindowAdaptiveInfo] to acquire Material-recommended dense-mode adaptive
- * layout settings of the current activity window.
+ * layout settings of the current activity window. Note that this function results in a dual-pane
+ * layout when the [WindowWidthSizeClass] is [WindowWidthSizeClass.MEDIUM], while
+ * [calculatePaneScaffoldDirective] results in a single-pane layout instead. We recommend to use
+ * [calculatePaneScaffoldDirective], unless you have a strong use case to show two panes on
+ * a medium-width window, which can make your layout look too packed.
  *
  * See more details on the [Material design guideline site]
  * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes).
@@ -106,29 +103,24 @@
  *        vertical hinges.
  * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states.
  */
-// TODO(b/285144647): Add more details regarding the use scenarios of this function.
 @ExperimentalMaterial3AdaptiveApi
-fun calculateDensePaneScaffoldDirective(
+fun calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
     windowAdaptiveInfo: WindowAdaptiveInfo,
     verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating
 ): PaneScaffoldDirective {
     val maxHorizontalPartitions: Int
-    val contentPadding: PaddingValues
     val verticalSpacerSize: Dp
     when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) {
         WindowWidthSizeClass.COMPACT -> {
             maxHorizontalPartitions = 1
-            contentPadding = PaddingValues(16.dp)
             verticalSpacerSize = 0.dp
         }
         WindowWidthSizeClass.MEDIUM -> {
             maxHorizontalPartitions = 2
-            contentPadding = PaddingValues(24.dp)
             verticalSpacerSize = 24.dp
         }
         else -> {
             maxHorizontalPartitions = 2
-            contentPadding = PaddingValues(24.dp)
             verticalSpacerSize = 24.dp
         }
     }
@@ -144,7 +136,6 @@
     }
 
     return PaneScaffoldDirective(
-        contentPadding,
         maxHorizontalPartitions,
         verticalSpacerSize,
         maxVerticalPartitions,
@@ -168,7 +159,6 @@
  * partitions the layout can be split into and what should be the gutter size.
  *
  * @constructor create an instance of [PaneScaffoldDirective]
- * @param contentPadding Size of the paddings between the panes and the outer bounds of the layout.
  * @param maxHorizontalPartitions the max number of partitions along the horizontal axis the layout
  *        can be split into.
  * @param horizontalPartitionSpacerSize Size of the spacers between horizontal partitions.
@@ -183,7 +173,6 @@
 @ExperimentalMaterial3AdaptiveApi
 @Immutable
 class PaneScaffoldDirective(
-    val contentPadding: PaddingValues,
     val maxHorizontalPartitions: Int,
     val horizontalPartitionSpacerSize: Dp,
     val maxVerticalPartitions: Int,
@@ -193,7 +182,6 @@
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is PaneScaffoldDirective) return false
-        if (contentPadding != other.contentPadding) return false
         if (maxHorizontalPartitions != other.maxHorizontalPartitions) return false
         if (horizontalPartitionSpacerSize != other.horizontalPartitionSpacerSize) return false
         if (maxVerticalPartitions != other.maxVerticalPartitions) return false
@@ -202,8 +190,7 @@
     }
 
     override fun hashCode(): Int {
-        var result = contentPadding.hashCode()
-        result = 31 * result + maxHorizontalPartitions
+        var result = maxHorizontalPartitions
         result = 31 * result + horizontalPartitionSpacerSize.hashCode()
         result = 31 * result + maxVerticalPartitions
         result = 31 * result + verticalPartitionSpacerSize.hashCode()
@@ -211,8 +198,7 @@
     }
 
     override fun toString(): String {
-        return "PaneScaffoldDirective(contentPadding=$contentPadding, " +
-            "maxHorizontalPartitions=$maxHorizontalPartitions, " +
+        return "PaneScaffoldDirective(maxHorizontalPartitions=$maxHorizontalPartitions, " +
             "horizontalPartitionSpacerSize=$horizontalPartitionSpacerSize, " +
             "maxVerticalPartitions=$maxVerticalPartitions, " +
             "verticalPartitionSpacerSize=$verticalPartitionSpacerSize, " +
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
index a70ea4e..8c62ee1 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
@@ -16,11 +16,7 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.union
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -41,7 +37,6 @@
  * @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content
  *        besides the main and the supporting panes, for example, a styling panel in a doc app.
  *        See [SupportingPaneScaffoldRole.Extra].
- * @param windowInsets window insets that the scaffold will respect.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -52,14 +47,12 @@
     supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    windowInsets: WindowInsets = SupportingPaneScaffoldDefaults.windowInsets,
 ) {
     ThreePaneScaffold(
         modifier = modifier.fillMaxSize(),
         scaffoldDirective = directive,
         scaffoldValue = value,
         paneOrder = ThreePaneScaffoldDefaults.SupportingPaneLayoutPaneOrder,
-        windowInsets = windowInsets,
         secondaryPane = supportingPane,
         tertiaryPane = extraPane,
         primaryPane = mainPane
@@ -72,13 +65,6 @@
 @ExperimentalMaterial3AdaptiveApi
 object SupportingPaneScaffoldDefaults {
     /**
-     * Default insets that will be used and consumed by [SupportingPaneScaffold]. By default it will
-     * be the union of [WindowInsets.Companion.systemBars] and
-     * [WindowInsets.Companion.displayCutout].
-     */
-    val windowInsets @Composable get() = WindowInsets.systemBars.union(WindowInsets.displayCutout)
-
-    /**
      * Creates a default [ThreePaneScaffoldAdaptStrategies] for [SupportingPaneScaffold].
      *
      * @param mainPaneAdaptStrategy the adapt strategy of the main pane
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index b7d2264c..2518f1a 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -25,7 +25,6 @@
 import androidx.compose.animation.core.Transition
 import androidx.compose.animation.core.rememberTransition
 import androidx.compose.animation.core.snap
-import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
@@ -70,7 +69,7 @@
  * freely pipeline the relevant adaptive signals and use them as input of the scaffold function
  * to render the final adaptive layout.
  *
- * It's recommended to use [ThreePaneScaffold] with [calculateStandardPaneScaffoldDirective],
+ * It's recommended to use [ThreePaneScaffold] with [calculatePaneScaffoldDirective],
  * [calculateThreePaneScaffoldValue] to follow the Material design guidelines on adaptive
  * programming.
  *
@@ -90,7 +89,6 @@
     scaffoldDirective: PaneScaffoldDirective,
     scaffoldValue: ThreePaneScaffoldValue,
     paneOrder: ThreePaneScaffoldHorizontalOrder,
-    windowInsets: WindowInsets,
     secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
     paneExpansionState: PaneExpansionState = PaneExpansionState(),
@@ -108,7 +106,6 @@
         scaffoldDirective = scaffoldDirective,
         scaffoldState = scaffoldState,
         paneOrder = paneOrder,
-        windowInsets = windowInsets,
         secondaryPane = secondaryPane,
         tertiaryPane = tertiaryPane,
         paneExpansionState = paneExpansionState,
@@ -124,7 +121,6 @@
     scaffoldDirective: PaneScaffoldDirective,
     scaffoldState: SeekableTransitionState<ThreePaneScaffoldValue>,
     paneOrder: ThreePaneScaffoldHorizontalOrder,
-    windowInsets: WindowInsets,
     secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
     paneExpansionState: PaneExpansionState = PaneExpansionState(),
@@ -239,13 +235,11 @@
                 scaffoldState.targetState,
                 paneExpansionState,
                 ltrPaneOrder,
-                windowInsets
             )
         }.apply {
             this.scaffoldDirective = scaffoldDirective
             this.scaffoldValue = scaffoldState.targetState
             this.paneOrder = ltrPaneOrder
-            this.windowInsets = windowInsets
         }
 
         Layout(
@@ -265,12 +259,10 @@
     scaffoldValue: ThreePaneScaffoldValue,
     val paneExpansionState: PaneExpansionState,
     paneOrder: ThreePaneScaffoldHorizontalOrder,
-    windowInsets: WindowInsets
 ) : MultiContentMeasurePolicy {
     var scaffoldDirective by mutableStateOf(scaffoldDirective)
     var scaffoldValue by mutableStateOf(scaffoldValue)
     var paneOrder by mutableStateOf(paneOrder)
-    var windowInsets by mutableStateOf(windowInsets)
 
     /**
      * Data class that is used to store the position and width of an expanded pane to be reused when
@@ -317,32 +309,16 @@
             }
 
             val verticalSpacerSize = scaffoldDirective.horizontalPartitionSpacerSize.roundToPx()
-            val leftContentPadding = max(
-                scaffoldDirective.contentPadding.calculateLeftPadding(layoutDirection).roundToPx(),
-                windowInsets.getLeft(this@measure, layoutDirection)
-            )
-            val rightContentPadding = max(
-                scaffoldDirective.contentPadding.calculateRightPadding(layoutDirection).roundToPx(),
-                windowInsets.getRight(this@measure, layoutDirection)
-            )
-            val topContentPadding = max(
-                scaffoldDirective.contentPadding.calculateTopPadding().roundToPx(),
-                windowInsets.getTop(this@measure)
-            )
-            val bottomContentPadding = max(
-                scaffoldDirective.contentPadding.calculateBottomPadding().roundToPx(),
-                windowInsets.getBottom(this@measure)
-            )
             val outerBounds = IntRect(
-                leftContentPadding,
-                topContentPadding,
-                constraints.maxWidth - rightContentPadding,
-                constraints.maxHeight - bottomContentPadding
+                0,
+                0,
+                constraints.maxWidth,
+                constraints.maxHeight
             )
 
             if (!paneExpansionState.isUnspecified()) {
                 // Pane expansion should override everything
-                val availableWidth = constraints.maxWidth - leftContentPadding - rightContentPadding
+                val availableWidth = constraints.maxWidth
                 if (paneExpansionState.firstPaneWidth == 0 ||
                     paneExpansionState.firstPanePercentage == 0f) {
                     if (visiblePanes.size > 1) {
@@ -388,10 +364,10 @@
             } else if (scaffoldDirective.excludedBounds.isNotEmpty()) {
                 val layoutBounds = coordinates!!.boundsInWindow()
                 val layoutPhysicalPartitions = mutableListOf<Rect>()
-                var actualLeft = layoutBounds.left + leftContentPadding
-                var actualRight = layoutBounds.right - rightContentPadding
-                val actualTop = layoutBounds.top + topContentPadding
-                val actualBottom = layoutBounds.bottom - bottomContentPadding
+                var actualLeft = layoutBounds.left
+                var actualRight = layoutBounds.right
+                val actualTop = layoutBounds.top
+                val actualBottom = layoutBounds.bottom
                 // Assume hinge bounds are sorted from left to right, non-overlapped.
                 @Suppress("ListIterator")
                 scaffoldDirective.excludedBounds.forEach { hingeBound ->
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index 0c5fcc1..1a5b767 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -24,7 +24,9 @@
   }
 
   public final class ThreePaneScaffoldNavigatorKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
   }
 
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
index 0c5fcc1..1a5b767 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -24,7 +24,9 @@
   }
 
   public final class ThreePaneScaffoldNavigatorKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware);
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static <T> androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<T> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.layout.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional boolean isDestinationHistoryAware, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem<? extends T>> initialDestinationHistory);
   }
 
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
index 358be71..5b6e31c 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/ListDetailPaneScaffoldNavigatorTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.material3.adaptive.navigation
 
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
@@ -47,7 +46,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockSinglePaneScaffoldDirective
             )
             canNavigateBack = scaffoldNavigator.canNavigateBack()
@@ -78,7 +77,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockDualPaneScaffoldDirective
             )
             canNavigateBack = scaffoldNavigator.canNavigateBack()
@@ -109,7 +108,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockDualPaneScaffoldDirective,
                 isDestinationHistoryAware = false
             )
@@ -141,7 +140,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockDualPaneScaffoldDirective,
                 isDestinationHistoryAware = true
             )
@@ -173,7 +172,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
+            scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockSinglePaneScaffoldDirective
             )
             canNavigateBack = scaffoldNavigator.canNavigateBack()
@@ -569,7 +568,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
     maxHorizontalPartitions = 1,
     horizontalPartitionSpacerSize = 0.dp,
     maxVerticalPartitions = 1,
@@ -579,7 +577,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
     maxHorizontalPartitions = 2,
     horizontalPartitionSpacerSize = 16.dp,
     maxVerticalPartitions = 1,
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
index 5092174..bae3704 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/navigation/SupportingPaneScaffoldNavigatorTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.material3.adaptive.navigation
 
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
 import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective
@@ -47,7 +46,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockSinglePaneScaffoldDirective
             )
             canNavigateBack = scaffoldNavigator.canNavigateBack()
@@ -78,7 +77,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockDualPaneScaffoldDirective
             )
             canNavigateBack = scaffoldNavigator.canNavigateBack()
@@ -187,7 +186,7 @@
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
-            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
+            scaffoldNavigator = rememberSupportingPaneScaffoldNavigator<Int>(
                 scaffoldDirective = MockSinglePaneScaffoldDirective
             )
             canNavigateBack = scaffoldNavigator.canNavigateBack()
@@ -583,7 +582,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockSinglePaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(0.dp),
     maxHorizontalPartitions = 1,
     horizontalPartitionSpacerSize = 0.dp,
     maxVerticalPartitions = 1,
@@ -593,7 +591,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockDualPaneScaffoldDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
     maxHorizontalPartitions = 2,
     horizontalPartitionSpacerSize = 16.dp,
     maxVerticalPartitions = 1,
diff --git a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
index b419700..7d0d22a 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt
@@ -29,7 +29,7 @@
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue
-import androidx.compose.material3.adaptive.layout.calculateStandardPaneScaffoldDirective
+import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective
 import androidx.compose.material3.adaptive.layout.calculateThreePaneScaffoldValue
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -148,7 +148,7 @@
  *   This type must be storable in a Bundle. Used to customize navigation behavior (for example,
  *   [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing].
  * @param scaffoldDirective the current layout directives to follow. The default value will be
- *   calculated with [calculateStandardPaneScaffoldDirective] using
+ *   calculated with [calculatePaneScaffoldDirective] using
  *   [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from
  *   the current context.
  * @param adaptStrategies adaptation strategies of each pane.
@@ -162,7 +162,7 @@
 @Composable
 fun <T> rememberListDetailPaneScaffoldNavigator(
     scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+        calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         ListDetailPaneScaffoldDefaults.adaptStrategies(),
     isDestinationHistoryAware: Boolean = true,
@@ -178,6 +178,36 @@
 
 /**
  * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
+ * [ListDetailPaneScaffold], which will be updated automatically when the input values change.
+ * The default navigator is supposed to be used independently from any navigation frameworks and
+ * handles the navigation purely inside the [ListDetailPaneScaffold].
+ *
+ * @param scaffoldDirective the current layout directives to follow. The default value will be
+ *   calculated with [calculatePaneScaffoldDirective] using
+ *   [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from
+ *   the current context.
+ * @param adaptStrategies adaptation strategies of each pane.
+ * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the
+ *   full destination history, instead of just the current destination. See
+ *   [calculateThreePaneScaffoldValue] for more relevant details.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun rememberListDetailPaneScaffoldNavigator(
+    scaffoldDirective: PaneScaffoldDirective =
+        calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        ListDetailPaneScaffoldDefaults.adaptStrategies(),
+    isDestinationHistoryAware: Boolean = true,
+): ThreePaneScaffoldNavigator<Nothing> =
+    rememberListDetailPaneScaffoldNavigator<Nothing>(
+        scaffoldDirective,
+        adaptStrategies,
+        isDestinationHistoryAware,
+    )
+
+/**
+ * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
  * [SupportingPaneScaffold], which will be updated automatically when the input values change.
  * The default navigator is supposed to be used independently from any navigation frameworks and
  * handles the navigation purely inside the [SupportingPaneScaffold].
@@ -186,7 +216,7 @@
  *   This type must be storable in a Bundle. Used to customize navigation behavior (for example,
  *   [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing].
  * @param scaffoldDirective the current layout directives to follow. The default value will be
- *   calculated with [calculateStandardPaneScaffoldDirective] using
+ *   calculated with [calculatePaneScaffoldDirective] using
  *   [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from
  *   the current context.
  * @param adaptStrategies adaptation strategies of each pane.
@@ -200,7 +230,7 @@
 @Composable
 fun <T> rememberSupportingPaneScaffoldNavigator(
     scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+        calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         SupportingPaneScaffoldDefaults.adaptStrategies(),
     isDestinationHistoryAware: Boolean = true,
@@ -214,6 +244,36 @@
         initialDestinationHistory
     )
 
+/**
+ * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
+ * [SupportingPaneScaffold], which will be updated automatically when the input values change.
+ * The default navigator is supposed to be used independently from any navigation frameworks and
+ * handles the navigation purely inside the [SupportingPaneScaffold].
+ *
+ * @param scaffoldDirective the current layout directives to follow. The default value will be
+ *   calculated with [calculatePaneScaffoldDirective] using
+ *   [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from
+ *   the current context.
+ * @param adaptStrategies adaptation strategies of each pane.
+ * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the
+ *   full destination history, instead of just the current destination. See
+ *   [calculateThreePaneScaffoldValue] for more relevant details.
+ */
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+fun rememberSupportingPaneScaffoldNavigator(
+    scaffoldDirective: PaneScaffoldDirective =
+        calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()),
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies =
+        SupportingPaneScaffoldDefaults.adaptStrategies(),
+    isDestinationHistoryAware: Boolean = true,
+): ThreePaneScaffoldNavigator<Nothing> =
+    rememberSupportingPaneScaffoldNavigator<Nothing>(
+        scaffoldDirective,
+        adaptStrategies,
+        isDestinationHistoryAware,
+    )
+
 @ExperimentalMaterial3AdaptiveApi
 @Composable
 internal fun <T> rememberThreePaneScaffoldNavigator(
diff --git a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
index acfcf48..302c605 100644
--- a/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
+++ b/compose/material3/adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.layout.AnimatedPane
@@ -37,7 +36,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 val singlePaneDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
     maxHorizontalPartitions = 1,
     horizontalPartitionSpacerSize = 0.dp,
     maxVerticalPartitions = 1,
@@ -47,7 +45,6 @@
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 val dualPaneDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(24.dp),
     maxHorizontalPartitions = 2,
     horizontalPartitionSpacerSize = 24.dp,
     maxVerticalPartitions = 1,
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
index fa976d5..9db35f3 100644
--- a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
+++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -71,7 +71,7 @@
 @Sampled
 @Composable
 fun ListDetailPaneScaffoldSample() {
-    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
+    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator()
     ListDetailPaneScaffold(
         directive = scaffoldNavigator.scaffoldDirective,
         value = scaffoldNavigator.scaffoldValue,
@@ -109,7 +109,7 @@
 @Sampled
 @Composable
 fun ListDetailPaneScaffoldSampleWithExtraPane() {
-    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
+    val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator()
     ListDetailPaneScaffold(
         directive = scaffoldNavigator.scaffoldDirective,
         value = scaffoldNavigator.scaffoldValue,
diff --git a/compose/material3/material3-common/api/current.txt b/compose/material3/material3-common/api/current.txt
index cf8d9ee..6ca7d3f 100644
--- a/compose/material3/material3-common/api/current.txt
+++ b/compose/material3/material3-common/api/current.txt
@@ -12,11 +12,9 @@
   }
 
   public final class InteractiveComponentSizeKt {
-    method @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumTouchTargetEnforcement();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalMinimumInteractiveComponentSize();
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
-    property @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
-    property @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalMinimumInteractiveComponentSize;
   }
 
 }
diff --git a/compose/material3/material3-common/api/restricted_current.txt b/compose/material3/material3-common/api/restricted_current.txt
index cf8d9ee..6ca7d3f 100644
--- a/compose/material3/material3-common/api/restricted_current.txt
+++ b/compose/material3/material3-common/api/restricted_current.txt
@@ -12,11 +12,9 @@
   }
 
   public final class InteractiveComponentSizeKt {
-    method @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumTouchTargetEnforcement();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalMinimumInteractiveComponentSize();
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
-    property @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
-    property @Deprecated @SuppressCompatibility @androidx.compose.material3.common.ExperimentalMaterial3CommonApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalMinimumInteractiveComponentSize;
   }
 
 }
diff --git a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/Icon.kt b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/Icon.kt
index 6a35918..ec79327 100644
--- a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/Icon.kt
+++ b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/Icon.kt
@@ -46,7 +46,7 @@
  * color. If [imageVector] has no intrinsic size, this component will use the
  * recommended default size. Icon is an opinionated component designed to be used with single-color
  * icons so that they can be tinted correctly for the component they are placed in. For multicolored
- * icons and icons that should not be tinted, use [Color.Unspecified] for [tint]. For generic images
+ * icons and icons that should not be tinted, use null for [tint]. For generic images
  * that should not be tinted, and do not follow the recommended icon size, use the generic
  * [androidx.compose.foundation.Image] instead. For a clickable icon, see [IconButton].
  *
diff --git a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt
index 6cb3de9..363de05 100644
--- a/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt
+++ b/compose/material3/material3-common/src/commonMain/kotlin/androidx/compose/material3/common/InteractiveComponentSize.kt
@@ -20,7 +20,6 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
@@ -30,8 +29,10 @@
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.coerceAtLeast
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isSpecified
 import kotlin.math.roundToInt
 
 /**
@@ -74,24 +75,23 @@
     Modifier.Node(),
     CompositionLocalConsumerModifierNode,
     LayoutModifierNode {
-
-    @OptIn(ExperimentalMaterial3CommonApi::class)
     override fun MeasureScope.measure(
         measurable: Measurable,
         constraints: Constraints
     ): MeasureResult {
-        val size = minimumInteractiveComponentSize
+        val size = currentValueOf(LocalMinimumInteractiveComponentSize).coerceAtLeast(0.dp)
         val placeable = measurable.measure(constraints)
-        val enforcement = isAttached && currentValueOf(LocalMinimumInteractiveComponentEnforcement)
+        val enforcement = isAttached && (size.isSpecified && size > 0.dp)
 
+        val sizePx = if (size.isSpecified) size.roundToPx() else 0
         // Be at least as big as the minimum dimension in both dimensions
         val width = if (enforcement) {
-            maxOf(placeable.width, size.width.roundToPx())
+            maxOf(placeable.width, sizePx)
         } else {
             placeable.width
         }
         val height = if (enforcement) {
-            maxOf(placeable.height, size.height.roundToPx())
+            maxOf(placeable.height, sizePx)
         } else {
             placeable.height
         }
@@ -105,67 +105,12 @@
 }
 
 /**
- * CompositionLocal that configures whether Material components that have a visual size that is
- * lower than the minimum touch target size for accessibility (such as Button) will include
- * extra space outside the component to ensure that they are accessible. If set to false there
- * will be no extra space, and so it is possible that if the component is placed near the edge of
- * a layout / near to another component without any padding, there will not be enough space for
- * an accessible touch target.
+ * CompositionLocal that configures the minimum touch target size for Material components
+ * (such as [Button]) to ensure they are accessible. If a component has a visual size
+ * that is lower than the minimum touch target size, extra space outside the component will be
+ * included. If set to [Dp.Unspecified] there will be no extra space, and so it is possible that if the
+ * component is placed near the edge of a layout / near to another component without any padding,
+ * there will not be enough space for an accessible touch target.
  */
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterial3CommonApi
-@ExperimentalMaterial3CommonApi
-val LocalMinimumInteractiveComponentEnforcement: ProvidableCompositionLocal<Boolean> =
-    staticCompositionLocalOf { true }
-
-/**
- * CompositionLocal that configures whether Material components that have a visual size that is
- * lower than the minimum touch target size for accessibility (such as [Button]) will include
- * extra space outside the component to ensure that they are accessible. If set to false there
- * will be no extra space, and so it is possible that if the component is placed near the edge of
- * a layout / near to another component without any padding, there will not be enough space for
- * an accessible touch target.
- */
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterial3CommonApi
-@ExperimentalMaterial3CommonApi
-@Deprecated(
-    message = "Use LocalMinimumInteractiveComponentEnforcement instead.",
-    replaceWith = ReplaceWith(
-        "LocalMinimumInteractiveComponentEnforcement"
-    ),
-    level = DeprecationLevel.WARNING
-)
-val LocalMinimumTouchTargetEnforcement: ProvidableCompositionLocal<Boolean> =
-    LocalMinimumInteractiveComponentEnforcement
-
-private class MinimumInteractiveComponentSizeModifier(val size: DpSize) : LayoutModifier {
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-
-        val placeable = measurable.measure(constraints)
-
-        // Be at least as big as the minimum dimension in both dimensions
-        val width = maxOf(placeable.width, size.width.roundToPx())
-        val height = maxOf(placeable.height, size.height.roundToPx())
-
-        return layout(width, height) {
-            val centerX = ((width - placeable.width) / 2f).roundToInt()
-            val centerY = ((height - placeable.height) / 2f).roundToInt()
-            placeable.place(centerX, centerY)
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        val otherModifier = other as? MinimumInteractiveComponentSizeModifier ?: return false
-        return size == otherModifier.size
-    }
-
-    override fun hashCode(): Int {
-        return size.hashCode()
-    }
-}
-
-private val minimumInteractiveComponentSize: DpSize = DpSize(48.dp, 48.dp)
+val LocalMinimumInteractiveComponentSize: ProvidableCompositionLocal<Dp> =
+    staticCompositionLocalOf { 48.dp }
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 7c294bb..097e597 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -36,11 +36,14 @@
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.BottomAppBarState BottomAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float collapsedHeight, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float collapsedHeight, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
   }
@@ -622,17 +625,6 @@
     property public abstract kotlin.ranges.IntRange yearRange;
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection EndToStart;
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection StartToEnd;
-  }
-
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue Default;
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToEnd;
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToStart;
-  }
-
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DisplayMode {
     field public static final androidx.compose.material3.DisplayMode.Companion Companion;
   }
@@ -892,11 +884,11 @@
   }
 
   public final class InteractiveComponentSizeKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumTouchTargetEnforcement();
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalMinimumInteractiveComponentSize();
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
-    property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
+    property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalMinimumInteractiveComponentSize;
   }
 
   public final class LabelKt {
@@ -1009,7 +1001,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface MultiChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
+  public interface MultiChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
   }
 
   public final class NavigationBarDefaults {
@@ -1068,8 +1060,10 @@
   }
 
   public final class NavigationDrawerKt {
+    method @androidx.compose.runtime.Composable public static void DismissibleDrawerSheet(androidx.compose.material3.DrawerState drawerState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DismissibleDrawerSheet(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DismissibleNavigationDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void ModalDrawerSheet(androidx.compose.material3.DrawerState drawerState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void ModalDrawerSheet(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void ModalNavigationDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void NavigationDrawerItem(kotlin.jvm.functions.Function0<kotlin.Unit> label, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.NavigationDrawerItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
@@ -1137,9 +1131,7 @@
   }
 
   public final class OutlinedTextFieldKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
@@ -1215,7 +1207,7 @@
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class RangeSliderState {
     ctor public RangeSliderState(optional float activeRangeStart, optional float activeRangeEnd, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
     method public float getActiveRangeEnd();
     method public float getActiveRangeStart();
@@ -1224,6 +1216,7 @@
     method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
     method public void setActiveRangeEnd(float);
     method public void setActiveRangeStart(float);
+    method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
     property public final float activeRangeEnd;
     property public final float activeRangeStart;
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
@@ -1280,6 +1273,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SearchBarColors {
+    ctor public SearchBarColors(long containerColor, long dividerColor, androidx.compose.material3.TextFieldColors inputFieldColors);
     method public long getContainerColor();
     method public long getDividerColor();
     method public androidx.compose.material3.TextFieldColors getInputFieldColors();
@@ -1316,7 +1310,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SearchBar(String query, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onQueryChange, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onSearch, boolean active, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onActiveChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SearchBarColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SegmentedButtonColors {
+  @androidx.compose.runtime.Immutable public final class SegmentedButtonColors {
     ctor public SegmentedButtonColors(long activeContainerColor, long activeContentColor, long activeBorderColor, long inactiveContainerColor, long inactiveContentColor, long inactiveBorderColor, long disabledActiveContainerColor, long disabledActiveContentColor, long disabledActiveBorderColor, long disabledInactiveContainerColor, long disabledInactiveContentColor, long disabledInactiveBorderColor);
     method public androidx.compose.material3.SegmentedButtonColors copy(optional long activeContainerColor, optional long activeContentColor, optional long activeBorderColor, optional long inactiveContainerColor, optional long inactiveContentColor, optional long inactiveBorderColor, optional long disabledActiveContainerColor, optional long disabledActiveContentColor, optional long disabledActiveBorderColor, optional long disabledInactiveContainerColor, optional long disabledInactiveContentColor, optional long disabledInactiveBorderColor);
     method public long getActiveBorderColor();
@@ -1345,7 +1339,7 @@
     property public final long inactiveContentColor;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SegmentedButtonDefaults {
+  @androidx.compose.runtime.Stable public final class SegmentedButtonDefaults {
     method @androidx.compose.runtime.Composable public void ActiveIcon();
     method @androidx.compose.runtime.Composable public void Icon(boolean active, optional kotlin.jvm.functions.Function0<kotlin.Unit> activeContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? inactiveContent);
     method public androidx.compose.foundation.BorderStroke borderStroke(long color, optional float width);
@@ -1362,10 +1356,10 @@
   }
 
   public final class SegmentedButtonKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MultiChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.MultiChoiceSegmentedButtonRowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.MultiChoiceSegmentedButtonRowScope, boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.SingleChoiceSegmentedButtonRowScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SingleChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SingleChoiceSegmentedButtonRowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void MultiChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.MultiChoiceSegmentedButtonRowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.MultiChoiceSegmentedButtonRowScope, boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
+    method @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.SingleChoiceSegmentedButtonRowScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
+    method @androidx.compose.runtime.Composable public static void SingleChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SingleChoiceSegmentedButtonRowScope,kotlin.Unit> content);
   }
 
   @androidx.compose.runtime.Immutable public final class SelectableChipColors {
@@ -1453,7 +1447,7 @@
     enum_constant public static final androidx.compose.material3.SheetValue PartiallyExpanded;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
+  public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
   }
 
   @androidx.compose.runtime.Immutable public final class SliderColors {
@@ -1510,7 +1504,7 @@
     property @Deprecated public final float[] tickFractions;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
     ctor public SliderState(optional float value, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
     method public void dispatchRawDelta(float delta);
     method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -1518,6 +1512,7 @@
     method public int getSteps();
     method public float getValue();
     method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
+    method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
     method public void setValue(float);
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
     property public final int steps;
@@ -1614,26 +1609,24 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalAbsoluteTonalElevation;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissBoxDefaults {
+  public final class SwipeToDismissBoxDefaults {
     method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getPositionalThreshold();
     property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> positionalThreshold;
     field public static final androidx.compose.material3.SwipeToDismissBoxDefaults INSTANCE;
   }
 
   public final class SwipeToDismissBoxKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.SwipeToDismissBoxState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.SwipeToDismissBoxValue> directions);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.SwipeToDismissBoxState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional boolean enableDismissFromStartToEnd, optional boolean enableDismissFromEndToStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SwipeToDismissBoxState rememberSwipeToDismissBoxState(optional androidx.compose.material3.SwipeToDismissBoxValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.SwipeToDismissBoxState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional boolean enableDismissFromStartToEnd, optional boolean enableDismissFromEndToStart, optional boolean gesturesEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static androidx.compose.material3.SwipeToDismissBoxState rememberSwipeToDismissBoxState(optional androidx.compose.material3.SwipeToDismissBoxValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissBoxState {
+  public final class SwipeToDismissBoxState {
     ctor public SwipeToDismissBoxState(androidx.compose.material3.SwipeToDismissBoxValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
     method public suspend Object? dismiss(androidx.compose.material3.SwipeToDismissBoxValue direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.SwipeToDismissBoxValue getCurrentValue();
     method public androidx.compose.material3.SwipeToDismissBoxValue getDismissDirection();
     method @FloatRange(from=0.0, to=1.0) public float getProgress();
     method public androidx.compose.material3.SwipeToDismissBoxValue getTargetValue();
-    method @Deprecated public boolean isDismissed(androidx.compose.material3.DismissDirection direction);
     method public float requireOffset();
     method public suspend Object? reset(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? snapTo(androidx.compose.material3.SwipeToDismissBoxValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -1648,7 +1641,7 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SwipeToDismissBoxState,androidx.compose.material3.SwipeToDismissBoxValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SwipeToDismissBoxValue {
+  public enum SwipeToDismissBoxValue {
     enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue EndToStart;
     enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue Settled;
     enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue StartToEnd;
@@ -1850,12 +1843,6 @@
   @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void ContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void FilledContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedBorderContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithLabel(optional float start, optional float end, optional float top, optional float bottom);
@@ -1870,11 +1857,7 @@
     method @Deprecated public float getUnfocusedBorderThickness();
     method public float getUnfocusedIndicatorThickness();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long containerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues outlinedTextFieldPadding(optional float start, optional float top, optional float end, optional float bottom);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long containerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues textFieldWithLabelPadding(optional float start, optional float end, optional float top, optional float bottom);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues textFieldWithoutLabelPadding(optional float start, optional float top, optional float end, optional float bottom);
     property @Deprecated public final float FocusedBorderThickness;
@@ -1890,9 +1873,7 @@
   }
 
   public final class TextFieldKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
@@ -2033,15 +2014,24 @@
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior exitUntilCollapsedScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
+    method public float getLargeAppBarCollapsedHeight();
+    method public float getLargeAppBarExpandedHeight();
+    method public float getMediumAppBarCollapsedHeight();
+    method public float getMediumAppBarExpandedHeight();
+    method public float getTopAppBarExpandedHeight();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors largeTopAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors largeTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors mediumTopAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors mediumTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior pinnedScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll);
-    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors smallTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors topAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors topAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
+    property public final float LargeAppBarCollapsedHeight;
+    property public final float LargeAppBarExpandedHeight;
+    property public final float MediumAppBarCollapsedHeight;
+    property public final float MediumAppBarExpandedHeight;
+    property public final float TopAppBarExpandedHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
     field public static final androidx.compose.material3.TopAppBarDefaults INSTANCE;
   }
@@ -2119,6 +2109,46 @@
 
 }
 
+package androidx.compose.material3.carousel {
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class CarouselDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior multiBrowseFlingBehavior(androidx.compose.material3.carousel.CarouselState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior noSnapFlingBehavior();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior singleAdvanceFlingBehavior(androidx.compose.material3.carousel.CarouselState state, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec);
+    field public static final androidx.compose.material3.carousel.CarouselDefaults INSTANCE;
+  }
+
+  public final class CarouselKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void HorizontalMultiBrowseCarousel(androidx.compose.material3.carousel.CarouselState state, float preferredItemWidth, optional androidx.compose.ui.Modifier modifier, optional float itemSpacing, optional androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior, optional float minSmallItemWidth, optional float maxSmallItemWidth, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function2<? super androidx.compose.material3.carousel.CarouselScope,? super java.lang.Integer,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void HorizontalUncontainedCarousel(androidx.compose.material3.carousel.CarouselState state, float itemWidth, optional androidx.compose.ui.Modifier modifier, optional float itemSpacing, optional androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function2<? super androidx.compose.material3.carousel.CarouselScope,? super java.lang.Integer,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public sealed interface CarouselScope {
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class CarouselState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor public CarouselState(optional int currentItem, optional @FloatRange(from=-0.5, to=0.5) float currentItemOffsetFraction, kotlin.jvm.functions.Function0<java.lang.Integer> itemCount);
+    method public float dispatchRawDelta(float delta);
+    method public androidx.compose.runtime.MutableState<kotlin.jvm.functions.Function0<java.lang.Integer>> getItemCountState();
+    method public boolean isScrollInProgress();
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void setItemCountState(androidx.compose.runtime.MutableState<kotlin.jvm.functions.Function0<java.lang.Integer>>);
+    property public boolean isScrollInProgress;
+    property public final androidx.compose.runtime.MutableState<kotlin.jvm.functions.Function0<java.lang.Integer>> itemCountState;
+    field public static final androidx.compose.material3.carousel.CarouselState.Companion Companion;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final class CarouselState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.carousel.CarouselState,?> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.compose.material3.carousel.CarouselState,?> Saver;
+  }
+
+  public final class CarouselStateKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.carousel.CarouselState rememberCarouselState(optional int initialItem, kotlin.jvm.functions.Function0<java.lang.Integer> itemCount);
+  }
+
+}
+
 package androidx.compose.material3.pulltorefresh {
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class PullToRefreshDefaults {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 7c294bb..097e597 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -36,11 +36,14 @@
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.BottomAppBarState BottomAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float collapsedHeight, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float collapsedHeight, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional float expandedHeight, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
   }
@@ -622,17 +625,6 @@
     property public abstract kotlin.ranges.IntRange yearRange;
   }
 
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection EndToStart;
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissDirection StartToEnd;
-  }
-
-  @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissValue {
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue Default;
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToEnd;
-    enum_constant @Deprecated public static final androidx.compose.material3.DismissValue DismissedToStart;
-  }
-
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DisplayMode {
     field public static final androidx.compose.material3.DisplayMode.Companion Companion;
   }
@@ -892,11 +884,11 @@
   }
 
   public final class InteractiveComponentSizeKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumTouchTargetEnforcement();
+    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalMinimumInteractiveComponentEnforcement();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalMinimumInteractiveComponentSize();
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
-    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
-    property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
+    property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumInteractiveComponentEnforcement;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalMinimumInteractiveComponentSize;
   }
 
   public final class LabelKt {
@@ -1009,7 +1001,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface MultiChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
+  public interface MultiChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
   }
 
   public final class NavigationBarDefaults {
@@ -1068,8 +1060,10 @@
   }
 
   public final class NavigationDrawerKt {
+    method @androidx.compose.runtime.Composable public static void DismissibleDrawerSheet(androidx.compose.material3.DrawerState drawerState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DismissibleDrawerSheet(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DismissibleNavigationDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void ModalDrawerSheet(androidx.compose.material3.DrawerState drawerState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void ModalDrawerSheet(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.Shape drawerShape, optional long drawerContainerColor, optional long drawerContentColor, optional float drawerTonalElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void ModalNavigationDrawer(kotlin.jvm.functions.Function0<kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DrawerState drawerState, optional boolean gesturesEnabled, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void NavigationDrawerItem(kotlin.jvm.functions.Function0<kotlin.Unit> label, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? badge, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.NavigationDrawerItemColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
@@ -1137,9 +1131,7 @@
   }
 
   public final class OutlinedTextFieldKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
@@ -1215,7 +1207,7 @@
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RangeSliderState {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class RangeSliderState {
     ctor public RangeSliderState(optional float activeRangeStart, optional float activeRangeEnd, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
     method public float getActiveRangeEnd();
     method public float getActiveRangeStart();
@@ -1224,6 +1216,7 @@
     method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
     method public void setActiveRangeEnd(float);
     method public void setActiveRangeStart(float);
+    method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
     property public final float activeRangeEnd;
     property public final float activeRangeStart;
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
@@ -1280,6 +1273,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SearchBarColors {
+    ctor public SearchBarColors(long containerColor, long dividerColor, androidx.compose.material3.TextFieldColors inputFieldColors);
     method public long getContainerColor();
     method public long getDividerColor();
     method public androidx.compose.material3.TextFieldColors getInputFieldColors();
@@ -1316,7 +1310,7 @@
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SearchBar(String query, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onQueryChange, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onSearch, boolean active, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onActiveChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SearchBarColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SegmentedButtonColors {
+  @androidx.compose.runtime.Immutable public final class SegmentedButtonColors {
     ctor public SegmentedButtonColors(long activeContainerColor, long activeContentColor, long activeBorderColor, long inactiveContainerColor, long inactiveContentColor, long inactiveBorderColor, long disabledActiveContainerColor, long disabledActiveContentColor, long disabledActiveBorderColor, long disabledInactiveContainerColor, long disabledInactiveContentColor, long disabledInactiveBorderColor);
     method public androidx.compose.material3.SegmentedButtonColors copy(optional long activeContainerColor, optional long activeContentColor, optional long activeBorderColor, optional long inactiveContainerColor, optional long inactiveContentColor, optional long inactiveBorderColor, optional long disabledActiveContainerColor, optional long disabledActiveContentColor, optional long disabledActiveBorderColor, optional long disabledInactiveContainerColor, optional long disabledInactiveContentColor, optional long disabledInactiveBorderColor);
     method public long getActiveBorderColor();
@@ -1345,7 +1339,7 @@
     property public final long inactiveContentColor;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SegmentedButtonDefaults {
+  @androidx.compose.runtime.Stable public final class SegmentedButtonDefaults {
     method @androidx.compose.runtime.Composable public void ActiveIcon();
     method @androidx.compose.runtime.Composable public void Icon(boolean active, optional kotlin.jvm.functions.Function0<kotlin.Unit> activeContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? inactiveContent);
     method public androidx.compose.foundation.BorderStroke borderStroke(long color, optional float width);
@@ -1362,10 +1356,10 @@
   }
 
   public final class SegmentedButtonKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MultiChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.MultiChoiceSegmentedButtonRowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.MultiChoiceSegmentedButtonRowScope, boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.SingleChoiceSegmentedButtonRowScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SingleChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SingleChoiceSegmentedButtonRowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void MultiChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.MultiChoiceSegmentedButtonRowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.MultiChoiceSegmentedButtonRowScope, boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
+    method @androidx.compose.runtime.Composable public static void SegmentedButton(androidx.compose.material3.SingleChoiceSegmentedButtonRowScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.SegmentedButtonColors colors, optional androidx.compose.foundation.BorderStroke border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> icon, kotlin.jvm.functions.Function0<kotlin.Unit> label);
+    method @androidx.compose.runtime.Composable public static void SingleChoiceSegmentedButtonRow(optional androidx.compose.ui.Modifier modifier, optional float space, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SingleChoiceSegmentedButtonRowScope,kotlin.Unit> content);
   }
 
   @androidx.compose.runtime.Immutable public final class SelectableChipColors {
@@ -1453,7 +1447,7 @@
     enum_constant public static final androidx.compose.material3.SheetValue PartiallyExpanded;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
+  public interface SingleChoiceSegmentedButtonRowScope extends androidx.compose.foundation.layout.RowScope {
   }
 
   @androidx.compose.runtime.Immutable public final class SliderColors {
@@ -1510,7 +1504,7 @@
     property @Deprecated public final float[] tickFractions;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SliderState implements androidx.compose.foundation.gestures.DraggableState {
     ctor public SliderState(optional float value, optional @IntRange(from=0L) int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange);
     method public void dispatchRawDelta(float delta);
     method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -1518,6 +1512,7 @@
     method public int getSteps();
     method public float getValue();
     method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getValueRange();
+    method public void setOnValueChangeFinished(kotlin.jvm.functions.Function0<kotlin.Unit>?);
     method public void setValue(float);
     property public final kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished;
     property public final int steps;
@@ -1614,26 +1609,24 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalAbsoluteTonalElevation;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissBoxDefaults {
+  public final class SwipeToDismissBoxDefaults {
     method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> getPositionalThreshold();
     property @androidx.compose.runtime.Composable public final kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Float> positionalThreshold;
     field public static final androidx.compose.material3.SwipeToDismissBoxDefaults INSTANCE;
   }
 
   public final class SwipeToDismissBoxKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismiss(androidx.compose.material3.SwipeToDismissBoxState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> background, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissContent, optional androidx.compose.ui.Modifier modifier, optional java.util.Set<? extends androidx.compose.material3.SwipeToDismissBoxValue> directions);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.SwipeToDismissBoxState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional boolean enableDismissFromStartToEnd, optional boolean enableDismissFromEndToStart, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SwipeToDismissBoxState rememberSwipeToDismissBoxState(optional androidx.compose.material3.SwipeToDismissBoxValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
+    method @androidx.compose.runtime.Composable public static void SwipeToDismissBox(androidx.compose.material3.SwipeToDismissBoxState state, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> backgroundContent, optional androidx.compose.ui.Modifier modifier, optional boolean enableDismissFromStartToEnd, optional boolean enableDismissFromEndToStart, optional boolean gesturesEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static androidx.compose.material3.SwipeToDismissBoxState rememberSwipeToDismissBoxState(optional androidx.compose.material3.SwipeToDismissBoxValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, optional kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class SwipeToDismissBoxState {
+  public final class SwipeToDismissBoxState {
     ctor public SwipeToDismissBoxState(androidx.compose.material3.SwipeToDismissBoxValue initialValue, androidx.compose.ui.unit.Density density, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold);
     method public suspend Object? dismiss(androidx.compose.material3.SwipeToDismissBoxValue direction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.compose.material3.SwipeToDismissBoxValue getCurrentValue();
     method public androidx.compose.material3.SwipeToDismissBoxValue getDismissDirection();
     method @FloatRange(from=0.0, to=1.0) public float getProgress();
     method public androidx.compose.material3.SwipeToDismissBoxValue getTargetValue();
-    method @Deprecated public boolean isDismissed(androidx.compose.material3.DismissDirection direction);
     method public float requireOffset();
     method public suspend Object? reset(kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? snapTo(androidx.compose.material3.SwipeToDismissBoxValue targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
@@ -1648,7 +1641,7 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SwipeToDismissBoxState,androidx.compose.material3.SwipeToDismissBoxValue> Saver(kotlin.jvm.functions.Function1<? super androidx.compose.material3.SwipeToDismissBoxValue,java.lang.Boolean> confirmValueChange, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> positionalThreshold, androidx.compose.ui.unit.Density density);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public enum SwipeToDismissBoxValue {
+  public enum SwipeToDismissBoxValue {
     enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue EndToStart;
     enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue Settled;
     enum_constant public static final androidx.compose.material3.SwipeToDismissBoxValue StartToEnd;
@@ -1850,12 +1843,6 @@
   @androidx.compose.runtime.Immutable public final class TextFieldDefaults {
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void ContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void DecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void FilledContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedBorderContainerBox(boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional androidx.compose.ui.graphics.Shape shape, optional float focusedBorderThickness, optional float unfocusedBorderThickness);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void OutlinedTextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TextFieldDecorationBox(String value, kotlin.jvm.functions.Function0<kotlin.Unit> innerTextField, boolean enabled, boolean singleLine, androidx.compose.ui.text.input.VisualTransformation visualTransformation, androidx.compose.foundation.interaction.InteractionSource interactionSource, optional boolean isError, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function0<kotlin.Unit> container);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors colors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long focusedContainerColor, optional long unfocusedContainerColor, optional long disabledContainerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors? selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method public androidx.compose.foundation.layout.PaddingValues contentPaddingWithLabel(optional float start, optional float end, optional float top, optional float bottom);
@@ -1870,11 +1857,7 @@
     method @Deprecated public float getUnfocusedBorderThickness();
     method public float getUnfocusedIndicatorThickness();
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public androidx.compose.ui.Modifier indicatorLine(androidx.compose.ui.Modifier, boolean enabled, boolean isError, androidx.compose.foundation.interaction.InteractionSource interactionSource, androidx.compose.material3.TextFieldColors colors, optional float focusedIndicatorLineThickness, optional float unfocusedIndicatorLineThickness);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long containerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues outlinedTextFieldPadding(optional float start, optional float top, optional float end, optional float bottom);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long errorTextColor, optional long containerColor, optional long errorContainerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor, optional long errorPlaceholderColor, optional long focusedSupportingTextColor, optional long unfocusedSupportingTextColor, optional long disabledSupportingTextColor, optional long errorSupportingTextColor, optional long focusedPrefixColor, optional long unfocusedPrefixColor, optional long disabledPrefixColor, optional long errorPrefixColor, optional long focusedSuffixColor, optional long unfocusedSuffixColor, optional long disabledSuffixColor, optional long errorSuffixColor);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues textFieldWithLabelPadding(optional float start, optional float end, optional float top, optional float bottom);
     method @Deprecated public androidx.compose.foundation.layout.PaddingValues textFieldWithoutLabelPadding(optional float start, optional float top, optional float end, optional float bottom);
     property @Deprecated public final float FocusedBorderThickness;
@@ -1890,9 +1873,7 @@
   }
 
   public final class TextFieldKt {
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void TextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
-    method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
     method @androidx.compose.runtime.Composable public static void TextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
@@ -2033,15 +2014,24 @@
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors centerAlignedTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior enterAlwaysScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior exitUntilCollapsedScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
+    method public float getLargeAppBarCollapsedHeight();
+    method public float getLargeAppBarExpandedHeight();
+    method public float getMediumAppBarCollapsedHeight();
+    method public float getMediumAppBarExpandedHeight();
+    method public float getTopAppBarExpandedHeight();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors largeTopAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors largeTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors mediumTopAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors mediumTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarScrollBehavior pinnedScrollBehavior(optional androidx.compose.material3.TopAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll);
-    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors smallTopAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors topAppBarColors();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TopAppBarColors topAppBarColors(optional long containerColor, optional long scrolledContainerColor, optional long navigationIconContentColor, optional long titleContentColor, optional long actionIconContentColor);
+    property public final float LargeAppBarCollapsedHeight;
+    property public final float LargeAppBarExpandedHeight;
+    property public final float MediumAppBarCollapsedHeight;
+    property public final float MediumAppBarExpandedHeight;
+    property public final float TopAppBarExpandedHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
     field public static final androidx.compose.material3.TopAppBarDefaults INSTANCE;
   }
@@ -2119,6 +2109,46 @@
 
 }
 
+package androidx.compose.material3.carousel {
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class CarouselDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior multiBrowseFlingBehavior(androidx.compose.material3.carousel.CarouselState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior noSnapFlingBehavior();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.TargetedFlingBehavior singleAdvanceFlingBehavior(androidx.compose.material3.carousel.CarouselState state, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec);
+    field public static final androidx.compose.material3.carousel.CarouselDefaults INSTANCE;
+  }
+
+  public final class CarouselKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void HorizontalMultiBrowseCarousel(androidx.compose.material3.carousel.CarouselState state, float preferredItemWidth, optional androidx.compose.ui.Modifier modifier, optional float itemSpacing, optional androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior, optional float minSmallItemWidth, optional float maxSmallItemWidth, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function2<? super androidx.compose.material3.carousel.CarouselScope,? super java.lang.Integer,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void HorizontalUncontainedCarousel(androidx.compose.material3.carousel.CarouselState state, float itemWidth, optional androidx.compose.ui.Modifier modifier, optional float itemSpacing, optional androidx.compose.foundation.gestures.TargetedFlingBehavior flingBehavior, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function2<? super androidx.compose.material3.carousel.CarouselScope,? super java.lang.Integer,kotlin.Unit> content);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public sealed interface CarouselScope {
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class CarouselState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor public CarouselState(optional int currentItem, optional @FloatRange(from=-0.5, to=0.5) float currentItemOffsetFraction, kotlin.jvm.functions.Function0<java.lang.Integer> itemCount);
+    method public float dispatchRawDelta(float delta);
+    method public androidx.compose.runtime.MutableState<kotlin.jvm.functions.Function0<java.lang.Integer>> getItemCountState();
+    method public boolean isScrollInProgress();
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public void setItemCountState(androidx.compose.runtime.MutableState<kotlin.jvm.functions.Function0<java.lang.Integer>>);
+    property public boolean isScrollInProgress;
+    property public final androidx.compose.runtime.MutableState<kotlin.jvm.functions.Function0<java.lang.Integer>> itemCountState;
+    field public static final androidx.compose.material3.carousel.CarouselState.Companion Companion;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final class CarouselState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.carousel.CarouselState,?> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.compose.material3.carousel.CarouselState,?> Saver;
+  }
+
+  public final class CarouselStateKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.carousel.CarouselState rememberCarouselState(optional int initialItem, kotlin.jvm.functions.Function0<java.lang.Integer> itemCount);
+  }
+
+}
+
 package androidx.compose.material3.pulltorefresh {
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class PullToRefreshDefaults {
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
index 814b0841..af81870 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -444,7 +444,7 @@
     BottomSheets,
     Buttons,
     Card,
-    // Carousel, // TODO: Re-enable when ready
+    Carousel,
     Checkboxes,
     Chips,
     DatePickers,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index e7b564a..037d0dbf 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -39,7 +39,6 @@
 import androidx.compose.material3.samples.ButtonSample
 import androidx.compose.material3.samples.ButtonWithIconSample
 import androidx.compose.material3.samples.CardSample
-import androidx.compose.material3.samples.CarouselSample
 import androidx.compose.material3.samples.CheckboxSample
 import androidx.compose.material3.samples.CheckboxWithTextSample
 import androidx.compose.material3.samples.ChipGroupReflowSample
@@ -79,6 +78,8 @@
 import androidx.compose.material3.samples.FilterChipSample
 import androidx.compose.material3.samples.FilterChipWithLeadingIconSample
 import androidx.compose.material3.samples.FloatingActionButtonSample
+import androidx.compose.material3.samples.HorizontalMultiBrowseCarouselSample
+import androidx.compose.material3.samples.HorizontalUncontainedCarouselSample
 import androidx.compose.material3.samples.IconButtonSample
 import androidx.compose.material3.samples.IconToggleButtonSample
 import androidx.compose.material3.samples.IndeterminateCircularProgressIndicatorSample
@@ -326,11 +327,18 @@
 private const val CarouselExampleSourceUrl = "$SampleSourceUrl/CarouselSamples.kt"
 val CarouselExamples = listOf(
     Example(
-        name = ::CarouselSample.name,
+        name = ::HorizontalMultiBrowseCarouselSample.name,
         description = CarouselExampleDescription,
         sourceUrl = CarouselExampleSourceUrl
     ) {
-        CarouselSample()
+        HorizontalMultiBrowseCarouselSample()
+    },
+    Example(
+        name = ::HorizontalUncontainedCarouselSample.name,
+        description = CarouselExampleDescription,
+        sourceUrl = CarouselExampleSourceUrl
+    ) {
+        HorizontalUncontainedCarouselSample()
     }
 )
 
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
index cecf379..93625ef 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
@@ -28,7 +28,6 @@
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material.icons.filled.Done
 import androidx.compose.material3.Card
-import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.SwipeToDismissBox
@@ -70,7 +69,6 @@
 )
 
 @Composable
-@OptIn(ExperimentalMaterial3Api::class)
 fun SwipeToDismissDemo() {
     // This is an example of a list of dismissible items, similar to what you would see in an
     // email app. Swiping left reveals a 'delete' icon and swiping right reveals a 'done' icon.
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt
index a55a9bd..5410945 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt
@@ -17,16 +17,18 @@
 package androidx.compose.material3.samples
 
 import androidx.annotation.DrawableRes
+import androidx.annotation.Sampled
 import androidx.annotation.StringRes
 import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.itemsIndexed
-import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel
+import androidx.compose.material3.carousel.HorizontalUncontainedCarousel
+import androidx.compose.material3.carousel.rememberCarouselState
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
@@ -35,16 +37,19 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Preview
+@Sampled
 @Composable
-fun CarouselSample() {
+fun HorizontalMultiBrowseCarouselSample() {
+
     data class CarouselItem(
         val id: Int,
         @DrawableRes val imageResId: Int,
         @StringRes val contentDescriptionResId: Int
     )
 
-    val Items = listOf(
+    val items = listOf(
         CarouselItem(0, R.drawable.carousel_image_1, R.string.carousel_image_1_description),
         CarouselItem(1, R.drawable.carousel_image_2, R.string.carousel_image_2_description),
         CarouselItem(2, R.drawable.carousel_image_3, R.string.carousel_image_3_description),
@@ -52,23 +57,69 @@
         CarouselItem(4, R.drawable.carousel_image_5, R.string.carousel_image_5_description),
     )
 
-    LazyRow(
-        modifier = Modifier.fillMaxWidth(),
-        state = rememberLazyListState()
-    ) {
-        itemsIndexed(Items) { _, item ->
-            Card(
-                modifier = Modifier
-                    .width(350.dp)
-                    .height(200.dp),
-            ) {
-                Image(
-                    painter = painterResource(id = item.imageResId),
-                    contentDescription = stringResource(item.contentDescriptionResId),
-                    modifier = Modifier.fillMaxSize(),
-                    contentScale = ContentScale.Crop
-                )
-            }
+    HorizontalMultiBrowseCarousel(
+        state = rememberCarouselState { items.count() },
+        modifier = Modifier
+            .width(412.dp)
+            .height(221.dp),
+        preferredItemWidth = 186.dp,
+        itemSpacing = 8.dp,
+        contentPadding = PaddingValues(horizontal = 16.dp)
+    ) { i ->
+        val item = items[i]
+        Card(
+            modifier = Modifier
+                .height(205.dp)
+        ) {
+            Image(
+                painter = painterResource(id = item.imageResId),
+                contentDescription = stringResource(item.contentDescriptionResId),
+                modifier = Modifier.fillMaxSize(),
+                contentScale = ContentScale.Crop
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun HorizontalUncontainedCarouselSample() {
+
+    data class CarouselItem(
+        val id: Int,
+        @DrawableRes val imageResId: Int,
+        @StringRes val contentDescriptionResId: Int
+    )
+
+    val items = listOf(
+        CarouselItem(0, R.drawable.carousel_image_1, R.string.carousel_image_1_description),
+        CarouselItem(1, R.drawable.carousel_image_2, R.string.carousel_image_2_description),
+        CarouselItem(2, R.drawable.carousel_image_3, R.string.carousel_image_3_description),
+        CarouselItem(3, R.drawable.carousel_image_4, R.string.carousel_image_4_description),
+        CarouselItem(4, R.drawable.carousel_image_5, R.string.carousel_image_5_description),
+    )
+    HorizontalUncontainedCarousel(
+        state = rememberCarouselState { items.count() },
+        modifier = Modifier
+            .width(412.dp)
+            .height(221.dp),
+        itemWidth = 186.dp,
+        itemSpacing = 8.dp,
+        contentPadding = PaddingValues(horizontal = 16.dp)
+    ) { i ->
+        val item = items[i]
+        Card(
+            modifier = Modifier
+                .height(205.dp)
+        ) {
+            Image(
+                painter = painterResource(id = item.imageResId),
+                contentDescription = stringResource(item.contentDescriptionResId),
+                modifier = Modifier.fillMaxSize(),
+                contentScale = ContentScale.Crop
+            )
         }
     }
 }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt
index f21529e..6d07a51 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DrawerSamples.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.material3.samples
 
-import androidx.activity.compose.BackHandler
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
@@ -24,10 +23,27 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.AccountCircle
+import androidx.compose.material.icons.filled.Bookmarks
+import androidx.compose.material.icons.filled.CalendarMonth
+import androidx.compose.material.icons.filled.Dashboard
 import androidx.compose.material.icons.filled.Email
-import androidx.compose.material.icons.filled.Face
 import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Group
+import androidx.compose.material.icons.filled.Headphones
+import androidx.compose.material.icons.filled.Image
+import androidx.compose.material.icons.filled.JoinFull
+import androidx.compose.material.icons.filled.Keyboard
+import androidx.compose.material.icons.filled.Laptop
+import androidx.compose.material.icons.filled.Map
+import androidx.compose.material.icons.filled.Navigation
+import androidx.compose.material.icons.filled.Outbox
+import androidx.compose.material.icons.filled.PushPin
+import androidx.compose.material.icons.filled.QrCode
+import androidx.compose.material.icons.filled.Radio
 import androidx.compose.material3.Button
 import androidx.compose.material3.DismissibleDrawerSheet
 import androidx.compose.material3.DismissibleNavigationDrawer
@@ -58,24 +74,45 @@
     val drawerState = rememberDrawerState(DrawerValue.Closed)
     val scope = rememberCoroutineScope()
     // icons to mimic drawer destinations
-    val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
+    val items = listOf(
+        Icons.Default.AccountCircle,
+        Icons.Default.Bookmarks,
+        Icons.Default.CalendarMonth,
+        Icons.Default.Dashboard,
+        Icons.Default.Email,
+        Icons.Default.Favorite,
+        Icons.Default.Group,
+        Icons.Default.Headphones,
+        Icons.Default.Image,
+        Icons.Default.JoinFull,
+        Icons.Default.Keyboard,
+        Icons.Default.Laptop,
+        Icons.Default.Map,
+        Icons.Default.Navigation,
+        Icons.Default.Outbox,
+        Icons.Default.PushPin,
+        Icons.Default.QrCode,
+        Icons.Default.Radio,
+    )
     val selectedItem = remember { mutableStateOf(items[0]) }
     ModalNavigationDrawer(
         drawerState = drawerState,
         drawerContent = {
-            ModalDrawerSheet {
-                Spacer(Modifier.height(12.dp))
-                items.forEach { item ->
-                    NavigationDrawerItem(
-                        icon = { Icon(item, contentDescription = null) },
-                        label = { Text(item.name) },
-                        selected = item == selectedItem.value,
-                        onClick = {
-                            scope.launch { drawerState.close() }
-                            selectedItem.value = item
-                        },
-                        modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
-                    )
+            ModalDrawerSheet(drawerState) {
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    Spacer(Modifier.height(12.dp))
+                    items.forEach { item ->
+                        NavigationDrawerItem(
+                            icon = { Icon(item, contentDescription = null) },
+                            label = { Text(item.name) },
+                            selected = item == selectedItem.value,
+                            onClick = {
+                                scope.launch { drawerState.close() }
+                                selectedItem.value = item
+                            },
+                            modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
+                        )
+                    }
                 }
             }
         },
@@ -101,22 +138,43 @@
 @Composable
 fun PermanentNavigationDrawerSample() {
     // icons to mimic drawer destinations
-    val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
+    val items = listOf(
+        Icons.Default.AccountCircle,
+        Icons.Default.Bookmarks,
+        Icons.Default.CalendarMonth,
+        Icons.Default.Dashboard,
+        Icons.Default.Email,
+        Icons.Default.Favorite,
+        Icons.Default.Group,
+        Icons.Default.Headphones,
+        Icons.Default.Image,
+        Icons.Default.JoinFull,
+        Icons.Default.Keyboard,
+        Icons.Default.Laptop,
+        Icons.Default.Map,
+        Icons.Default.Navigation,
+        Icons.Default.Outbox,
+        Icons.Default.PushPin,
+        Icons.Default.QrCode,
+        Icons.Default.Radio,
+    )
     val selectedItem = remember { mutableStateOf(items[0]) }
     PermanentNavigationDrawer(
         drawerContent = {
             PermanentDrawerSheet(Modifier.width(240.dp)) {
-                Spacer(Modifier.height(12.dp))
-                items.forEach { item ->
-                    NavigationDrawerItem(
-                        icon = { Icon(item, contentDescription = null) },
-                        label = { Text(item.name) },
-                        selected = item == selectedItem.value,
-                        onClick = {
-                            selectedItem.value = item
-                        },
-                        modifier = Modifier.padding(horizontal = 12.dp)
-                    )
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    Spacer(Modifier.height(12.dp))
+                    items.forEach { item ->
+                        NavigationDrawerItem(
+                            icon = { Icon(item, contentDescription = null) },
+                            label = { Text(item.name) },
+                            selected = item == selectedItem.value,
+                            onClick = {
+                                selectedItem.value = item
+                            },
+                            modifier = Modifier.padding(horizontal = 12.dp)
+                        )
+                    }
                 }
             }
         },
@@ -140,30 +198,46 @@
     val drawerState = rememberDrawerState(DrawerValue.Closed)
     val scope = rememberCoroutineScope()
     // icons to mimic drawer destinations
-    val items = listOf(Icons.Default.Favorite, Icons.Default.Face, Icons.Default.Email)
+    val items = listOf(
+        Icons.Default.AccountCircle,
+        Icons.Default.Bookmarks,
+        Icons.Default.CalendarMonth,
+        Icons.Default.Dashboard,
+        Icons.Default.Email,
+        Icons.Default.Favorite,
+        Icons.Default.Group,
+        Icons.Default.Headphones,
+        Icons.Default.Image,
+        Icons.Default.JoinFull,
+        Icons.Default.Keyboard,
+        Icons.Default.Laptop,
+        Icons.Default.Map,
+        Icons.Default.Navigation,
+        Icons.Default.Outbox,
+        Icons.Default.PushPin,
+        Icons.Default.QrCode,
+        Icons.Default.Radio,
+    )
     val selectedItem = remember { mutableStateOf(items[0]) }
-    BackHandler(enabled = drawerState.isOpen) {
-        scope.launch {
-            drawerState.close()
-        }
-    }
 
     DismissibleNavigationDrawer(
         drawerState = drawerState,
         drawerContent = {
-            DismissibleDrawerSheet {
-                Spacer(Modifier.height(12.dp))
-                items.forEach { item ->
-                    NavigationDrawerItem(
-                        icon = { Icon(item, contentDescription = null) },
-                        label = { Text(item.name) },
-                        selected = item == selectedItem.value,
-                        onClick = {
-                            scope.launch { drawerState.close() }
-                            selectedItem.value = item
-                        },
-                        modifier = Modifier.padding(horizontal = 12.dp)
-                    )
+            DismissibleDrawerSheet(drawerState) {
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    Spacer(Modifier.height(12.dp))
+                    items.forEach { item ->
+                        NavigationDrawerItem(
+                            icon = { Icon(item, contentDescription = null) },
+                            label = { Text(item.name) },
+                            selected = item == selectedItem.value,
+                            onClick = {
+                                scope.launch { drawerState.close() }
+                                selectedItem.value = item
+                            },
+                            modifier = Modifier.padding(horizontal = 12.dp)
+                        )
+                    }
                 }
             }
         },
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt
index ebd1a75..da7d53d 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SearchBarSamples.kt
@@ -33,6 +33,7 @@
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
 import androidx.compose.material3.SearchBar
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -42,6 +43,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.traversalIndex
@@ -56,7 +58,11 @@
     var text by rememberSaveable { mutableStateOf("") }
     var active by rememberSaveable { mutableStateOf(false) }
 
-    Box(Modifier.fillMaxSize().semantics { isTraversalGroup = true }) {
+    Box(
+        Modifier
+            .fillMaxSize()
+            .semantics { isTraversalGroup = true }
+    ) {
         SearchBar(
             modifier = Modifier
                 .align(Alignment.TopCenter)
@@ -78,6 +84,7 @@
                     headlineContent = { Text(resultText) },
                     supportingContent = { Text("Additional info") },
                     leadingContent = { Icon(Icons.Filled.Star, contentDescription = null) },
+                    colors = ListItemDefaults.colors(containerColor = Color.Transparent),
                     modifier = Modifier
                         .clickable {
                             text = resultText
@@ -95,7 +102,12 @@
         ) {
             val list = List(100) { "Text $it" }
             items(count = list.size) {
-                Text(list[it], Modifier.fillMaxWidth().padding(horizontal = 16.dp))
+                Text(
+                    text = list[it],
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(horizontal = 16.dp),
+                )
             }
         }
     }
@@ -109,7 +121,11 @@
     var text by rememberSaveable { mutableStateOf("") }
     var active by rememberSaveable { mutableStateOf(false) }
 
-    Box(Modifier.fillMaxSize().semantics { isTraversalGroup = true }) {
+    Box(
+        Modifier
+            .fillMaxSize()
+            .semantics { isTraversalGroup = true }
+    ) {
         DockedSearchBar(
             modifier = Modifier
                 .align(Alignment.TopCenter)
@@ -130,6 +146,7 @@
                     headlineContent = { Text(resultText) },
                     supportingContent = { Text("Additional info") },
                     leadingContent = { Icon(Icons.Filled.Star, contentDescription = null) },
+                    colors = ListItemDefaults.colors(containerColor = Color.Transparent),
                     modifier = Modifier
                         .clickable {
                             text = resultText
@@ -147,7 +164,12 @@
         ) {
             val list = List(100) { "Text $it" }
             items(count = list.size) {
-                Text(list[it], Modifier.fillMaxWidth().padding(horizontal = 16.dp))
+                Text(
+                    text = list[it],
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(horizontal = 16.dp),
+                )
             }
         }
     }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt
index eb57d09..2de7fe8 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SegmentedButtonSamples.kt
@@ -23,7 +23,6 @@
 import androidx.compose.material.icons.automirrored.filled.TrendingUp
 import androidx.compose.material.icons.filled.BookmarkBorder
 import androidx.compose.material.icons.filled.StarBorder
-import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MultiChoiceSegmentedButtonRow
 import androidx.compose.material3.SegmentedButton
 import androidx.compose.material3.SegmentedButtonDefaults
@@ -38,7 +37,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Sampled
 @Composable
 @Preview
@@ -58,7 +56,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Sampled
 @Composable
 @Preview
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
index fc063f8..7235969 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SwipeToDismissSamples.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.OutlinedCard
 import androidx.compose.material3.SwipeToDismissBox
@@ -38,7 +37,6 @@
 @Preview
 @Sampled
 @Composable
-@OptIn(ExperimentalMaterial3Api::class)
 fun SwipeToDismissListItems() {
     val dismissState = rememberSwipeToDismissBoxState()
     SwipeToDismissBox(
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
index e5f979ba..a479779 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -275,6 +275,28 @@
     }
 
     @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun smallTopAppBar_customHeight() {
+        lateinit var scrollBehavior: TopAppBarScrollBehavior
+        val expandedHeightDp = 50.dp
+        var expandedHeightDpPx = 0f
+
+        rule.setMaterialContent(lightColorScheme()) {
+            scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+            expandedHeightDpPx = with(LocalDensity.current) { expandedHeightDp.toPx() }
+            TopAppBar(
+                title = { Text("Title", Modifier.testTag(TitleTestTag)) },
+                modifier = Modifier.testTag(TopAppBarTestTag),
+                expandedHeight = expandedHeightDp,
+                scrollBehavior = scrollBehavior
+            )
+        }
+
+        assertThat(scrollBehavior.state.heightOffsetLimit).isEqualTo(-expandedHeightDpPx)
+        rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(expandedHeightDp)
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun smallTopAppBar_transparentContainerColor() {
@@ -368,7 +390,7 @@
             .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding + padding)
             // Navigation icon should be centered within the height of the app bar.
             .assertTopPositionInRootIsEqualTo(
-                appBarBottomEdgeY - AppBarTopAndBottomPadding - padding - FakeIconSize
+                appBarBottomEdgeY - DefaultAppBarTopAndBottomPadding - padding - FakeIconSize
             )
     }
 
@@ -598,7 +620,38 @@
 
         // The bottom text baseline should be 24.dp from the bottom of the app bar.
         assertMediumOrLargeDefaultPositioning(
-            expectedAppBarHeight = TopAppBarMediumTokens.ContainerHeight,
+            appBarCollapsedHeight = TopAppBarSmallTokens.ContainerHeight,
+            appBarExpandedHeight = TopAppBarMediumTokens.ContainerHeight,
+            bottomTextPadding = 24.dp
+        )
+    }
+
+    @Test
+    fun mediumTopAppBar_customHeight_expanded_positioning() {
+        val collapsedHeightDp = 36.dp
+        val expandedHeightDp = 112.dp
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                MediumTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    collapsedHeight = collapsedHeightDp,
+                    expandedHeight = expandedHeightDp
+                )
+            }
+        }
+
+        // The bottom text baseline should be 24.dp from the bottom of the app bar.
+        assertMediumOrLargeDefaultPositioning(
+            appBarCollapsedHeight = collapsedHeightDp,
+            appBarExpandedHeight = expandedHeightDp,
             bottomTextPadding = 24.dp
         )
     }
@@ -631,6 +684,38 @@
         )
     }
 
+    @Test
+    fun mediumTopAppBar_customHeight_scrolled_positioning() {
+        val collapsedHeightDp = 40.dp
+        val expandedHeightDp = 120.dp
+        val windowInsets = WindowInsets(13.dp, 13.dp, 13.dp, 13.dp)
+        val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                MediumTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    collapsedHeight = collapsedHeightDp,
+                    expandedHeight = expandedHeightDp,
+                    windowInsets = windowInsets,
+                    scrollBehavior = scrollBehavior,
+                )
+            }
+        }
+        assertMediumOrLargeScrolledHeight(
+            appBarMaxHeight = expandedHeightDp,
+            appBarMinHeight = collapsedHeightDp,
+            windowInsets,
+            content
+        )
+    }
+
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun mediumTopAppBar_scrolledContainerColor() {
@@ -756,7 +841,38 @@
 
         // The bottom text baseline should be 28.dp from the bottom of the app bar.
         assertMediumOrLargeDefaultPositioning(
-            expectedAppBarHeight = TopAppBarLargeTokens.ContainerHeight,
+            appBarCollapsedHeight = TopAppBarSmallTokens.ContainerHeight,
+            appBarExpandedHeight = TopAppBarLargeTokens.ContainerHeight,
+            bottomTextPadding = 28.dp
+        )
+    }
+
+    @Test
+    fun largeTopAppBar_customHeight_expanded_positioning() {
+        val collapsedHeightDp = 30.dp
+        val expandedHeightDp = 130.dp
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                LargeTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    collapsedHeight = collapsedHeightDp,
+                    expandedHeight = expandedHeightDp
+                )
+            }
+        }
+
+        // The bottom text baseline should be 28.dp from the bottom of the app bar.
+        assertMediumOrLargeDefaultPositioning(
+            appBarCollapsedHeight = collapsedHeightDp,
+            appBarExpandedHeight = expandedHeightDp,
             bottomTextPadding = 28.dp
         )
     }
@@ -789,6 +905,38 @@
         )
     }
 
+    @Test
+    fun largeTopAppBar_customHeight_scrolled_positioning() {
+        val collapsedHeightDp = 30.dp
+        val expandedHeightDp = 130.dp
+        val windowInsets = WindowInsets(4.dp, 4.dp, 4.dp, 4.dp)
+        val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                LargeTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    collapsedHeight = collapsedHeightDp,
+                    expandedHeight = expandedHeightDp,
+                    windowInsets = windowInsets,
+                    scrollBehavior = scrollBehavior
+                )
+            }
+        }
+        assertMediumOrLargeScrolledHeight(
+            appBarMaxHeight = expandedHeightDp,
+            appBarMinHeight = collapsedHeightDp,
+            windowInsets,
+            content
+        )
+    }
+
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun largeTopAppBar_scrolledContainerColor() {
@@ -1407,7 +1555,7 @@
             .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
             // Navigation icon should be centered within the height of the app bar.
             .assertTopPositionInRootIsEqualTo(
-                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+                appBarBottomEdgeY - DefaultAppBarTopAndBottomPadding - FakeIconSize
             )
 
         val titleNode = rule.onNodeWithTag(TitleTestTag)
@@ -1429,7 +1577,7 @@
             .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
             // Action should be 8.dp from the top
             .assertTopPositionInRootIsEqualTo(
-                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+                appBarBottomEdgeY - DefaultAppBarTopAndBottomPadding - FakeIconSize
             )
     }
 
@@ -1438,11 +1586,12 @@
      * [LargeTopAppBar].
      */
     private fun assertMediumOrLargeDefaultPositioning(
-        expectedAppBarHeight: Dp,
+        appBarCollapsedHeight: Dp,
+        appBarExpandedHeight: Dp,
         bottomTextPadding: Dp
     ) {
         val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
-        appBarBounds.height.assertIsEqualTo(expectedAppBarHeight, "top app bar height")
+        appBarBounds.height.assertIsEqualTo(appBarExpandedHeight, "top app bar height")
 
         // Expecting the title composable to be reused for the top and bottom rows of the top app
         // bar, so obtaining the node with the title tag should return two nodes, one for each row.
@@ -1453,15 +1602,16 @@
 
         val topTitleBounds = topTitleNode.getUnclippedBoundsInRoot()
         val bottomTitleBounds = bottomTitleNode.getUnclippedBoundsInRoot()
-        val topAppBarBottomEdgeY = appBarBounds.top + TopAppBarSmallTokens.ContainerHeight
+        val topAppBarBottomEdgeY = appBarBounds.top + appBarCollapsedHeight
         val bottomAppBarBottomEdgeY = appBarBounds.top + appBarBounds.height
 
+        val topAndBottomPadding = (appBarCollapsedHeight - FakeIconSize) / 2
         rule.onNodeWithTag(NavigationIconTestTag)
             // Navigation icon should be 4.dp from the start
             .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
             // Navigation icon should be centered within the height of the top part of the app bar.
             .assertTopPositionInRootIsEqualTo(
-                topAppBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+                topAppBarBottomEdgeY - topAndBottomPadding - FakeIconSize
             )
 
         rule.onNodeWithTag(ActionsTestTag)
@@ -1469,7 +1619,7 @@
             .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
             // Action should be 8.dp from the top
             .assertTopPositionInRootIsEqualTo(
-                topAppBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+                topAppBarBottomEdgeY - topAndBottomPadding - FakeIconSize
             )
 
         topTitleNode
@@ -1726,7 +1876,12 @@
 
     private val FakeIconSize = 48.dp
     private val AppBarStartAndEndPadding = 4.dp
-    private val AppBarTopAndBottomPadding =
+
+    /**
+     * Top and bottom padding for all the built-in app bars that have a top part (or only part) with
+     * a height of TopAppBarSmallTokens.ContainerHeight
+     */
+    private val DefaultAppBarTopAndBottomPadding =
         (TopAppBarSmallTokens.ContainerHeight - FakeIconSize) / 2
 
     private val LazyListTag = "lazyList"
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/CheckboxTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/CheckboxTest.kt
index ed984d5..5c6a6bf 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/CheckboxTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/CheckboxTest.kt
@@ -54,6 +54,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -124,12 +125,17 @@
 
         rule.setMaterialContent(lightColorScheme()) {
             val (checked, _) = remember { mutableStateOf(false) }
-            Box(Modifier.semantics(mergeDescendants = true) {}.testTag(parentTag)) {
+            Box(
+                Modifier
+                    .semantics(mergeDescendants = true) {}
+                    .testTag(parentTag)) {
                 Checkbox(
                     checked,
                     {},
                     enabled = false,
-                    modifier = Modifier.testTag(defaultTag).semantics { focused = true }
+                    modifier = Modifier
+                        .testTag(defaultTag)
+                        .semantics { focused = true }
                 )
             }
         }
@@ -146,7 +152,10 @@
     fun checkBoxTest_untoggleableAndMergeable_whenNullLambda() {
         rule.setMaterialContent(lightColorScheme()) {
             val (checked, _) = remember { mutableStateOf(false) }
-            Box(Modifier.semantics(mergeDescendants = true) {}.testTag(defaultTag)) {
+            Box(
+                Modifier
+                    .semantics(mergeDescendants = true) {}
+                    .testTag(defaultTag)) {
                 Checkbox(
                     checked,
                     null,
@@ -277,7 +286,8 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides minimumTouchTarget
+                    LocalMinimumInteractiveComponentSize provides
+                        if (minimumTouchTarget) 48.dp else Dp.Unspecified
                 ) {
                     TriStateCheckbox(
                         state = checkboxValue,
@@ -307,7 +317,10 @@
                 TriStateCheckbox(
                     state = state,
                     onClick = { state = On },
-                    modifier = Modifier.align(Alignment.Center).requiredSize(2.dp).testTag(tag)
+                    modifier = Modifier
+                        .align(Alignment.Center)
+                        .requiredSize(2.dp)
+                        .testTag(tag)
                 )
             }
         }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
index 925e620..53fe513 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
@@ -137,7 +137,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     SmallFloatingActionButton(onClick = {}) {
                         Icon(
@@ -162,12 +162,8 @@
     fun smallFabHasMinTouchTarget() {
         rule
             .setMaterialContentForSizeAssertions {
-                CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides true
-                ) {
-                    SmallFloatingActionButton(onClick = {}) {
-                        Icon(Icons.Filled.Favorite, null)
-                    }
+                SmallFloatingActionButton(onClick = {}) {
+                    Icon(Icons.Filled.Favorite, null)
                 }
             }
             // Expecting the size to be equal to the minimum touch target.
@@ -308,14 +304,18 @@
         rule.setMaterialContent(lightColorScheme()) {
             Column {
                 Spacer(
-                    Modifier.requiredSize(10.dp).weight(1f).onGloballyPositioned {
-                        item1Bounds = it.boundsInRoot()
-                    }
+                    Modifier
+                        .requiredSize(10.dp)
+                        .weight(1f)
+                        .onGloballyPositioned {
+                            item1Bounds = it.boundsInRoot()
+                        }
                 )
 
                 FloatingActionButton(
                     onClick = {},
-                    modifier = Modifier.weight(1f)
+                    modifier = Modifier
+                        .weight(1f)
                         .onGloballyPositioned {
                             buttonBounds = it.boundsInRoot()
                         }
@@ -323,7 +323,10 @@
                     Text("Button")
                 }
 
-                Spacer(Modifier.requiredSize(10.dp).weight(1f))
+                Spacer(
+                    Modifier
+                        .requiredSize(10.dp)
+                        .weight(1f))
             }
         }
 
@@ -344,7 +347,8 @@
                     }
                 ) {
                     Box(
-                        Modifier.size(2.dp)
+                        Modifier
+                            .size(2.dp)
                             .onGloballyPositioned { contentCoordinates = it }
                     )
                 }
@@ -375,7 +379,8 @@
                     modifier = Modifier.onGloballyPositioned { buttonCoordinates = it },
                 ) {
                     Box(
-                        Modifier.size(2.dp)
+                        Modifier
+                            .size(2.dp)
                             .onGloballyPositioned { contentCoordinates = it }
                     )
                 }
@@ -405,13 +410,15 @@
                 ExtendedFloatingActionButton(
                     text = {
                         Box(
-                            Modifier.size(2.dp)
+                            Modifier
+                                .size(2.dp)
                                 .onGloballyPositioned { textCoordinates = it }
                         )
                     },
                     icon = {
                         Box(
-                            Modifier.size(10.dp)
+                            Modifier
+                                .size(10.dp)
                                 .onGloballyPositioned { iconCoordinates = it }
                         )
                     },
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
index f93a36c..66e5938 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/IconButtonTest.kt
@@ -54,6 +54,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -91,7 +92,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     IconButton(onClick = { /* doSomething() */ }) {
                         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
@@ -105,6 +106,27 @@
     }
 
     @Test
+    fun iconButton_sizeWithCustomMinInteractiveComponentSize() {
+        val customTouchTargetSize = 44.dp
+        rule
+            .setMaterialContentForSizeAssertions {
+                CompositionLocalProvider(
+                    LocalMinimumInteractiveComponentSize provides customTouchTargetSize
+                ) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(
+                            Icons.Outlined.FavoriteBorder,
+                            contentDescription = "Localized description"
+                        )
+                    }
+                }
+            }
+            .assertWidthIsEqualTo(customTouchTargetSize)
+            .assertHeightIsEqualTo(customTouchTargetSize)
+            .assertTouchWidthIsEqualTo(customTouchTargetSize)
+            .assertTouchHeightIsEqualTo(customTouchTargetSize)
+    }
+    @Test
     fun iconButton_defaultSemantics() {
         rule.setMaterialContent(lightColorScheme()) {
             IconButton(onClick = { /* doSomething() */ }) {
@@ -236,7 +258,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     IconToggleButton(checked = true, onCheckedChange = { /* doSomething() */ }) {
                         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
@@ -367,7 +389,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     FilledIconButton(onClick = { /* doSomething() */ }) {
                         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
@@ -489,7 +511,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     FilledIconToggleButton(
                         checked = true,
@@ -595,7 +617,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     OutlinedIconButton(onClick = { /* doSomething() */ }) {
                         Icon(
@@ -699,7 +721,7 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides false
+                    LocalMinimumInteractiveComponentSize provides Dp.Unspecified
                 ) {
                     OutlinedIconToggleButton(
                         checked = true,
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt
index 06d8653..3abf32b 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerScreenshotTest.kt
@@ -19,11 +19,34 @@
 import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.AccountCircle
+import androidx.compose.material.icons.filled.Build
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.DateRange
+import androidx.compose.material.icons.filled.Email
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.Lock
+import androidx.compose.material.icons.filled.Notifications
+import androidx.compose.material.icons.filled.Place
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.filled.ShoppingCart
+import androidx.compose.material.icons.filled.ThumbUp
+import androidx.compose.material.icons.filled.Warning
+import androidx.compose.runtime.Composable
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
@@ -117,6 +140,167 @@
             .captureToImage()
             .assertAgainstGolden(screenshotRule, goldenName)
     }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress0AndSwipeEdgeLeft() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0f, swipeEdgeLeft = true)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress0AndSwipeEdgeLeft")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress25AndSwipeEdgeLeft() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0.25f, swipeEdgeLeft = true)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress25AndSwipeEdgeLeft")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress50AndSwipeEdgeLeft() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0.5f, swipeEdgeLeft = true)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress50AndSwipeEdgeLeft")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress75AndSwipeEdgeLeft() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0.75f, swipeEdgeLeft = true)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress75AndSwipeEdgeLeft")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress100AndSwipeEdgeLeft() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 1f, swipeEdgeLeft = true)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress100AndSwipeEdgeLeft")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress0AndSwipeEdgeRight() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0f, swipeEdgeLeft = false)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress0AndSwipeEdgeRight")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress25AndSwipeEdgeRight() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0.25f, swipeEdgeLeft = false)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress25AndSwipeEdgeRight")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress50AndSwipeEdgeRight() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0.5f, swipeEdgeLeft = false)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress50AndSwipeEdgeRight")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress75AndSwipeEdgeRight() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 0.75f, swipeEdgeLeft = false)
+        }
+        assertScreenshotAgainstGolden("navigationDrawer_predictiveBack_progress75AndSwipeEdgeRight")
+    }
+
+    @Test
+    fun predictiveBack_navigationDrawer_progress100AndSwipeEdgeRight() {
+        rule.setMaterialContent(lightColorScheme()) {
+            ModalNavigationDrawerPredictiveBack(progress = 1f, swipeEdgeLeft = false)
+        }
+        assertScreenshotAgainstGolden(
+            "navigationDrawer_predictiveBack_progress100AndSwipeEdgeRight"
+        )
+    }
 }
 
 private val ContainerTestTag = "container"
+
+private val items = listOf(
+    Icons.Default.AccountCircle,
+    Icons.Default.Build,
+    Icons.Default.Check,
+    Icons.Default.DateRange,
+    Icons.Default.Email,
+    Icons.Default.Favorite,
+    Icons.Default.Home,
+    Icons.Default.Info,
+    Icons.Default.Lock,
+    Icons.Default.Notifications,
+    Icons.Default.Place,
+    Icons.Default.Refresh,
+    Icons.Default.ShoppingCart,
+    Icons.Default.ThumbUp,
+    Icons.Default.Warning,
+)
+
+@Composable
+private fun ModalNavigationDrawerPredictiveBack(progress: Float, swipeEdgeLeft: Boolean) {
+    val maxScaleXDistanceGrow: Float
+    val maxScaleXDistanceShrink: Float
+    val maxScaleYDistance: Float
+    with(LocalDensity.current) {
+        maxScaleXDistanceGrow = PredictiveBackDrawerMaxScaleXDistanceGrow.toPx()
+        maxScaleXDistanceShrink = PredictiveBackDrawerMaxScaleXDistanceShrink.toPx()
+        maxScaleYDistance = PredictiveBackDrawerMaxScaleYDistance.toPx()
+    }
+
+    val drawerPredictiveBackState = DrawerPredictiveBackState().apply {
+        update(
+            progress = progress,
+            swipeEdgeLeft = swipeEdgeLeft,
+            isRtl = false,
+            maxScaleXDistanceGrow = maxScaleXDistanceGrow,
+            maxScaleXDistanceShrink = maxScaleXDistanceShrink,
+            maxScaleYDistance = maxScaleYDistance
+        )
+    }
+
+    ModalNavigationDrawer(
+        modifier = Modifier.testTag(ContainerTestTag),
+        drawerState = rememberDrawerState(DrawerValue.Open),
+        drawerContent = {
+            // Use the internal DrawerSheet instead of ModalDrawerSheet so we can simulate different
+            // back progress values for the test, and avoid the real PredictiveBackHandler.
+            DrawerSheet(
+                drawerPredictiveBackState,
+                DrawerDefaults.windowInsets,
+                Modifier,
+                DrawerDefaults.shape,
+                DrawerDefaults.modalContainerColor,
+                contentColorFor(DrawerDefaults.modalContainerColor),
+                DrawerDefaults.ModalDrawerElevation
+            ) {
+                Column(Modifier.verticalScroll(rememberScrollState())) {
+                    Spacer(Modifier.height(12.dp))
+                    items.forEach { item ->
+                        NavigationDrawerItem(
+                            icon = { Icon(item, contentDescription = null) },
+                            label = { Text(item.name) },
+                            selected = item == Icons.Default.AccountCircle,
+                            onClick = {},
+                            modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
+                        )
+                    }
+                }
+            }
+        },
+        content = {
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.background)
+            )
+        }
+    )
+}
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RadioButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RadioButtonTest.kt
index 04e3bfb..1b8b1bf 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RadioButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/RadioButtonTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -285,7 +286,8 @@
         rule
             .setMaterialContentForSizeAssertions {
                 CompositionLocalProvider(
-                    LocalMinimumInteractiveComponentEnforcement provides minimumTouchTarget
+                    LocalMinimumInteractiveComponentSize provides
+                        if (minimumTouchTarget) 48.dp else Dp.Unspecified
                 ) {
                     RadioButton(
                         selected = selected, onClick = if (clickable) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarScreenshotTest.kt
index 240aa9a..3c5554b 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SearchBarScreenshotTest.kt
@@ -407,17 +407,21 @@
             )
         }
 
-        SearchBarInternal(
+        SearchBarImpl(
             animationProgress = animationProgress,
             finalBackProgress = finalBackProgress,
             firstBackEvent = firstBackEvent,
             currentBackEvent = currentBackEvent,
             modifier = Modifier.testTag(testTag),
-            query = "Query",
-            onQueryChange = {},
-            onSearch = {},
-            active = true,
-            onActiveChange = {},
+            inputField = {
+                SearchBarInputField(
+                    query = "Query",
+                    onQueryChange = {},
+                    onSearch = {},
+                    active = true,
+                    onActiveChange = {},
+                )
+            },
             content = { Text("Content") },
         )
     }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SegmentedButtonTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SegmentedButtonTest.kt
index 92f8b4f..10301b9 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SegmentedButtonTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SegmentedButtonTest.kt
@@ -50,7 +50,6 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalMaterial3Api::class)
 class SegmentedButtonTest {
 
     @get:Rule
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
index 9a9ef7d..740d6ec 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
@@ -277,7 +277,6 @@
                         activeTickColor = Color.Yellow,
                         inactiveTickColor = Color.Magenta
                     )
-
                 )
             }
         }
@@ -301,7 +300,6 @@
                         disabledActiveTickColor = Color.Magenta,
                         disabledInactiveTickColor = Color.Cyan
                     )
-
                 )
             }
         }
@@ -310,6 +308,19 @@
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
+    fun sliderTest_min_corner() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                Slider(
+                    remember { SliderState(0.91f) }
+                )
+            }
+        }
+        assertSliderAgainstGolden("slider_min_corner")
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
     fun rangeSliderTest_middle_no_gap() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
index 102eb3c..2e153e1 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SliderTest.kt
@@ -40,7 +40,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
-import androidx.compose.testutils.expectAssertionError
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
@@ -739,14 +738,12 @@
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun slider_rowWithInfiniteWidth() {
-        expectAssertionError(false) {
-            rule.setContent {
-                Row(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
-                    Slider(
-                        state = SliderState(0f),
-                        modifier = Modifier.weight(1f)
-                    )
-                }
+        rule.setContent {
+            Row(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
+                Slider(
+                    state = SliderState(0f),
+                    modifier = Modifier.weight(1f)
+                )
             }
         }
     }
@@ -1393,14 +1390,12 @@
     @Test
     fun rangeSlider_rowWithInfiniteWidth() {
         val state = RangeSliderState(0f, 1f)
-        expectAssertionError(false) {
-            rule.setContent {
-                Row(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
-                    RangeSlider(
-                        state = state,
-                        modifier = Modifier.weight(1f)
-                    )
-                }
+        rule.setContent {
+            Row(modifier = Modifier.requiredWidth(Int.MAX_VALUE.dp)) {
+                RangeSlider(
+                    state = state,
+                    modifier = Modifier.weight(1f)
+                )
             }
         }
     }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
index 425848b..f879b0c 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwipeToDismissTest.kt
@@ -51,9 +51,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalMaterial3Api::class)
 class SwipeToDismissTest {
-
     @get:Rule
     val rule = createComposeRule()
 
@@ -395,4 +393,36 @@
             .that(newItem!!.anchoredDraggableState.anchors.size)
             .isAtLeast(1)
     }
+
+    @Test
+    fun swipeDismiss_respectsGesturesEnabled() {
+        lateinit var swipeToDismissBoxState: SwipeToDismissBoxState
+        rule.setContent {
+            swipeToDismissBoxState = rememberSwipeToDismissBoxState(SwipeToDismissBoxValue.Settled)
+            SwipeToDismissBox(
+                state = swipeToDismissBoxState,
+                modifier = Modifier.testTag(swipeDismissTag),
+                gesturesEnabled = false,
+                backgroundContent = { }
+            ) { Box(Modifier.fillMaxSize()) }
+        }
+
+        rule.onNodeWithTag(swipeDismissTag).performTouchInput { swipeRight() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(swipeToDismissBoxState.currentValue)
+                .isEqualTo(SwipeToDismissBoxValue.Settled)
+        }
+
+        rule.onNodeWithTag(swipeDismissTag).performTouchInput { swipeLeft() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(swipeToDismissBoxState.currentValue)
+                .isEqualTo(SwipeToDismissBoxValue.Settled)
+        }
+    }
 }
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwitchTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwitchTest.kt
index d67ee61..d3d74bd 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwitchTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SwitchTest.kt
@@ -60,6 +60,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -380,7 +381,8 @@
     ) = with(rule.density) {
         rule.setMaterialContentForSizeAssertions {
             CompositionLocalProvider(
-                LocalMinimumInteractiveComponentEnforcement provides minimumTouchTarget
+                LocalMinimumInteractiveComponentSize provides
+                    if (minimumTouchTarget) 48.dp else Dp.Unspecified
             ) {
                 Switch(
                     checked = checked,
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt
index 11e09d2..827bf49 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/TabTest.kt
@@ -18,6 +18,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
@@ -295,6 +296,45 @@
     }
 
     @Test
+    fun tabRow_indicatorHeight() {
+        val indicatorHeight = 1.dp
+        val titles = listOf("TAB 1", "TAB 2")
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                Modifier
+                    .testTag("tabRow")
+                    .fillMaxSize(),
+                propagateMinConstraints = true
+            ) {
+                PrimaryTabRow(
+                    selectedTabIndex = 1,
+                    indicator = {
+                        Box(
+                            Modifier
+                                .tabIndicatorOffset(1)
+                                .fillMaxWidth()
+                                .height(indicatorHeight)
+                                .background(color = Color.Red)
+                                .testTag("indicator")
+                        )
+                    }
+                ) {
+                    titles.forEachIndexed { index, title ->
+                        Tab(
+                            selected = index == 1,
+                            onClick = { },
+                            text = { Text(title) }
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag("indicator", true)
+            .assertHeightIsEqualTo(indicatorHeight)
+    }
+
+    @Test
     fun fixedTabRow_dividerHeight() {
         rule.setMaterialContent(lightColorScheme()) {
             val titles = listOf("TAB 1", "TAB 2")
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
index 83811d5..75d56ed 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
@@ -19,7 +19,9 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.TargetedFlingBehavior
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
@@ -130,6 +132,113 @@
         }
     }
 
+    @Test
+    fun carouselSingleAdvanceFling_capsScroll() {
+        // Arrange
+        createCarousel()
+        assertThat(carouselState.pagerState.currentPage).isEqualTo(0)
+
+        // Act
+        rule.onNodeWithTag(CarouselTestTag)
+            .performTouchInput {
+                swipeWithVelocity(
+                    centerRight,
+                    centerLeft,
+                    10000f
+                )
+            }
+
+        // Assert
+        rule.runOnIdle {
+            // A swipe from the very right to very left should be capped at
+            // the item right after the visible pages onscreen regardless of velocity
+            assertThat(carouselState.pagerState.currentPage).isLessThan(
+                carouselState.pagerState.layoutInfo.visiblePagesInfo.size + 1)
+        }
+    }
+
+    @Test
+    fun carouselMultibrowseFling_ScrollsToEnd() {
+        // Arrange
+        createCarousel(
+            flingBehavior =
+            { state: CarouselState -> CarouselDefaults.multiBrowseFlingBehavior(state) },
+        )
+        assertThat(carouselState.pagerState.currentPage).isEqualTo(0)
+
+        // Act
+        rule.onNodeWithTag(CarouselTestTag)
+            .performTouchInput { swipeWithVelocity(centerRight, centerLeft, 10000f) }
+
+        // Assert
+        rule.runOnIdle {
+            // A swipe from the very right to very left at a high velocity should go beyond
+            // first item after the visible pages as it's not capped
+            assertThat(carouselState.pagerState.currentPage).isGreaterThan(
+                carouselState.pagerState.layoutInfo.visiblePagesInfo.size)
+        }
+    }
+
+    @Test
+    fun carousel_calculateOutOfBoundsPageCount() {
+        val xSmallSize = 5f
+        val smallSize = 100f
+        val mediumSize = 200f
+        val largeSize = 400f
+        val keylineList = keylineListOf(carouselMainAxisSize = 1000f, 0f, CarouselAlignment.Start) {
+            add(xSmallSize, isAnchor = true)
+            add(largeSize)
+            add(mediumSize)
+            add(mediumSize)
+            add(smallSize)
+            add(smallSize)
+            add(xSmallSize, isAnchor = true)
+        }
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 1000f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+        val outOfBoundsNum = calculateOutOfBounds(strategy)
+        // With this strategy, we expect 3 loaded items
+        val loadedItems = 3
+
+        assertThat(outOfBoundsNum).isEqualTo(
+            (keylineList.filter { !it.isAnchor }.size - loadedItems) + 1
+        )
+    }
+
+    @Test
+    fun carousel_correctlyCalculatesMaxScrollOffsetWithItemSpacing() {
+        rule.setMaterialContent(lightColorScheme()) {
+            val state = rememberCarouselState { 10 }.also {
+                carouselState = it
+            }
+            val strategy = Strategy { availableSpace, itemSpacing ->
+                keylineListOf(availableSpace, itemSpacing, CarouselAlignment.Start) {
+                    add(10f, isAnchor = true)
+                    add(186f)
+                    add(122f)
+                    add(56f)
+                    add(10f, isAnchor = true)
+                }
+            }.apply(
+                availableSpace = 380f,
+                itemSpacing = 8f,
+                beforeContentPadding = 0f,
+                afterContentPadding = 0f
+            )
+
+            // Max offset should only add item spacing between each item
+            val expectedMaxScrollOffset = (186f * 10) + (8f * 9) - 380f
+
+            assertThat(calculateMaxScrollOffset(state, strategy)).isEqualTo(
+                expectedMaxScrollOffset
+            )
+        }
+    }
+
     @Composable
     internal fun Item(index: Int) {
         Box(
@@ -147,8 +256,15 @@
     private fun createCarousel(
         initialItem: Int = 0,
         itemCount: () -> Int = { DefaultItemCount },
-        modifier: Modifier = Modifier.width(412.dp).height(221.dp),
+        modifier: Modifier = Modifier
+            .width(412.dp)
+            .height(221.dp),
         orientation: Orientation = Orientation.Horizontal,
+        flingBehavior: @Composable (CarouselState) -> TargetedFlingBehavior = @Composable {
+            CarouselDefaults.singleAdvanceFlingBehavior(
+                state = it,
+            )
+        },
         content: @Composable CarouselScope.(item: Int) -> Unit = { Item(index = it) }
     ) {
         rule.setMaterialContent(lightColorScheme()) {
@@ -159,17 +275,19 @@
             Carousel(
                 state = state,
                 orientation = orientation,
-                keylineList = { availableSpace ->
+                keylineList = { availableSpace, itemSpacing ->
                     multiBrowseKeylineList(
                         density = density,
                         carouselMainAxisSize = availableSpace,
                         preferredItemSize = with(density) { 186.dp.toPx() },
-                        itemSpacing = 0f,
+                        itemSpacing = itemSpacing,
                         itemCount = itemCount.invoke(),
                     )
                 },
+                flingBehavior = flingBehavior(state),
                 modifier = modifier.testTag(CarouselTestTag),
                 itemSpacing = 0.dp,
+                contentPadding = PaddingValues(0.dp),
                 content = content,
             )
         }
@@ -178,7 +296,9 @@
     private fun createUncontainedCarousel(
         initialItem: Int = 0,
         itemCount: () -> Int = { DefaultItemCount },
-        modifier: Modifier = Modifier.width(412.dp).height(221.dp),
+        modifier: Modifier = Modifier
+            .width(412.dp)
+            .height(221.dp),
         content: @Composable CarouselScope.(item: Int) -> Unit = { Item(index = it) }
     ) {
         rule.setMaterialContent(lightColorScheme()) {
@@ -187,7 +307,7 @@
             }
             HorizontalUncontainedCarousel(
                 state = state,
-                itemSize = 150.dp,
+                itemWidth = 150.dp,
                 modifier = modifier.testTag(CarouselTestTag),
                 itemSpacing = 0.dp,
                 content = content,
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt
index d8bebd7..43399f0 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.android.kt
@@ -146,15 +146,6 @@
     val scope = remember(expanded, onExpandedChange, config, view, density) {
         object : ExposedDropdownMenuBoxScope() {
             override fun Modifier.menuAnchor(): Modifier = this
-                .onGloballyPositioned {
-                    anchorCoordinates = it
-                    anchorWidth = it.size.width
-                    menuMaxHeight = calculateMaxHeight(
-                        windowBounds = view.rootView.getWindowBounds(),
-                        anchorBounds = anchorCoordinates.getAnchorBounds(),
-                        verticalMargin = verticalMargin,
-                    )
-                }
                 .expandable(
                     expanded = expanded,
                     onExpandedChange = { onExpandedChange(!expanded) },
@@ -179,7 +170,17 @@
         }
     }
 
-    Box(modifier) {
+    Box(
+        modifier.onGloballyPositioned {
+            anchorCoordinates = it
+            anchorWidth = it.size.width
+            menuMaxHeight = calculateMaxHeight(
+                windowBounds = view.rootView.getWindowBounds(),
+                anchorBounds = anchorCoordinates.getAnchorBounds(),
+                verticalMargin = verticalMargin,
+            )
+        }
+    ) {
         scope.content()
     }
 
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/NavigationDrawer.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/NavigationDrawer.android.kt
new file mode 100644
index 0000000..535c610
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/NavigationDrawer.android.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.activity.BackEventCompat
+import androidx.activity.compose.PredictiveBackHandler
+import androidx.compose.animation.core.animate
+import androidx.compose.material3.internal.PredictiveBack
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.launch
+
+/**
+ * Registers a [PredictiveBackHandler] and provides animation values in [DrawerPredictiveBackState]
+ * based on back progress.
+ *
+ * @param drawerState state of the drawer
+ * @param content content of the rest of the UI
+ */
+@Composable
+internal actual fun DrawerPredictiveBackHandler(
+    drawerState: DrawerState,
+    content: @Composable (DrawerPredictiveBackState) -> Unit
+) {
+    val drawerPredictiveBackState = remember { DrawerPredictiveBackState() }
+    val scope = rememberCoroutineScope()
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    val maxScaleXDistanceGrow: Float
+    val maxScaleXDistanceShrink: Float
+    val maxScaleYDistance: Float
+    with(LocalDensity.current) {
+        maxScaleXDistanceGrow = PredictiveBackDrawerMaxScaleXDistanceGrow.toPx()
+        maxScaleXDistanceShrink = PredictiveBackDrawerMaxScaleXDistanceShrink.toPx()
+        maxScaleYDistance = PredictiveBackDrawerMaxScaleYDistance.toPx()
+    }
+
+    PredictiveBackHandler(enabled = drawerState.isOpen) { progress ->
+        try {
+            progress.collect { backEvent ->
+                drawerPredictiveBackState.update(
+                    PredictiveBack.transform(backEvent.progress),
+                    backEvent.swipeEdge == BackEventCompat.EDGE_LEFT,
+                    isRtl,
+                    maxScaleXDistanceGrow,
+                    maxScaleXDistanceShrink,
+                    maxScaleYDistance
+                )
+            }
+        } catch (e: CancellationException) {
+            drawerPredictiveBackState.clear()
+        } finally {
+            if (drawerPredictiveBackState.swipeEdgeMatchesDrawer) {
+                // If swipe edge matches drawer gravity and we've stretched the drawer horizontally,
+                // un-stretch it smoothly so that it hides completely during the drawer close.
+                scope.launch {
+                    animate(
+                        initialValue = drawerPredictiveBackState.scaleXDistance,
+                        targetValue = 0f
+                    ) { value, _ -> drawerPredictiveBackState.scaleXDistance = value }
+                    drawerPredictiveBackState.clear()
+                }
+            }
+            drawerState.close()
+        }
+    }
+
+    LaunchedEffect(drawerState.isClosed) {
+        if (drawerState.isClosed) {
+            drawerPredictiveBackState.clear()
+        }
+    }
+
+    content(drawerPredictiveBackState)
+}
+
+internal val PredictiveBackDrawerMaxScaleXDistanceGrow = 12.dp
+internal val PredictiveBackDrawerMaxScaleXDistanceShrink = 24.dp
+internal val PredictiveBackDrawerMaxScaleYDistance = 48.dp
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt
index 51db309..6d540f6 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.android.kt
@@ -38,17 +38,14 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
 import androidx.compose.foundation.layout.consumeWindowInsets
 import androidx.compose.foundation.layout.exclude
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.layout.statusBars
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.GenericShape
@@ -69,8 +66,6 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableFloatState
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
@@ -89,7 +84,8 @@
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.takeOrElse
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalFocusManager
@@ -104,9 +100,11 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.lerp
-import androidx.compose.ui.unit.offset
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.lerp
 import androidx.compose.ui.zIndex
 import kotlin.coroutines.cancellation.CancellationException
@@ -239,179 +237,20 @@
         }
     }
 
-    SearchBarInternal(
+    SearchBarImpl(
         animationProgress = animationProgress,
         finalBackProgress = finalBackProgress,
         firstBackEvent = firstBackEvent,
         currentBackEvent = currentBackEvent,
-        query = query,
-        onQueryChange = onQueryChange,
-        onSearch = onSearch,
-        active = active,
-        onActiveChange = onActiveChange,
         modifier = modifier,
-        enabled = enabled,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        shape = shape,
-        colors = colors,
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation,
-        windowInsets = windowInsets,
-        interactionSource = interactionSource,
-        content = content,
-    )
-}
-
-@ExperimentalMaterial3Api
-@Composable
-internal fun SearchBarInternal(
-    animationProgress: Animatable<Float, AnimationVector1D>,
-    finalBackProgress: MutableFloatState,
-    firstBackEvent: MutableState<BackEventCompat?>,
-    currentBackEvent: MutableState<BackEventCompat?>,
-    query: String,
-    onQueryChange: (String) -> Unit,
-    onSearch: (String) -> Unit,
-    active: Boolean,
-    onActiveChange: (Boolean) -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    placeholder: @Composable (() -> Unit)? = null,
-    leadingIcon: @Composable (() -> Unit)? = null,
-    trailingIcon: @Composable (() -> Unit)? = null,
-    shape: Shape = SearchBarDefaults.inputFieldShape,
-    colors: SearchBarColors = SearchBarDefaults.colors(),
-    tonalElevation: Dp = SearchBarDefaults.TonalElevation,
-    shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
-    windowInsets: WindowInsets = SearchBarDefaults.windowInsets,
-    interactionSource: MutableInteractionSource? = null,
-    content: @Composable ColumnScope.() -> Unit,
-) {
-    @Suppress("NAME_SHADOWING")
-    val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
-    val focusManager = LocalFocusManager.current
-    val density = LocalDensity.current
-
-    val defaultInputFieldShape = SearchBarDefaults.inputFieldShape
-    val defaultFullScreenShape = SearchBarDefaults.fullScreenShape
-    val useFullScreenShape by remember {
-        derivedStateOf(structuralEqualityPolicy()) { animationProgress.value == 1f }
-    }
-    val animatedShape = remember(useFullScreenShape, shape) {
-        when {
-            shape == defaultInputFieldShape ->
-                // The shape can only be animated if it's the default spec value
-                GenericShape { size, _ ->
-                    val radius = with(density) {
-                        (SearchBarCornerRadius * (1 - animationProgress.value)).toPx()
-                    }
-                    addRoundRect(RoundRect(size.toRect(), CornerRadius(radius)))
-                }
-            useFullScreenShape -> defaultFullScreenShape
-            else -> shape
-        }
-    }
-
-    // The main animation complexity is allowing the component to smoothly expand while keeping the
-    // input field at the same relative location on screen. `Modifier.windowInsetsPadding` does not
-    // support animation and thus is not suitable. Instead, we convert the insets to a padding
-    // applied to the Surface, which gradually becomes padding applied to the input field as the
-    // animation proceeds.
-    val unconsumedInsets = remember { MutableWindowInsets() }
-    val topPadding = remember(density) {
-        derivedStateOf {
-            SearchBarVerticalPadding +
-                unconsumedInsets.asPaddingValues(density).calculateTopPadding()
-        }
-    }
-
-    Surface(
-        shape = animatedShape,
-        color = colors.containerColor,
-        contentColor = contentColorFor(colors.containerColor),
-        tonalElevation = tonalElevation,
-        shadowElevation = shadowElevation,
-        modifier = modifier
-            .zIndex(1f)
-            .onConsumedWindowInsetsChanged { consumedInsets ->
-                unconsumedInsets.insets = windowInsets.exclude(consumedInsets)
-            }
-            .consumeWindowInsets(unconsumedInsets)
-            .layout { measurable, constraints ->
-                val animatedTopPadding =
-                    lerp(topPadding.value, 0.dp, animationProgress.value).roundToPx()
-
-                val defaultStartWidth = max(constraints.minWidth, SearchBarMinWidth.roundToPx())
-                    .coerceAtMost(min(constraints.maxWidth, SearchBarMaxWidth.roundToPx()))
-                val defaultStartHeight = max(constraints.minHeight, InputFieldHeight.roundToPx())
-                    .coerceAtMost(constraints.maxHeight)
-                val predictiveBackStartWidth =
-                    (constraints.maxWidth * SearchBarPredictiveBackMinScale).roundToInt()
-                val predictiveBackStartHeight =
-                    (constraints.maxHeight * SearchBarPredictiveBackMinScale).roundToInt()
-                val predictiveBackMultiplier = calculatePredictiveBackMultiplier(
-                    currentBackEvent.value,
-                    animationProgress.value,
-                    finalBackProgress.floatValue
-                )
-                val startWidth =
-                    lerp(defaultStartWidth, predictiveBackStartWidth, predictiveBackMultiplier)
-                val startHeight =
-                    lerp(defaultStartHeight, predictiveBackStartHeight, predictiveBackMultiplier)
-
-                val endWidth = constraints.maxWidth
-                val endHeight = constraints.maxHeight
-
-                val width = lerp(startWidth, endWidth, animationProgress.value)
-                val height =
-                    lerp(startHeight, endHeight, animationProgress.value) + animatedTopPadding
-
-                val minOffsetMargin = SearchBarPredictiveBackMinMargin.roundToPx()
-                val predictiveBackOffsetX = calculatePredictiveBackOffsetX(
-                    constraints,
-                    minOffsetMargin,
-                    currentBackEvent.value,
-                    animationProgress.value,
-                    predictiveBackMultiplier
-                )
-                val predictiveBackOffsetY = calculatePredictiveBackOffsetY(
-                    constraints,
-                    minOffsetMargin,
-                    currentBackEvent.value,
-                    firstBackEvent.value,
-                    height,
-                    SearchBarPredictiveBackMaxOffsetY.roundToPx(),
-                    predictiveBackMultiplier
-                )
-
-                val placeable = measurable.measure(
-                    Constraints
-                        .fixed(width, height)
-                        .offset(
-                            vertical = -animatedTopPadding
-                        )
-                )
-                layout(width, height) {
-                    placeable.placeRelative(
-                        predictiveBackOffsetX,
-                        animatedTopPadding + predictiveBackOffsetY
-                    )
-                }
-            }
-    ) {
-        Column {
-            val animatedInputFieldPadding = remember {
-                AnimatedPaddingValues(animationProgress.asState(), topPadding)
-            }
+        inputField = {
             SearchBarInputField(
                 query = query,
                 onQueryChange = onQueryChange,
                 onSearch = onSearch,
                 active = active,
                 onActiveChange = onActiveChange,
-                modifier = Modifier.padding(paddingValues = animatedInputFieldPadding),
+                modifier = Modifier.fillMaxWidth(),
                 enabled = enabled,
                 placeholder = placeholder,
                 leadingIcon = leadingIcon,
@@ -419,78 +258,14 @@
                 colors = colors.inputFieldColors,
                 interactionSource = interactionSource,
             )
-
-            val showResults by remember {
-                derivedStateOf(structuralEqualityPolicy()) { animationProgress.value > 0 }
-            }
-            if (showResults) {
-                Column(Modifier.graphicsLayer { alpha = animationProgress.value }) {
-                    HorizontalDivider(color = colors.dividerColor)
-                    content()
-                }
-            }
-        }
-    }
-
-    val isFocused = interactionSource.collectIsFocusedAsState().value
-    val shouldClearFocus = !active && isFocused
-    LaunchedEffect(active) {
-        if (shouldClearFocus) {
-            // Not strictly needed according to the motion spec, but since the animation already has
-            // a delay, this works around b/261632544.
-            delay(AnimationDelayMillis.toLong())
-            focusManager.clearFocus()
-        }
-    }
-}
-
-private fun calculatePredictiveBackMultiplier(
-    currentBackEvent: BackEventCompat?,
-    progress: Float,
-    finalBackProgress: Float
-) = when {
-    currentBackEvent == null -> 0f // Not in predictive back at all.
-    finalBackProgress.isNaN() -> 1f // User is currently swiping predictive back.
-    finalBackProgress <= 0 -> 0f // Safety check for divide by zero.
-    else -> progress / finalBackProgress // User has released predictive back swipe.
-}
-
-private fun calculatePredictiveBackOffsetX(
-    constraints: Constraints,
-    minMargin: Int,
-    currentBackEvent: BackEventCompat?,
-    progress: Float,
-    predictiveBackMultiplier: Float
-): Int {
-    if (currentBackEvent == null || predictiveBackMultiplier == 0f) {
-        return 0
-    }
-    val directionMultiplier = if (currentBackEvent.swipeEdge == BackEventCompat.EDGE_LEFT) 1 else -1
-    val maxOffsetX =
-        (constraints.maxWidth * SearchBarPredictiveBackMaxOffsetXRatio) - minMargin
-    val interpolatedOffsetX = maxOffsetX * (1 - progress)
-    return (interpolatedOffsetX * predictiveBackMultiplier * directionMultiplier).roundToInt()
-}
-
-private fun calculatePredictiveBackOffsetY(
-    constraints: Constraints,
-    minMargin: Int,
-    currentBackEvent: BackEventCompat?,
-    firstBackEvent: BackEventCompat?,
-    height: Int,
-    maxOffsetY: Int,
-    predictiveBackMultiplier: Float
-): Int {
-    if (firstBackEvent == null || currentBackEvent == null || predictiveBackMultiplier == 0f) {
-        return 0
-    }
-    val availableVerticalSpace = max(0, (constraints.maxHeight - height) / 2 - minMargin)
-    val adjustedMaxOffsetY = min(availableVerticalSpace, maxOffsetY)
-    val yDelta = currentBackEvent.touchY - firstBackEvent.touchY
-    val yProgress = abs(yDelta) / constraints.maxHeight
-    val directionMultiplier = sign(yDelta)
-    val interpolatedOffsetY = lerp(0, adjustedMaxOffsetY, yProgress)
-    return (interpolatedOffsetY * predictiveBackMultiplier * directionMultiplier).roundToInt()
+        },
+        shape = shape,
+        colors = colors,
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation,
+        windowInsets = windowInsets,
+        content = content,
+    )
 }
 
 /**
@@ -561,7 +336,6 @@
 ) {
     @Suppress("NAME_SHADOWING")
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
-    val focusManager = LocalFocusManager.current
 
     Surface(
         shape = shape,
@@ -575,6 +349,7 @@
     ) {
         Column {
             SearchBarInputField(
+                modifier = Modifier.fillMaxWidth(),
                 query = query,
                 onQueryChange = onQueryChange,
                 onSearch = onSearch,
@@ -609,17 +384,6 @@
         }
     }
 
-    val isFocused = interactionSource.collectIsFocusedAsState().value
-    val shouldClearFocus = !active && isFocused
-    LaunchedEffect(active) {
-        if (shouldClearFocus) {
-            // Not strictly needed according to the motion spec, but since the animation already has
-            // a delay, this works around b/261632544.
-            delay(AnimationDelayMillis.toLong())
-            focusManager.clearFocus()
-        }
-    }
-
     BackHandler(enabled = active) {
         onActiveChange(false)
     }
@@ -627,7 +391,7 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
-private fun SearchBarInputField(
+internal fun SearchBarInputField(
     query: String,
     onQueryChange: (String) -> Unit,
     onSearch: (String) -> Unit,
@@ -644,6 +408,7 @@
     @Suppress("NAME_SHADOWING")
     val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
     val focusRequester = remember { FocusRequester() }
+    val focusManager = LocalFocusManager.current
     val searchSemantics = getString(Strings.SearchBarSearch)
     val suggestionsAvailableSemantics = getString(Strings.SuggestionsAvailable)
     val textColor = LocalTextStyle.current.color.takeOrElse {
@@ -655,8 +420,11 @@
         value = query,
         onValueChange = onQueryChange,
         modifier = modifier
-            .height(InputFieldHeight)
-            .fillMaxWidth()
+            .sizeIn(
+                minWidth = SearchBarMinWidth,
+                maxWidth = SearchBarMaxWidth,
+                minHeight = InputFieldHeight,
+            )
             .focusRequester(focusRequester)
             .onFocusChanged { if (it.isFocused) onActiveChange(true) }
             .semantics {
@@ -698,6 +466,17 @@
             )
         }
     )
+
+    val isFocused = interactionSource.collectIsFocusedAsState().value
+    val shouldClearFocus = !active && isFocused
+    LaunchedEffect(active) {
+        if (shouldClearFocus) {
+            // Not strictly needed according to the motion spec, but since the animation already has
+            // a delay, this works around b/261632544.
+            delay(AnimationDelayMillis.toLong())
+            focusManager.clearFocus()
+        }
+    }
 }
 
 /**
@@ -860,7 +639,7 @@
  */
 @ExperimentalMaterial3Api
 @Immutable
-class SearchBarColors internal constructor(
+class SearchBarColors(
     val containerColor: Color,
     val dividerColor: Color,
     val inputFieldColors: TextFieldColors,
@@ -886,18 +665,274 @@
     }
 }
 
-@Stable
-private class AnimatedPaddingValues(
-    val animationProgress: State<Float>,
-    val topPadding: State<Dp>,
-) : PaddingValues {
-    override fun calculateTopPadding(): Dp = topPadding.value * animationProgress.value
-    override fun calculateBottomPadding(): Dp = SearchBarVerticalPadding * animationProgress.value
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun SearchBarImpl(
+    animationProgress: Animatable<Float, AnimationVector1D>,
+    finalBackProgress: MutableFloatState,
+    firstBackEvent: MutableState<BackEventCompat?>,
+    currentBackEvent: MutableState<BackEventCompat?>,
+    modifier: Modifier = Modifier,
+    inputField: @Composable () -> Unit,
+    shape: Shape = SearchBarDefaults.inputFieldShape,
+    colors: SearchBarColors = SearchBarDefaults.colors(),
+    tonalElevation: Dp = SearchBarDefaults.TonalElevation,
+    shadowElevation: Dp = SearchBarDefaults.ShadowElevation,
+    windowInsets: WindowInsets = SearchBarDefaults.windowInsets,
+    content: @Composable ColumnScope.() -> Unit,
+) {
+    val density = LocalDensity.current
 
-    override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = 0.dp
-    override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = 0.dp
+    val defaultInputFieldShape = SearchBarDefaults.inputFieldShape
+    val defaultFullScreenShape = SearchBarDefaults.fullScreenShape
+    val useFullScreenShape by remember {
+        derivedStateOf(structuralEqualityPolicy()) { animationProgress.value == 1f }
+    }
+    val animatedShape = remember(useFullScreenShape, shape) {
+        when {
+            shape == defaultInputFieldShape ->
+                // The shape can only be animated if it's the default spec value
+                GenericShape { size, _ ->
+                    val radius = with(density) {
+                        (SearchBarCornerRadius * (1 - animationProgress.value)).toPx()
+                    }
+                    addRoundRect(RoundRect(size.toRect(), CornerRadius(radius)))
+                }
+            useFullScreenShape -> defaultFullScreenShape
+            else -> shape
+        }
+    }
+    val surface = @Composable {
+        Surface(
+            modifier = Modifier,
+            shape = animatedShape,
+            color = colors.containerColor,
+            contentColor = contentColorFor(colors.containerColor),
+            tonalElevation = tonalElevation,
+            shadowElevation = shadowElevation,
+            content = {},
+        )
+    }
+
+    val showContent by remember {
+        derivedStateOf(structuralEqualityPolicy()) { animationProgress.value > 0 }
+    }
+    val wrappedContent: (@Composable () -> Unit)? = if (showContent) {
+        {
+            Column(Modifier.graphicsLayer { alpha = animationProgress.value }) {
+                HorizontalDivider(color = colors.dividerColor)
+                content()
+            }
+        }
+    } else null
+
+    SearchBarLayout(
+        animationProgress = animationProgress,
+        finalBackProgress = finalBackProgress,
+        firstBackEvent = firstBackEvent,
+        currentBackEvent = currentBackEvent,
+        modifier = modifier,
+        windowInsets = windowInsets,
+        inputField = inputField,
+        surface = surface,
+        content = wrappedContent,
+    )
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun SearchBarLayout(
+    animationProgress: Animatable<Float, AnimationVector1D>,
+    finalBackProgress: MutableFloatState,
+    firstBackEvent: MutableState<BackEventCompat?>,
+    currentBackEvent: MutableState<BackEventCompat?>,
+    modifier: Modifier,
+    windowInsets: WindowInsets,
+    inputField: @Composable () -> Unit,
+    surface: @Composable () -> Unit,
+    content: (@Composable () -> Unit)?,
+) {
+    // `Modifier.windowInsetsPadding` does not support animation,
+    // so the insets are converted to paddings in the Layout's MeasureScope
+    // and the animation calculations are done manually.
+    val unconsumedInsets = remember { MutableWindowInsets() }
+    Layout(
+        modifier = modifier
+            .zIndex(1f)
+            .onConsumedWindowInsetsChanged { consumedInsets ->
+                unconsumedInsets.insets = windowInsets.exclude(consumedInsets)
+            }
+            .consumeWindowInsets(unconsumedInsets),
+        content = {
+            Box(Modifier.layoutId(LayoutIdSurface), propagateMinConstraints = true) {
+                surface()
+            }
+            Box(Modifier.layoutId(LayoutIdInputField), propagateMinConstraints = true) {
+                inputField()
+            }
+            content?.let { content ->
+                Box(Modifier.layoutId(LayoutIdSearchContent), propagateMinConstraints = true) {
+                    content()
+                }
+            }
+        },
+    ) { measurables, constraints ->
+        @Suppress("NAME_SHADOWING")
+        val animationProgress = animationProgress.value
+
+        val inputFieldMeasurable = measurables.fastFirst { it.layoutId == LayoutIdInputField }
+        val surfaceMeasurable = measurables.fastFirst { it.layoutId == LayoutIdSurface }
+        val contentMeasurable = measurables.fastFirstOrNull { it.layoutId == LayoutIdSearchContent }
+
+        val topPadding = unconsumedInsets.getTop(this) + SearchBarVerticalPadding.roundToPx()
+        val bottomPadding = SearchBarVerticalPadding.roundToPx()
+
+        val defaultStartWidth = constraints
+            .constrainWidth(inputFieldMeasurable.maxIntrinsicWidth(constraints.maxHeight))
+        val defaultStartHeight = constraints
+            .constrainHeight(inputFieldMeasurable.minIntrinsicHeight(constraints.maxWidth))
+
+        val predictiveBackStartWidth =
+            (constraints.maxWidth * SearchBarPredictiveBackMinScale).roundToInt()
+        val predictiveBackStartHeight =
+            (constraints.maxHeight * SearchBarPredictiveBackMinScale).roundToInt()
+        val predictiveBackMultiplier = calculatePredictiveBackMultiplier(
+            currentBackEvent.value,
+            animationProgress,
+            finalBackProgress.floatValue
+        )
+
+        val startWidth =
+            lerp(defaultStartWidth, predictiveBackStartWidth, predictiveBackMultiplier)
+        val startHeight = lerp(
+            topPadding + defaultStartHeight,
+            predictiveBackStartHeight,
+            predictiveBackMultiplier
+        )
+
+        val endWidth = constraints.maxWidth
+        val endHeight = constraints.maxHeight
+
+        val width = lerp(startWidth, endWidth, animationProgress)
+        val height = lerp(startHeight, endHeight, animationProgress)
+
+        // Note: animatedTopPadding decreases w.r.t. animationProgress
+        val animatedTopPadding = lerp(topPadding, 0, animationProgress)
+        val animatedBottomPadding = lerp(0, bottomPadding, animationProgress)
+
+        // As the animation proceeds, the surface loses its padding
+        // and expands to cover the entire container.
+        val surfacePlaceable = surfaceMeasurable
+            .measure(Constraints.fixed(width, height - animatedTopPadding))
+        val inputFieldPlaceable = inputFieldMeasurable
+            .measure(Constraints.fixed(width, defaultStartHeight))
+        val contentPlaceable = contentMeasurable?.measure(
+            Constraints(
+                minWidth = width,
+                maxWidth = width,
+                minHeight = 0,
+                maxHeight = if (constraints.hasBoundedHeight) {
+                    (constraints.maxHeight - (topPadding + defaultStartHeight + bottomPadding))
+                        .coerceAtLeast(0)
+                } else {
+                    constraints.maxHeight
+                }
+            )
+        )
+
+        layout(width, height) {
+            val minOffsetMargin = SearchBarPredictiveBackMinMargin.roundToPx()
+            val predictiveBackOffsetX = calculatePredictiveBackOffsetX(
+                constraints = constraints,
+                minMargin = minOffsetMargin,
+                currentBackEvent = currentBackEvent.value,
+                layoutDirection = layoutDirection,
+                progress = animationProgress,
+                predictiveBackMultiplier = predictiveBackMultiplier,
+            )
+            val predictiveBackOffsetY = calculatePredictiveBackOffsetY(
+                constraints = constraints,
+                minMargin = minOffsetMargin,
+                currentBackEvent = currentBackEvent.value,
+                firstBackEvent = firstBackEvent.value,
+                height = height,
+                maxOffsetY = SearchBarPredictiveBackMaxOffsetY.roundToPx(),
+                predictiveBackMultiplier = predictiveBackMultiplier,
+            )
+
+            surfacePlaceable.placeRelative(
+                predictiveBackOffsetX,
+                predictiveBackOffsetY + animatedTopPadding,
+            )
+            inputFieldPlaceable.placeRelative(
+                predictiveBackOffsetX,
+                predictiveBackOffsetY + topPadding,
+            )
+            contentPlaceable?.placeRelative(
+                predictiveBackOffsetX,
+                predictiveBackOffsetY + topPadding + inputFieldPlaceable.height +
+                    animatedBottomPadding,
+            )
+        }
+    }
+}
+
+private fun calculatePredictiveBackMultiplier(
+    currentBackEvent: BackEventCompat?,
+    progress: Float,
+    finalBackProgress: Float
+) = when {
+    currentBackEvent == null -> 0f // Not in predictive back at all.
+    finalBackProgress.isNaN() -> 1f // User is currently swiping predictive back.
+    finalBackProgress <= 0 -> 0f // Safety check for divide by zero.
+    else -> progress / finalBackProgress // User has released predictive back swipe.
+}
+
+private fun calculatePredictiveBackOffsetX(
+    constraints: Constraints,
+    minMargin: Int,
+    currentBackEvent: BackEventCompat?,
+    layoutDirection: LayoutDirection,
+    progress: Float,
+    predictiveBackMultiplier: Float
+): Int {
+    if (currentBackEvent == null || predictiveBackMultiplier == 0f) {
+        return 0
+    }
+    val directionMultiplier = if (currentBackEvent.swipeEdge == BackEventCompat.EDGE_LEFT) 1 else -1
+    val rtlMultiplier = if (layoutDirection == LayoutDirection.Ltr) 1 else -1
+    val maxOffsetX =
+        (constraints.maxWidth * SearchBarPredictiveBackMaxOffsetXRatio) - minMargin
+    val interpolatedOffsetX = maxOffsetX * (1 - progress)
+    return (interpolatedOffsetX * predictiveBackMultiplier * directionMultiplier * rtlMultiplier)
+        .roundToInt()
+}
+
+private fun calculatePredictiveBackOffsetY(
+    constraints: Constraints,
+    minMargin: Int,
+    currentBackEvent: BackEventCompat?,
+    firstBackEvent: BackEventCompat?,
+    height: Int,
+    maxOffsetY: Int,
+    predictiveBackMultiplier: Float
+): Int {
+    if (firstBackEvent == null || currentBackEvent == null || predictiveBackMultiplier == 0f) {
+        return 0
+    }
+    val availableVerticalSpace = max(0, (constraints.maxHeight - height) / 2 - minMargin)
+    val adjustedMaxOffsetY = min(availableVerticalSpace, maxOffsetY)
+    val yDelta = currentBackEvent.touchY - firstBackEvent.touchY
+    val yProgress = abs(yDelta) / constraints.maxHeight
+    val directionMultiplier = sign(yDelta)
+    val interpolatedOffsetY = lerp(0, adjustedMaxOffsetY, yProgress)
+    return (interpolatedOffsetY * predictiveBackMultiplier * directionMultiplier).roundToInt()
+}
+
+private const val LayoutIdInputField = "InputField"
+private const val LayoutIdSurface = "Surface"
+private const val LayoutIdSearchContent = "Content"
+
 // Measurement specs
 @OptIn(ExperimentalMaterial3Api::class)
 private val SearchBarCornerRadius: Dp = InputFieldHeight / 2
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TouchExplorationStateProvider.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TouchExplorationStateProvider.android.kt
index 8a90585..5ef72e1 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TouchExplorationStateProvider.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TouchExplorationStateProvider.android.kt
@@ -28,9 +28,9 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 /**
  * It depends on the state of accessibility services to determine the current state of touch
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/ArrangementTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/ArrangementTest.kt
index 59d6971..832b6e5 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/ArrangementTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/ArrangementTest.kt
@@ -33,6 +33,7 @@
 
         val arrangement = Arrangement.findLowestCostArrangement(
             availableSpace = targetLargeSize + targetMediumSize + targetSmallSize,
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -54,6 +55,7 @@
         val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f
         val arrangement = Arrangement.findLowestCostArrangement(
             availableSpace = targetLargeSize + targetMediumSize + targetSmallSize - 10f,
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -75,6 +77,7 @@
         val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f
         val arrangement = Arrangement.findLowestCostArrangement(
             availableSpace = targetLargeSize + targetMediumSize + targetSmallSize + 10f,
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -99,6 +102,7 @@
             availableSpace = targetLargeSize +
                 targetMediumSize +
                 targetSmallSize - mediumAdjustment,
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -124,6 +128,7 @@
             availableSpace = targetLargeSize +
                 targetMediumSize +
                 targetSmallSize + mediumAdjustment,
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -150,6 +155,7 @@
                 targetMediumSize +
                 (targetSmallSize * 2) +
                 (smallAdjustment * 2),
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -175,6 +181,7 @@
             availableSpace = targetLargeSize +
                 targetMediumSize +
                 (targetSmallSize * 2) - (smallAdjustment * 2),
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -200,6 +207,7 @@
                 (targetMediumSize * 2) +
                 (targetSmallSize * 2) +
                 (mediumAdjustment * 2),
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
@@ -225,6 +233,7 @@
             availableSpace = (targetLargeSize * 2) +
                 (targetMediumSize * 2) +
                 (targetSmallSize * 2) - (mediumAdjustment * 2),
+            itemSpacing = 0f,
             targetSmallSize = targetSmallSize,
             minSmallSize = 40f,
             maxSmallSize = 56f,
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
index 86a5b81..a6b9613 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
@@ -83,7 +83,11 @@
         val smallSize = 100f
         val mediumSize = 200f
         val largeSize = 400f
-        val keylineList = keylineListOf(carouselMainAxisSize = 1000f, CarouselAlignment.Center) {
+        val keylineList = keylineListOf(
+            carouselMainAxisSize = 1000f,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Center
+        ) {
             add(xSmallSize, isAnchor = true)
             add(smallSize)
             add(mediumSize)
@@ -93,7 +97,12 @@
             add(xSmallSize, isAnchor = true)
         }
 
-        return Strategy { keylineList }.apply(1000f)
+        return Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 1000f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
     }
 
     // Test strategy that is start aligned:
@@ -110,7 +119,11 @@
         val smallSize = 100f
         val mediumSize = 200f
         val largeSize = 400f
-        val keylineList = keylineListOf(carouselMainAxisSize = 1000f, CarouselAlignment.Start) {
+        val keylineList = keylineListOf(
+            carouselMainAxisSize = 1000f,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
             add(xSmallSize, isAnchor = true)
             add(largeSize)
             add(mediumSize)
@@ -119,7 +132,12 @@
             add(smallSize)
             add(xSmallSize, isAnchor = true)
         }
-        return Strategy { keylineList }.apply(1000f)
+        return Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 1000f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
     }
 
     // Test strategy that is start aligned:
@@ -134,7 +152,7 @@
         val smallSize = 75f
         val mediumSize = 125f
         val largeSize = 400f
-        val keylineList = keylineListOf(carouselMainAxisSize = 1000f, CarouselAlignment.Start) {
+        val keylineList = keylineListOf(carouselMainAxisSize = 1000f, 0f, CarouselAlignment.Start) {
             add(xSmallSize, isAnchor = true)
             add(largeSize)
             add(largeSize)
@@ -142,6 +160,11 @@
             add(smallSize)
             add(xSmallSize, isAnchor = true)
         }
-        return Strategy { keylineList }.apply(1000f)
+        return Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 1000f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
     }
 }
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
index 2fdb017..f47697e 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
@@ -35,7 +35,11 @@
     @Test
     fun testKeylineList_pivotsWithCorrectCutoffsRight() {
         val carouselMainAxisSize = LargeSize + (MediumSize * .75f).roundToInt()
-        val keylineList = keylineListOf(carouselMainAxisSize, CarouselAlignment.Start) {
+        val keylineList = keylineListOf(
+            carouselMainAxisSize = carouselMainAxisSize,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
             add(SmallSize, isAnchor = true)
             add(LargeSize)
             add(MediumSize)
@@ -49,7 +53,11 @@
     @Test
     fun testKeylineList_pivotsWithCorrectCutoffsLeft() {
         val carouselMainAxisSize = (MediumSize * .75f).roundToInt() + LargeSize
-        val keylineList = keylineListOf(carouselMainAxisSize, CarouselAlignment.End) {
+        val keylineList = keylineListOf(
+            carouselMainAxisSize = carouselMainAxisSize,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.End
+        ) {
             add(SmallSize, isAnchor = true)
             add(MediumSize)
             add(LargeSize)
@@ -95,7 +103,12 @@
     @Test
     fun testKeylineListLerp() {
         val carouselMainAxisSize = StrategyTest.large + StrategyTest.medium + StrategyTest.small
-        val from = keylineListOf(carouselMainAxisSize, 1, StrategyTest.large / 2) {
+        val from = keylineListOf(
+            carouselMainAxisSize = carouselMainAxisSize,
+            itemSpacing = 0f,
+            pivotIndex = 1,
+            pivotOffset = StrategyTest.large / 2
+        ) {
             add(StrategyTest.xSmall, isAnchor = true)
             add(StrategyTest.large)
             add(StrategyTest.medium)
@@ -103,9 +116,10 @@
             add(StrategyTest.xSmall, isAnchor = true)
         }
         val to = keylineListOf(
-            carouselMainAxisSize,
-            2,
-            StrategyTest.small + (StrategyTest.large / 2)
+            carouselMainAxisSize = carouselMainAxisSize,
+            itemSpacing = 0f,
+            pivotIndex = 2,
+            pivotOffset = StrategyTest.small + (StrategyTest.large / 2)
         ) {
             add(StrategyTest.xSmall, isAnchor = true)
             add(StrategyTest.small)
@@ -134,13 +148,21 @@
 
     @Test
     fun test_keylineListsShouldBeEqual() {
-        val keylineList1 = keylineListOf(120f, CarouselAlignment.Start) {
+        val keylineList1 = keylineListOf(
+            carouselMainAxisSize = 120f,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
             add(10f, true)
             add(100f)
             add(20f)
             add(10f, true)
         }
-        val keylineList2 = keylineListOf(120f, CarouselAlignment.Start) {
+        val keylineList2 = keylineListOf(
+            carouselMainAxisSize = 120f,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
             add(10f, true)
             add(100f)
             add(20f)
@@ -153,13 +175,21 @@
 
     @Test
     fun testDifferentSizedItem_keylineListsShouldNotBeEqual() {
-        val keylineList1 = keylineListOf(120f, CarouselAlignment.Start) {
+        val keylineList1 = keylineListOf(
+            carouselMainAxisSize = 120f,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
             add(11f, true)
             add(100f)
             add(20f)
             add(10f, true)
         }
-        val keylineList2 = keylineListOf(120f, CarouselAlignment.Start) {
+        val keylineList2 = keylineListOf(
+            carouselMainAxisSize = 120f,
+            itemSpacing = 0f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
             add(10f, true)
             add(100f)
             add(20f)
@@ -170,6 +200,80 @@
         assertThat(keylineList1.hashCode()).isNotEqualTo(keylineList2.hashCode())
     }
 
+    @Test
+    fun testStartKeylines_shouldAddSpacingBetweenItems() {
+        val keylines = keylineListOf(
+            carouselMainAxisSize = 380f,
+            itemSpacing = 8f,
+            carouselAlignment = CarouselAlignment.Start
+        ) {
+            add(10f, isAnchor = true)
+            add(186f)
+            add(122f)
+            add(56f)
+            add(10f, isAnchor = true)
+        }
+
+        val actualOffsets = keylines.map { it.offset }.toFloatArray()
+        val expectedOffsets = floatArrayOf(-13f, 93f, 255f, 352f, 393f)
+        assertThat(actualOffsets).isEqualTo(expectedOffsets)
+
+        val actualUnadjustedOffsets = keylines.map { it.unadjustedOffset }.toFloatArray()
+        val expectedUnadjustedOffsets = floatArrayOf(-101f, 93f, 287f, 481f, 675f)
+        assertThat(actualUnadjustedOffsets).isEqualTo(expectedUnadjustedOffsets)
+    }
+
+    @Test
+    fun testCenteredKeylines_shouldAddSpacingBetweenItems() {
+        val keylines = keylineListOf(
+            carouselMainAxisSize = 768f,
+            itemSpacing = 8f,
+            carouselAlignment = CarouselAlignment.Center
+        ) {
+            add(10f, isAnchor = true)
+            add(56f)
+            add(122f)
+            add(186f)
+            add(186f)
+            add(122f)
+            add(56f)
+            add(10f, isAnchor = true)
+        }
+
+        val actualOffsets = keylines.map { it.offset }.toFloatArray()
+        val expectedOffsets = floatArrayOf(-13f, 28f, 125f, 287f, 481f, 643f, 740f, 781f)
+        assertThat(actualOffsets).isEqualTo(expectedOffsets)
+
+        val actualUnadjustedOffsets = keylines.map { it.unadjustedOffset }.toFloatArray()
+        val expectedUnadjustedOffsets = floatArrayOf(
+            -295f, -101f, 93f, 287f, 481f, 675f, 869f, 1063f
+        )
+        assertThat(actualUnadjustedOffsets).isEqualTo(expectedUnadjustedOffsets)
+    }
+
+    @Test
+    fun testEndKeylines_shouldAddSpacingBetweenItems() {
+        val keylines = keylineListOf(
+            carouselMainAxisSize = 380f,
+            itemSpacing = 8f,
+            carouselAlignment = CarouselAlignment.End
+        ) {
+            add(10f, isAnchor = true)
+            add(56f)
+            add(122f)
+            add(186f)
+            add(10f, isAnchor = true)
+        }
+
+        val actualOffsets = keylines.map { it.offset }.toFloatArray()
+        val expectedOffsets = floatArrayOf(-13f, 28f, 125f, 287f, 393f)
+        assertThat(actualOffsets).isEqualTo(expectedOffsets)
+
+        val actualUnadjustedOffsets = keylines.map { it.unadjustedOffset }.toFloatArray()
+        val expectedUnadjustedOffsets = floatArrayOf(-295f, -101f, 93f, 287f, 481f)
+        assertThat(actualUnadjustedOffsets).isEqualTo(expectedUnadjustedOffsets)
+    }
+
     companion object {
         private const val LargeSize = 100f
         private const val SmallSize = 20f
@@ -184,7 +288,11 @@
             // [xs-s-s-m-l-l-m-s-s-xs]
             val carouselMainAxisSize =
                 (XSmallSize * 2) + (SmallSize * 4) + (MediumSize * 2) + (LargeSize * 2)
-            return keylineListOf(carouselMainAxisSize, CarouselAlignment.Center) {
+            return keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                carouselAlignment = CarouselAlignment.Center
+            ) {
                 add(XSmallSize, isAnchor = true)
                 add(SmallSize)
                 add(SmallSize)
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
index 9cd40a4..830559f 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
@@ -16,12 +16,14 @@
 
 package androidx.compose.material3.carousel
 
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.ui.unit.Density
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalMaterial3Api::class)
 @RunWith(JUnit4::class)
 class MultiBrowseTest {
 
@@ -37,7 +39,12 @@
             itemSpacing = 0f,
             itemCount = 10,
         )!!
-        val strategy = Strategy { keylineList }.apply(500f)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 500f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy.itemMainAxisSize).isEqualTo(itemSize)
     }
@@ -52,8 +59,13 @@
             itemSpacing = 0f,
             itemCount = 10,
             )!!
-        val strategy = Strategy { keylineList }.apply(100f)
-        val minSmallItemSize: Float = with(Density) { StrategyDefaults.MinSmallSize.toPx() }
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 100f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+        val minSmallItemSize: Float = with(Density) { CarouselDefaults.MinSmallItemSize.toPx() }
         val keylines = strategy.defaultKeylines
 
         // If the item size given is larger than the container, the adjusted keyline list from
@@ -68,7 +80,7 @@
 
     @Test
     fun testMultiBrowse_hasNoSmallItemsIfNotEnoughRoom() {
-        val minSmallItemSize: Float = with(Density) { StrategyDefaults.MinSmallSize.toPx() }
+        val minSmallItemSize: Float = with(Density) { CarouselDefaults.MinSmallItemSize.toPx() }
         val keylineList = multiBrowseKeylineList(
             density = Density,
             carouselMainAxisSize = minSmallItemSize,
@@ -76,7 +88,12 @@
             itemSpacing = 0f,
             itemCount = 10,
             )!!
-        val strategy = Strategy { keylineList }.apply(minSmallItemSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = minSmallItemSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
 
         assertThat(strategy.itemMainAxisSize).isEqualTo(minSmallItemSize)
@@ -98,7 +115,7 @@
 
     @Test
     fun testMultiBrowse_adjustsMediumSizeToBeProportional() {
-        val maxSmallItemSize: Float = with(Density) { StrategyDefaults.MaxSmallSize.toPx() }
+        val maxSmallItemSize: Float = with(Density) { CarouselDefaults.MaxSmallItemSize.toPx() }
         val preferredItemSize = 200f
         val carouselSize = preferredItemSize * 2 + maxSmallItemSize * 2
         val keylineList = multiBrowseKeylineList(
@@ -108,7 +125,12 @@
             itemSpacing = 0f,
             itemCount = 10,
             )!!
-        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = carouselSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
 
         // Assert that there's only one small item, and a medium item that has a size between
@@ -123,7 +145,7 @@
 
     @Test
     fun testMultiBrowse_withLessItemsThanKeylines() {
-        val maxSmallItemSize: Float = with(Density) { StrategyDefaults.MaxSmallSize.toPx() }
+        val maxSmallItemSize: Float = with(Density) { CarouselDefaults.MaxSmallItemSize.toPx() }
         val preferredItemSize = 200f
         val carouselSize = preferredItemSize * 2 + maxSmallItemSize * 2
         val keylineList = multiBrowseKeylineList(
@@ -133,7 +155,12 @@
             itemSpacing = 0f,
             itemCount = 3,
         )!!
-        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = carouselSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
 
         // We originally expect a keyline list of [xSmall-Large-Large-Medium-Small-xSmall], but with
@@ -143,4 +170,41 @@
         assertThat(keylines[2].isFocal).isTrue()
         assertThat(keylines[3].size).isLessThan(keylines[2].size)
     }
+
+    @Test
+    fun testMultiBrowse_adjustsForItemSpacing() {
+        val keylineList = multiBrowseKeylineList(
+            density = Density,
+            carouselMainAxisSize = 380f,
+            preferredItemSize = 186f,
+            itemSpacing = 8f,
+            itemCount = 10
+        )!!
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 380f,
+            itemSpacing = 8f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+
+        assertThat(keylineList.firstFocal.size).isEqualTo(186f)
+        // Ensure the first visible item is large and aligned with the start of the container
+        assertThat(keylineList.firstFocal.offset).isEqualTo(186f / 2)
+        // Ensure the last  visible item is aligned with the end of the container
+        assertThat(keylineList.lastNonAnchor.offset + (keylineList.lastNonAnchor.size / 2f))
+            .isEqualTo(380f)
+
+        assertThat(strategy.itemMainAxisSize).isEqualTo(186f)
+        val lastVisible = strategy.defaultKeylines[3]
+        assertThat(lastVisible.size).isEqualTo(56f)
+        assertThat(lastVisible.offset).isEqualTo(380f - (56f / 2f))
+
+        val maxScrollOffset = ((186f * 10) + (8f * 10)) - 380f
+        val defaultActualUnadjustedOffsets = strategy.getKeylineListForScrollOffset(
+            0f,
+            maxScrollOffset
+        ).map { it.unadjustedOffset }.toFloatArray()
+        val defaultExpectedUnadjustedOffsets = floatArrayOf(-101f, 93f, 287f, 481f, 675f)
+        assertThat(defaultActualUnadjustedOffsets).isEqualTo(defaultExpectedUnadjustedOffsets)
+    }
 }
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
index b19c365..595e34c 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/StrategyTest.kt
@@ -34,7 +34,12 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylineList = createStartAlignedKeylineList()
 
-        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
+        val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy.getKeylineListForScrollOffset(0f, maxScrollOffset))
             .isEqualTo(defaultKeylineList)
@@ -62,7 +67,12 @@
         val cutoff = 50f
         val defaultKeylineList = createStartAlignedCutoffKeylineList(cutoff = cutoff)
 
-        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
+        val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val endKeylineList = strategy.getEndKeylines()
 
         assertThat(defaultKeylineList.lastNonAnchor.cutoff).isEqualTo(cutoff)
@@ -81,7 +91,12 @@
         val cutoff = 50f
         val defaultKeylineList = createEndAlignedCutoffKeylineList(cutoff = cutoff)
 
-        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
+        val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val startKeylineList = strategy.getStartKeylines()
 
         assertThat(defaultKeylineList.firstNonAnchor.cutoff).isEqualTo(cutoff)
@@ -101,13 +116,23 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylines = createCenterAlignedKeylineList()
 
-        val strategy = Strategy { defaultKeylines }.apply(carouselMainAxisSize)
+        val strategy = Strategy { _, _ -> defaultKeylines }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         val startSteps = listOf(
             // default step - [xs | s s m l l m s s | xs]
             createCenterAlignedKeylineList(),
             // step 1 - [xs | s m l l m s s s | xs]
-            keylineListOf(carouselMainAxisSize, 3, (small * 1 + medium + (large / 2))) {
+            keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                pivotIndex = 3,
+                pivotOffset = (small * 1 + medium + (large / 2))
+            ) {
                 add(xSmall, isAnchor = true)
                 add(small)
                 add(medium)
@@ -120,7 +145,12 @@
                 add(xSmall, isAnchor = true)
             },
             // step 2 - [xs | m l l m s s s s | xs]
-            keylineListOf(carouselMainAxisSize, 2, medium + (large / 2)) {
+            keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                pivotIndex = 2,
+                pivotOffset = medium + (large / 2)
+            ) {
                 add(xSmall, isAnchor = true)
                 add(medium)
                 add(large)
@@ -133,7 +163,12 @@
                 add(xSmall, isAnchor = true)
             },
             // step 3 - [xs | l l m m s s s s | xs]
-            keylineListOf(carouselMainAxisSize, 1, large / 2) {
+            keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                pivotIndex = 1,
+                pivotOffset = large / 2
+            ) {
                 add(xSmall, isAnchor = true)
                 add(large)
                 add(large)
@@ -190,13 +225,23 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylines = createCenterAlignedKeylineList()
 
-        val strategy = Strategy { defaultKeylines }.apply(carouselMainAxisSize)
+        val strategy = Strategy { _, _ -> defaultKeylines }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         val endSteps = listOf(
             // default step
             createCenterAlignedKeylineList(),
             // step 1: Move a small item from after focal to beginning of focal
-            keylineListOf(carouselMainAxisSize, 5, (small * 3) + medium + (large / 2)) {
+            keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                pivotIndex = 5,
+                pivotOffset = (small * 3) + medium + (large / 2)
+            ) {
                 add(xSmall, isAnchor = true)
                 add(small)
                 add(small)
@@ -209,7 +254,12 @@
                 add(xSmall, isAnchor = true)
             },
             // step 2: Move another small from after focal to beginning of focal
-            keylineListOf(carouselMainAxisSize, 6, (small * 4) + medium + (large / 2)) {
+            keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                pivotIndex = 6,
+                pivotOffset = (small * 4) + medium + (large / 2)
+            ) {
                 add(xSmall, isAnchor = true)
                 add(small)
                 add(small)
@@ -223,7 +273,12 @@
             },
 
             // step 3: Move medium from after focal to beginning of focal
-            keylineListOf(carouselMainAxisSize, 7, (small * 4) + (medium * 2) + (large / 2)) {
+            keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                pivotIndex = 7,
+                pivotOffset = (small * 4) + (medium * 2) + (large / 2)
+            ) {
                 add(xSmall, isAnchor = true)
                 add(small)
                 add(small)
@@ -301,16 +356,26 @@
     @Test
     fun testStrategy_sameAvailableSpaceCreatesEqualObjects() {
         val itemSize = large
-        val itemSpacing = 0f
         val itemCount = 10
-        val strategy1 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
+        val itemSpacing = 0f
+        val strategy1 = Strategy { availableSpace, itemSpacingPx ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        val strategy2 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
+        val strategy2 = Strategy { availableSpace, itemSpacingPx ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        strategy1.apply(500f)
-        strategy2.apply(500f)
+        strategy1.apply(
+            availableSpace = 500f,
+            itemSpacing = itemSpacing,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+        strategy2.apply(
+            availableSpace = 500f,
+            itemSpacing = itemSpacing,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy1 == strategy2).isTrue()
         assertThat(strategy1.hashCode()).isEqualTo(strategy2.hashCode())
@@ -321,14 +386,24 @@
         val itemSize = large
         val itemSpacing = 0f
         val itemCount = 10
-        val strategy1 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
+        val strategy1 = Strategy { availableSpace, itemSpacingPx ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        val strategy2 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
+        val strategy2 = Strategy { availableSpace, itemSpacingPx ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        strategy1.apply(500f)
-        strategy2.apply(500f + 1f)
+        strategy1.apply(
+            availableSpace = 500f,
+            itemSpacing = itemSpacing,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+        strategy2.apply(
+            availableSpace = 500f + 1f,
+            itemSpacing = itemSpacing,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy1 == strategy2).isFalse()
         assertThat(strategy1.hashCode()).isNotEqualTo(strategy2.hashCode())
@@ -339,13 +414,18 @@
         val itemSize = large
         val itemSpacing = 0f
         val itemCount = 10
-        val strategy1 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
+        val strategy1 = Strategy { availableSpace, itemSpacingPx ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        val strategy2 = Strategy { availableSpace ->
-            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacing, itemCount)
+        val strategy2 = Strategy { availableSpace, itemSpacingPx ->
+            multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        strategy1.apply(500f)
+        strategy1.apply(
+            availableSpace = 500f,
+            itemSpacing = itemSpacing,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy1 == strategy2).isFalse()
         assertThat(strategy1.hashCode()).isNotEqualTo(strategy2.hashCode())
@@ -358,12 +438,157 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylineList = createStartAlignedKeylineList()
 
-        val strategy = Strategy { defaultKeylineList }.apply(carouselMainAxisSize)
+        val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy.getKeylineListForScrollOffset(0f, maxScrollOffset))
             .isEqualTo(defaultKeylineList)
     }
 
+    @Test
+    fun testStartKeylineStrategy_endStepsShouldAccountForItemSpacing() {
+        val strategy = Strategy { availableSpace, itemSpacing ->
+            keylineListOf(availableSpace, itemSpacing, CarouselAlignment.Start) {
+                add(10f, isAnchor = true)
+                add(186f)
+                add(122f)
+                add(56f)
+                add(10f, isAnchor = true)
+            }
+        }.apply(
+            availableSpace = 380f,
+            itemSpacing = 8f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+
+        val middleStep = strategy.endKeylineSteps[1]
+        val actualMiddleOffsets = middleStep.map { it.offset }.toFloatArray()
+        val expectedMiddleOffsets = floatArrayOf(-13f, 28f, 157f, 319f, 393f)
+        assertThat(actualMiddleOffsets).isEqualTo(expectedMiddleOffsets)
+
+        val actualMiddleUnadjustedOffsets = middleStep.map { it.unadjustedOffset }.toFloatArray()
+        val expectedMiddleUnadjustedOffsets = floatArrayOf(-231f, -37f, 157f, 351f, 545f)
+        assertThat(actualMiddleUnadjustedOffsets).isEqualTo(expectedMiddleUnadjustedOffsets)
+
+        val endStep = strategy.endKeylineSteps[2]
+        val actualEndOffsets = endStep.map { it.offset }.toFloatArray()
+        val expectedEndOffsets = floatArrayOf(-13f, 28f, 125f, 287f, 393f)
+        assertThat(actualEndOffsets).isEqualTo(expectedEndOffsets)
+
+        val actualEndUnadjustedOffsets = endStep.map { it.unadjustedOffset }.toFloatArray()
+        val expectedEndUnadjustedOffsets = floatArrayOf(-295f, -101f, 93f, 287f, 481f)
+        assertThat(actualEndUnadjustedOffsets).isEqualTo(expectedEndUnadjustedOffsets)
+    }
+
+    @Test
+    fun testCenterKeylineStrategy_startAndEndStepsShouldAccountForItemSpacing() {
+        val strategy = Strategy { availableSpace, itemSpacing ->
+            keylineListOf(availableSpace, itemSpacing, CarouselAlignment.Center) {
+                add(10f, isAnchor = true)
+                add(56f)
+                add(122f)
+                add(186f)
+                add(186f)
+                add(122f)
+                add(56f)
+                add(10f, isAnchor = true)
+            }
+        }.apply(
+            availableSpace = 768f,
+            itemSpacing = 8f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
+
+        assertThat(strategy.startKeylineSteps).hasSize(3)
+        assertThat(strategy.endKeylineSteps).hasSize(3)
+
+        val s1 = strategy.startKeylineSteps[1]
+        val s1ActualOffsets = s1.map { it.offset }.toFloatArray()
+        // Should move one small from start to end - xs, m, l, l, m, s, s, xs
+        val s1ExpectedOffsets = floatArrayOf(-13f, 61f, 223f, 417f, 579f, 676f, 740f, 781f)
+        assertThat(s1ActualOffsets).isEqualTo(s1ExpectedOffsets)
+
+        val s1ActualUnadjustedOffsets = s1.map { it.unadjustedOffset }.toFloatArray()
+        val s1ExpectedUnadjustedOffsets = floatArrayOf(
+            -165f, 29f, 223f, 417f, 611f, 805f, 999f, 1193f
+        )
+        assertThat(s1ActualUnadjustedOffsets).isEqualTo(s1ExpectedUnadjustedOffsets)
+
+        val s2 = strategy.startKeylineSteps[2]
+        val s2ActualOffsets = s2.map { it.offset }.toFloatArray()
+        // Should be - xs, l, l, m, m, s, s, xs
+        val s2ExpectedOffsets = floatArrayOf(-13f, 93f, 287f, 449f, 579f, 676f, 740f, 781f)
+        assertThat(s2ActualOffsets).isEqualTo(s2ExpectedOffsets)
+
+        val s2ActualUnadjustedOffsets = s2.map { it.unadjustedOffset }.toFloatArray()
+        val s2ExpectedUnadjustedOffsets = floatArrayOf(
+            -101f, 93f, 287f, 481f, 675f, 869f, 1063f, 1257f
+        )
+        assertThat(s2ActualUnadjustedOffsets).isEqualTo(s2ExpectedUnadjustedOffsets)
+
+        val e1 = strategy.endKeylineSteps[1]
+        val e1ActualOffsets = e1.map { it.offset }.toFloatArray()
+        // Should move one small item to start - xs, s, s, m, l, l, m, xs
+        val e1ExpectedOffsets = floatArrayOf(-13f, 28f, 92f, 189f, 351f, 545f, 707f, 781f)
+        assertThat(e1ActualOffsets).isEqualTo(e1ExpectedOffsets)
+
+        val e1ActualUnadjustedOffsets = e1.map { it.unadjustedOffset }.toFloatArray()
+        val e1ExpectedUnadjustedOffsets = floatArrayOf(
+            -425f, -231f, -37f, 157f, 351f, 545f, 739f, 933f
+        )
+        assertThat(e1ActualUnadjustedOffsets).isEqualTo(e1ExpectedUnadjustedOffsets)
+
+        val e2 = strategy.endKeylineSteps[2]
+        val e2ActualOffsets = e2.map { it.offset }.toFloatArray()
+        // Should move one medium item to end
+        val e2ExpectedOffsets = floatArrayOf(-13f, 28f, 92f, 189f, 319f, 481f, 675f, 781f)
+        assertThat(e2ActualOffsets).isEqualTo(e2ExpectedOffsets)
+
+        val e2ActualUnadjustedOffsets = e2.map { it.unadjustedOffset }.toFloatArray()
+        val e2ExpectedUnadjustedOffsets = floatArrayOf(
+            -489f, -295f, -101f, 93f, 287f, 481f, 675f, 869f
+        )
+        assertThat(e2ActualUnadjustedOffsets).isEqualTo(e2ExpectedUnadjustedOffsets)
+    }
+
+    @Test
+    fun testCenterStrategy_stepsShouldAccountForContentPadding() {
+        val strategy = Strategy { availableSpace, itemSpacing ->
+            keylineListOf(availableSpace, itemSpacing, CarouselAlignment.Center) {
+                add(10f, isAnchor = true)
+                add(50f)
+                add(100f)
+                add(200f)
+                add(100f)
+                add(50f)
+                add(10f, isAnchor = true)
+            }
+        }.apply(500f, 0f, 16f, 24f)
+
+        val lastStartStep = strategy.startKeylineSteps.last()
+
+        val firstFocalLeft = lastStartStep.firstFocal.offset -
+            (lastStartStep.firstFocal.size / 2f)
+        val lastNonAnchorRight = lastStartStep.lastNonAnchor.offset +
+            (lastStartStep.lastNonAnchor.size / 2f)
+        val lastEndStep = strategy.endKeylineSteps.last()
+        val lastFocalRight = lastEndStep.lastFocal.offset +
+            (lastEndStep.lastFocal.size / 2f)
+        val firstNonAnchorLeft = lastEndStep.firstNonAnchor.offset -
+            (lastEndStep.firstNonAnchor.size / 2f)
+
+        assertThat(firstFocalLeft).isEqualTo(16f)
+        assertThat(lastNonAnchorRight).isEqualTo(500f)
+        assertThat(lastFocalRight).isEqualTo(500f - 24f)
+        assertThat(firstNonAnchorLeft).isWithin(.01f).of(0f)
+    }
+
     private fun assertEqualWithFloatTolerance(
         tolerance: Float,
         actual: Keyline,
@@ -391,7 +616,11 @@
         private fun createCenterAlignedKeylineList(): KeylineList {
             // [xs | s s m l l m s s | xs]
             val carouselMainAxisSize = (small * 2) + medium + (large * 2) + medium + (small * 2)
-            return keylineListOf(carouselMainAxisSize, CarouselAlignment.Center) {
+            return keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = 0f,
+                carouselAlignment = CarouselAlignment.Center
+            ) {
                 add(xSmall, isAnchor = true)
                 add(small)
                 add(small)
@@ -407,7 +636,11 @@
 
         private fun createStartAlignedKeylineList(): KeylineList {
             // [xs | l m s | xs]
-            return keylineListOf(large + medium + small, CarouselAlignment.Start) {
+            return keylineListOf(
+                carouselMainAxisSize = large + medium + small,
+                itemSpacing = 0f,
+                carouselAlignment = CarouselAlignment.Start
+            ) {
                 add(xSmall, isAnchor = true)
                 add(large)
                 add(medium)
@@ -418,7 +651,11 @@
 
         private fun createStartAlignedCutoffKeylineList(cutoff: Float): KeylineList {
             // [xs | l m m | xs]
-            return keylineListOf(large + medium + medium, CarouselAlignment.Start) {
+            return keylineListOf(
+                carouselMainAxisSize = large + medium + medium,
+                itemSpacing = 0f,
+                carouselAlignment = CarouselAlignment.Start
+            ) {
                 add(xSmall, isAnchor = true)
                 add(large + cutoff)
                 add(medium)
@@ -429,7 +666,11 @@
 
         private fun createEndAlignedCutoffKeylineList(cutoff: Float): KeylineList {
             // [xs | m m l | xs]
-            return keylineListOf(large + medium + medium, CarouselAlignment.End) {
+            return keylineListOf(
+                carouselMainAxisSize = large + medium + medium,
+                itemSpacing = 0f,
+                carouselAlignment = CarouselAlignment.End
+            ) {
                 add(xSmall, isAnchor = true)
                 add(medium)
                 add(medium)
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
index f107932..cf6aa49 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/UncontainedTest.kt
@@ -16,12 +16,14 @@
 
 package androidx.compose.material3.carousel
 
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.ui.unit.Density
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalMaterial3Api::class)
 @RunWith(JUnit4::class)
 class UncontainedTest {
 
@@ -37,9 +39,14 @@
             itemSize = itemSize,
             itemSpacing = 0f
         )
-        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = carouselSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
-        val anchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
+        val anchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
 
         // A fullscreen layout should be [xSmall-large-xSmall] where the xSmall items are
         // outside the bounds of the carousel container and the large item takes up the
@@ -60,9 +67,14 @@
             itemSize = itemSize,
             itemSpacing = 0f
         )
-        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = carouselSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
-        val anchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
+        val anchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
 
         // The layout should be [xSmall-large-xSmall] where the xSmall items are
         // outside the bounds of the carousel container and the large item takes up the
@@ -86,9 +98,14 @@
             itemSize = itemSize,
             itemSpacing = 0f
         )
-        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = carouselSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
-        val rightAnchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
+        val rightAnchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
 
         // The layout should be [xSmall-large-large-large-medium-xSmall] where medium is a size
         // such that a third of it is cut off.
@@ -121,9 +138,14 @@
             itemSize = itemSize,
             itemSpacing = 0f
         )
-        val strategy = Strategy { keylineList }.apply(carouselSize)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = carouselSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
         val keylines = strategy.defaultKeylines
-        val rightAnchorSize = with(Density) { StrategyDefaults.AnchorSize.toPx() }
+        val rightAnchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
 
         // The layout should be [xSmall-large-large-large-medium-xSmall]
         assertThat(keylines.size).isEqualTo(6)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index 6b97ee4..df176a7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -41,6 +41,7 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.only
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.windowInsetsPadding
@@ -54,8 +55,10 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.rememberSaveable
@@ -82,6 +85,8 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isFinite
+import androidx.compose.ui.unit.isSpecified
 import androidx.compose.ui.util.fastFirst
 import kotlin.math.abs
 import kotlin.math.max
@@ -117,6 +122,10 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@Deprecated(
+    message = "Deprecated in favor of TopAppBar with expandedHeight parameter",
+    level = DeprecationLevel.HIDDEN
+)
 @ExperimentalMaterial3Api
 @Composable
 fun TopAppBar(
@@ -127,26 +136,23 @@
     windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
     colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
     scrollBehavior: TopAppBarScrollBehavior? = null
-) {
-    SingleRowTopAppBar(
-        modifier = modifier,
-        title = title,
-        titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
-        centeredTitle = false,
-        navigationIcon = navigationIcon,
-        actions = actions,
-        windowInsets = windowInsets,
-        colors = colors,
-        scrollBehavior = scrollBehavior
-    )
-}
+) = TopAppBar(
+    title = title,
+    modifier = modifier,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    expandedHeight = TopAppBarDefaults.TopAppBarExpandedHeight,
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
 
 /**
  * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design small top app bar</a>.
  *
  * Top app bars display information and actions at the top of a screen.
  *
- * This SmallTopAppBar has slots for a title, navigation icon, and actions.
+ * This small TopAppBar has slots for a title, navigation icon, and actions.
  *
  * ![Small top app bar image](https://developer.android.com/images/reference/androidx/compose/material3/small-top-app-bar.png)
  *
@@ -163,6 +169,10 @@
  * typically be an [IconButton] or [IconToggleButton].
  * @param actions the actions displayed at the end of the top app bar. This should typically be
  * [IconButton]s. The default layout here is a [Row], so icons inside will be placed horizontally.
+ * @param expandedHeight this app bar's height. When a specified [scrollBehavior] causes the app bar
+ * to collapse or expand, this value will represent the maximum height that the bar will be allowed
+ * to expand. This value must be specified and finite, otherwise it will be ignored and replaced
+ * with [TopAppBarDefaults.TopAppBarExpandedHeight].
  * @param windowInsets a window insets that app bar will respect.
  * @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app
  * bar in different states. See [TopAppBarDefaults.topAppBarColors].
@@ -170,27 +180,34 @@
  * applied by this top app bar to set up its height and colors. A scroll behavior is designed to
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
- * @deprecated use [TopAppBar] instead
  */
-@Deprecated(
-    message = "Use TopAppBar instead.",
-    replaceWith = ReplaceWith(
-        "TopAppBar(title, modifier, navigationIcon, actions, windowInsets, colors, " +
-            "scrollBehavior)"
-    ),
-    level = DeprecationLevel.WARNING
-)
 @ExperimentalMaterial3Api
 @Composable
-fun SmallTopAppBar(
+fun TopAppBar(
     title: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     navigationIcon: @Composable () -> Unit = {},
     actions: @Composable RowScope.() -> Unit = {},
+    expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight,
     windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
     colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
     scrollBehavior: TopAppBarScrollBehavior? = null
-) = TopAppBar(title, modifier, navigationIcon, actions, windowInsets, colors, scrollBehavior)
+) = SingleRowTopAppBar(
+    modifier = modifier,
+    title = title,
+    titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
+    centeredTitle = false,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    expandedHeight = if (expandedHeight == Dp.Unspecified || expandedHeight == Dp.Infinity) {
+        TopAppBarDefaults.TopAppBarExpandedHeight
+    } else {
+        expandedHeight
+    },
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
 
 /**
  * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design center-aligned small top app bar</a>.
@@ -221,6 +238,10 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@Deprecated(
+    message = "Deprecated in favor of CenterAlignedTopAppBar with expandedHeight parameter",
+    level = DeprecationLevel.HIDDEN
+)
 @ExperimentalMaterial3Api
 @Composable
 fun CenterAlignedTopAppBar(
@@ -231,20 +252,77 @@
     windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
     colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(),
     scrollBehavior: TopAppBarScrollBehavior? = null
-) {
-    SingleRowTopAppBar(
-        modifier = modifier,
-        title = title,
-        titleTextStyle =
-        MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
-        centeredTitle = true,
-        navigationIcon = navigationIcon,
-        actions = actions,
-        colors = colors,
-        windowInsets = windowInsets,
-        scrollBehavior = scrollBehavior
-    )
-}
+) = CenterAlignedTopAppBar(
+    title = title,
+    modifier = modifier,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    expandedHeight = TopAppBarDefaults.TopAppBarExpandedHeight,
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
+
+/**
+ * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design center-aligned small top app bar</a>.
+ *
+ * Top app bars display information and actions at the top of a screen.
+ *
+ * This small top app bar has a header title that is horizontally aligned to the center.
+ *
+ * ![Center-aligned top app bar image](https://developer.android.com/images/reference/androidx/compose/material3/center-aligned-top-app-bar.png)
+ *
+ * This CenterAlignedTopAppBar has slots for a title, navigation icon, and actions.
+ *
+ * A center aligned top app bar that uses a [scrollBehavior] to customize its nested scrolling
+ * behavior when working in conjunction with a scrolling content looks like:
+ * @sample androidx.compose.material3.samples.SimpleCenterAlignedTopAppBar
+ *
+ * @param title the title to be displayed in the top app bar
+ * @param modifier the [Modifier] to be applied to this top app bar
+ * @param navigationIcon the navigation icon displayed at the start of the top app bar. This should
+ * typically be an [IconButton] or [IconToggleButton].
+ * @param actions the actions displayed at the end of the top app bar. This should typically be
+ * [IconButton]s. The default layout here is a [Row], so icons inside will be placed horizontally.
+ * @param expandedHeight this app bar's height. When a specified [scrollBehavior] causes the app bar
+ * to collapse or expand, this value will represent the maximum height that the bar will be allowed
+ * to expand. This value must be specified and finite, otherwise it will be ignored and replaced
+ * with  [TopAppBarDefaults.TopAppBarExpandedHeight].
+ * @param windowInsets a window insets that app bar will respect.
+ * @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app
+ * bar in different states. See [TopAppBarDefaults.centerAlignedTopAppBarColors].
+ * @param scrollBehavior a [TopAppBarScrollBehavior] which holds various offset values that will be
+ * applied by this top app bar to set up its height and colors. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the top app bar appearance as the content
+ * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun CenterAlignedTopAppBar(
+    title: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+    expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight,
+    windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
+    colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(),
+    scrollBehavior: TopAppBarScrollBehavior? = null
+) = SingleRowTopAppBar(
+    modifier = modifier,
+    title = title,
+    titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
+    centeredTitle = true,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    expandedHeight = if (expandedHeight == Dp.Unspecified || expandedHeight == Dp.Infinity) {
+        TopAppBarDefaults.TopAppBarExpandedHeight
+    } else {
+        expandedHeight
+    },
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
 
 /**
  * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design medium top app bar</a>.
@@ -276,6 +354,11 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@Deprecated(
+    message = "Deprecated in favor of MediumTopAppBar with collapsedHeight and expandedHeight " +
+        "parameters",
+    level = DeprecationLevel.HIDDEN
+)
 @ExperimentalMaterial3Api
 @Composable
 fun MediumTopAppBar(
@@ -286,23 +369,94 @@
     windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
     colors: TopAppBarColors = TopAppBarDefaults.mediumTopAppBarColors(),
     scrollBehavior: TopAppBarScrollBehavior? = null
-) {
-    TwoRowsTopAppBar(
-        modifier = modifier,
-        title = title,
-        titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarMediumTokens.HeadlineFont),
-        smallTitleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
-        titleBottomPadding = MediumTitleBottomPadding,
-        smallTitle = title,
-        navigationIcon = navigationIcon,
-        actions = actions,
-        colors = colors,
-        windowInsets = windowInsets,
-        maxHeight = TopAppBarMediumTokens.ContainerHeight,
-        pinnedHeight = TopAppBarSmallTokens.ContainerHeight,
-        scrollBehavior = scrollBehavior
-    )
-}
+) = MediumTopAppBar(
+    title = title,
+    modifier = modifier,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    collapsedHeight = TopAppBarDefaults.MediumAppBarCollapsedHeight,
+    expandedHeight = TopAppBarDefaults.MediumAppBarExpandedHeight,
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
+
+/**
+ * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design medium top app bar</a>.
+ *
+ * Top app bars display information and actions at the top of a screen.
+ *
+ * ![Medium top app bar image](https://developer.android.com/images/reference/androidx/compose/material3/medium-top-app-bar.png)
+ *
+ * This MediumTopAppBar has slots for a title, navigation icon, and actions. In its default expanded
+ * state, the title is displayed in a second row under the navigation and actions.
+ *
+ * A medium top app bar that uses a [scrollBehavior] to customize its nested scrolling behavior when
+ * working in conjunction with scrolling content looks like:
+ * @sample androidx.compose.material3.samples.ExitUntilCollapsedMediumTopAppBar
+ *
+ * @param title the title to be displayed in the top app bar. This title will be used in the app
+ * bar's expanded and collapsed states, although in its collapsed state it will be composed with a
+ * smaller sized [TextStyle]
+ * @param modifier the [Modifier] to be applied to this top app bar
+ * @param navigationIcon the navigation icon displayed at the start of the top app bar. This should
+ * typically be an [IconButton] or [IconToggleButton].
+ * @param actions the actions displayed at the end of the top app bar. This should typically be
+ * [IconButton]s. The default layout here is a [Row], so icons inside will be placed horizontally.
+ * @param collapsedHeight this app bar height when collapsed by a provided [scrollBehavior]. This
+ * value must be specified and finite, otherwise it will be ignored and replaced with
+ * [TopAppBarDefaults.MediumAppBarCollapsedHeight].
+ * @param expandedHeight this app bar's maximum height. When a specified [scrollBehavior] causes the
+ * app bar to collapse or expand, this value will represent the maximum height that the app-bar will
+ * be allowed to expand. The expanded height is expected to be greater or equal to the
+ * [collapsedHeight], and the function will throw an [IllegalArgumentException] otherwise. Also,
+ * this value must be specified and finite, otherwise it will be ignored and replaced with
+ * [TopAppBarDefaults.MediumAppBarExpandedHeight].
+ * @param windowInsets a window insets that app bar will respect.
+ * @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app
+ * bar in different states. See [TopAppBarDefaults.mediumTopAppBarColors].
+ * @param scrollBehavior a [TopAppBarScrollBehavior] which holds various offset values that will be
+ * applied by this top app bar to set up its height and colors. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the top app bar appearance as the content
+ * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
+ * @throws IllegalArgumentException if the provided [expandedHeight] is smaller than the
+ * [collapsedHeight]
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun MediumTopAppBar(
+    title: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+    collapsedHeight: Dp = TopAppBarDefaults.MediumAppBarCollapsedHeight,
+    expandedHeight: Dp = TopAppBarDefaults.MediumAppBarExpandedHeight,
+    windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
+    colors: TopAppBarColors = TopAppBarDefaults.mediumTopAppBarColors(),
+    scrollBehavior: TopAppBarScrollBehavior? = null
+) = TwoRowsTopAppBar(
+    modifier = modifier,
+    title = title,
+    titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarMediumTokens.HeadlineFont),
+    smallTitleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
+    titleBottomPadding = MediumTitleBottomPadding,
+    smallTitle = title,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    collapsedHeight = if (collapsedHeight == Dp.Unspecified || collapsedHeight == Dp.Infinity) {
+        TopAppBarDefaults.MediumAppBarCollapsedHeight
+    } else {
+        collapsedHeight
+    },
+    expandedHeight = if (expandedHeight == Dp.Unspecified || expandedHeight == Dp.Infinity) {
+        TopAppBarDefaults.MediumAppBarExpandedHeight
+    } else {
+        expandedHeight
+    },
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
 
 /**
  * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design large top app bar</a>.
@@ -334,6 +488,11 @@
  * work in conjunction with a scrolled content to change the top app bar appearance as the content
  * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
  */
+@Deprecated(
+    message = "Deprecated in favor of LargeTopAppBar with collapsedHeight and expandedHeight " +
+        "parameters",
+    level = DeprecationLevel.HIDDEN
+)
 @ExperimentalMaterial3Api
 @Composable
 fun LargeTopAppBar(
@@ -344,23 +503,94 @@
     windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
     colors: TopAppBarColors = TopAppBarDefaults.largeTopAppBarColors(),
     scrollBehavior: TopAppBarScrollBehavior? = null
-) {
-    TwoRowsTopAppBar(
-        title = title,
-        titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarLargeTokens.HeadlineFont),
-        smallTitleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
-        titleBottomPadding = LargeTitleBottomPadding,
-        smallTitle = title,
-        modifier = modifier,
-        navigationIcon = navigationIcon,
-        actions = actions,
-        colors = colors,
-        windowInsets = windowInsets,
-        maxHeight = TopAppBarLargeTokens.ContainerHeight,
-        pinnedHeight = TopAppBarSmallTokens.ContainerHeight,
-        scrollBehavior = scrollBehavior
-    )
-}
+) = LargeTopAppBar(
+    title = title,
+    modifier = modifier,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    collapsedHeight = TopAppBarDefaults.LargeAppBarCollapsedHeight,
+    expandedHeight = TopAppBarDefaults.LargeAppBarExpandedHeight,
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
+
+/**
+ * <a href="https://m3.material.io/components/top-app-bar/overview" class="external" target="_blank">Material Design large top app bar</a>.
+ *
+ * Top app bars display information and actions at the top of a screen.
+ *
+ * ![Large top app bar image](https://developer.android.com/images/reference/androidx/compose/material3/large-top-app-bar.png)
+ *
+ * This LargeTopAppBar has slots for a title, navigation icon, and actions. In its default expanded
+ * state, the title is displayed in a second row under the navigation and actions.
+ *
+ * A large top app bar that uses a [scrollBehavior] to customize its nested scrolling behavior when
+ * working in conjunction with scrolling content looks like:
+ * @sample androidx.compose.material3.samples.ExitUntilCollapsedLargeTopAppBar
+ *
+ * @param title the title to be displayed in the top app bar. This title will be used in the app
+ * bar's expanded and collapsed states, although in its collapsed state it will be composed with a
+ * smaller sized [TextStyle]
+ * @param modifier the [Modifier] to be applied to this top app bar
+ * @param navigationIcon the navigation icon displayed at the start of the top app bar. This should
+ * typically be an [IconButton] or [IconToggleButton].
+ * @param actions the actions displayed at the end of the top app bar. This should typically be
+ * [IconButton]s. The default layout here is a [Row], so icons inside will be placed horizontally.
+ * @param collapsedHeight this app bar height when collapsed by a provided [scrollBehavior]. This
+ * value must be specified and finite, otherwise it will be ignored and replaced with
+ * [TopAppBarDefaults.LargeAppBarCollapsedHeight].
+ * @param expandedHeight this app bar's maximum height. When a specified [scrollBehavior] causes the
+ * app bar to collapse or expand, this value will represent the maximum height that the app-bar will
+ * be allowed to expand. The expanded height is expected to be greater or equal to the
+ * [collapsedHeight], and the function will throw an [IllegalArgumentException] otherwise. Also,
+ * this value must be specified and finite, otherwise it will be ignored and replaced with
+ * [TopAppBarDefaults.LargeAppBarExpandedHeight].
+ * @param windowInsets a window insets that app bar will respect.
+ * @param colors [TopAppBarColors] that will be used to resolve the colors used for this top app
+ * bar in different states. See [TopAppBarDefaults.largeTopAppBarColors].
+ * @param scrollBehavior a [TopAppBarScrollBehavior] which holds various offset values that will be
+ * applied by this top app bar to set up its height and colors. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the top app bar appearance as the content
+ * scrolls. See [TopAppBarScrollBehavior.nestedScrollConnection].
+ * @throws IllegalArgumentException if the provided [expandedHeight] is smaller to the
+ * [collapsedHeight]
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun LargeTopAppBar(
+    title: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    navigationIcon: @Composable () -> Unit = {},
+    actions: @Composable RowScope.() -> Unit = {},
+    collapsedHeight: Dp = TopAppBarDefaults.LargeAppBarCollapsedHeight,
+    expandedHeight: Dp = TopAppBarDefaults.LargeAppBarExpandedHeight,
+    windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
+    colors: TopAppBarColors = TopAppBarDefaults.largeTopAppBarColors(),
+    scrollBehavior: TopAppBarScrollBehavior? = null
+) = TwoRowsTopAppBar(
+    title = title,
+    titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarLargeTokens.HeadlineFont),
+    smallTitleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
+    titleBottomPadding = LargeTitleBottomPadding,
+    smallTitle = title,
+    modifier = modifier,
+    navigationIcon = navigationIcon,
+    actions = actions,
+    collapsedHeight = if (collapsedHeight == Dp.Unspecified || collapsedHeight == Dp.Infinity) {
+        TopAppBarDefaults.LargeAppBarCollapsedHeight
+    } else {
+        collapsedHeight
+    },
+    expandedHeight = if (expandedHeight == Dp.Unspecified || expandedHeight == Dp.Infinity) {
+        TopAppBarDefaults.LargeAppBarExpandedHeight
+    } else {
+        expandedHeight
+    },
+    windowInsets = windowInsets,
+    colors = colors,
+    scrollBehavior = scrollBehavior
+)
 
 /**
  * <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external" target="_blank">Material Design bottom app bar</a>.
@@ -737,42 +967,6 @@
         }
 
     /**
-     * Creates a [TopAppBarColors] for small [TopAppBar]s. The default implementation animates
-     * between the provided colors according to the Material Design specification.
-     *
-     * @param containerColor the container color
-     * @param scrolledContainerColor the container color when content is scrolled behind it
-     * @param navigationIconContentColor the content color used for the navigation icon
-     * @param titleContentColor the content color used for the title
-     * @param actionIconContentColor the content color used for actions
-     * @return the resulting [TopAppBarColors] used for the top app bar
-     * @deprecated use [topAppBarColors] instead
-     */
-    @Deprecated(
-        message = "Use topAppBarColors instead.",
-        replaceWith = ReplaceWith(
-            "topAppBarColors(containerColor, scrolledContainerColor, " +
-                "navigationIconContentColor, titleContentColor, actionIconContentColor)"
-        ),
-        level = DeprecationLevel.WARNING
-    )
-    @Composable
-    fun smallTopAppBarColors(
-        containerColor: Color = TopAppBarSmallTokens.ContainerColor.value,
-        scrolledContainerColor: Color = TopAppBarSmallTokens.OnScrollContainerColor.value,
-        navigationIconContentColor: Color = TopAppBarSmallTokens.LeadingIconColor.value,
-        titleContentColor: Color = TopAppBarSmallTokens.HeadlineColor.value,
-        actionIconContentColor: Color = TopAppBarSmallTokens.TrailingIconColor.value,
-    ): TopAppBarColors =
-        topAppBarColors(
-            containerColor,
-            scrolledContainerColor,
-            navigationIconContentColor,
-            titleContentColor,
-            actionIconContentColor
-        )
-
-    /**
      * Default insets to be used and consumed by the top app bars
      */
     val windowInsets: WindowInsets
@@ -808,21 +1002,23 @@
         titleContentColor: Color = Color.Unspecified,
         actionIconContentColor: Color = Color.Unspecified,
     ): TopAppBarColors = MaterialTheme.colorScheme.defaultCenterAlignedTopAppBarColors.copy(
-            containerColor,
-            scrolledContainerColor,
-            navigationIconContentColor,
-            titleContentColor,
-            actionIconContentColor
-        )
+        containerColor,
+        scrolledContainerColor,
+        navigationIconContentColor,
+        titleContentColor,
+        actionIconContentColor
+    )
 
     internal val ColorScheme.defaultCenterAlignedTopAppBarColors: TopAppBarColors
         get() {
             return defaultCenterAlignedTopAppBarColorsCached ?: TopAppBarColors(
                 containerColor = fromToken(TopAppBarSmallCenteredTokens.ContainerColor),
-                scrolledContainerColor =
-                    fromToken(TopAppBarSmallCenteredTokens.OnScrollContainerColor),
-                navigationIconContentColor =
-                    fromToken(TopAppBarSmallCenteredTokens.LeadingIconColor),
+                scrolledContainerColor = fromToken(
+                    TopAppBarSmallCenteredTokens.OnScrollContainerColor
+                ),
+                navigationIconContentColor = fromToken(
+                    TopAppBarSmallCenteredTokens.LeadingIconColor
+                ),
                 titleContentColor = fromToken(TopAppBarSmallCenteredTokens.HeadlineColor),
                 actionIconContentColor = fromToken(TopAppBarSmallCenteredTokens.TrailingIconColor),
             ).also {
@@ -858,21 +1054,21 @@
         titleContentColor: Color = Color.Unspecified,
         actionIconContentColor: Color = Color.Unspecified,
     ): TopAppBarColors = MaterialTheme.colorScheme.defaultMediumTopAppBarColors.copy(
-            containerColor,
-            scrolledContainerColor,
-            navigationIconContentColor,
-            titleContentColor,
-            actionIconContentColor
-        )
+        containerColor,
+        scrolledContainerColor,
+        navigationIconContentColor,
+        titleContentColor,
+        actionIconContentColor
+    )
 
     internal val ColorScheme.defaultMediumTopAppBarColors: TopAppBarColors
         get() {
             return defaultMediumTopAppBarColorsCached ?: TopAppBarColors(
                 containerColor = fromToken(TopAppBarMediumTokens.ContainerColor),
-            scrolledContainerColor = fromToken(TopAppBarSmallTokens.OnScrollContainerColor),
-            navigationIconContentColor = fromToken(TopAppBarMediumTokens.LeadingIconColor),
-            titleContentColor = fromToken(TopAppBarMediumTokens.HeadlineColor),
-            actionIconContentColor = fromToken(TopAppBarMediumTokens.TrailingIconColor),
+                scrolledContainerColor = fromToken(TopAppBarSmallTokens.OnScrollContainerColor),
+                navigationIconContentColor = fromToken(TopAppBarMediumTokens.LeadingIconColor),
+                titleContentColor = fromToken(TopAppBarMediumTokens.HeadlineColor),
+                actionIconContentColor = fromToken(TopAppBarMediumTokens.TrailingIconColor),
             ).also {
                 defaultMediumTopAppBarColorsCached = it
             }
@@ -886,7 +1082,7 @@
     @Composable
     fun largeTopAppBarColors() = MaterialTheme.colorScheme.defaultLargeTopAppBarColors
 
-/**
+    /**
      * Creates a [TopAppBarColors] for [LargeTopAppBar]s. The default implementation interpolates
      * between the provided colors as the top app bar scrolls according to the Material Design
      * specification.
@@ -906,12 +1102,12 @@
         titleContentColor: Color = Color.Unspecified,
         actionIconContentColor: Color = Color.Unspecified,
     ): TopAppBarColors = MaterialTheme.colorScheme.defaultLargeTopAppBarColors.copy(
-            containerColor,
-            scrolledContainerColor,
-            navigationIconContentColor,
-            titleContentColor,
-            actionIconContentColor
-        )
+        containerColor,
+        scrolledContainerColor,
+        navigationIconContentColor,
+        titleContentColor,
+        actionIconContentColor
+    )
 
     internal val ColorScheme.defaultLargeTopAppBarColors: TopAppBarColors
         get() {
@@ -1004,6 +1200,31 @@
             flingAnimationSpec = flingAnimationSpec,
             canScroll = canScroll
         )
+
+    /**
+     * The default expanded height of a [TopAppBar] and the [CenterAlignedTopAppBar].
+     */
+    val TopAppBarExpandedHeight: Dp = TopAppBarSmallTokens.ContainerHeight
+
+    /**
+     * The default height of a [MediumTopAppBar] when collapsed by a [TopAppBarScrollBehavior].
+     */
+    val MediumAppBarCollapsedHeight: Dp = TopAppBarSmallTokens.ContainerHeight
+
+    /**
+     * The default expanded height of a [MediumTopAppBar].
+     */
+    val MediumAppBarExpandedHeight: Dp = TopAppBarMediumTokens.ContainerHeight
+
+    /**
+     * The default height of a [LargeTopAppBar] when collapsed by a [TopAppBarScrollBehavior].
+     */
+    val LargeAppBarCollapsedHeight: Dp = TopAppBarSmallTokens.ContainerHeight
+
+    /**
+     * The default expanded height of a [LargeTopAppBar].
+     */
+    val LargeAppBarExpandedHeight: Dp = TopAppBarLargeTokens.ContainerHeight
 }
 
 /**
@@ -1167,7 +1388,7 @@
         navigationIconContentColor: Color = this.navigationIconContentColor,
         titleContentColor: Color = this.titleContentColor,
         actionIconContentColor: Color = this.actionIconContentColor,
-        ) = TopAppBarColors(
+    ) = TopAppBarColors(
         containerColor.takeOrElse { this.containerColor },
         scrolledContainerColor.takeOrElse { this.scrolledContainerColor },
         navigationIconContentColor.takeOrElse { this.navigationIconContentColor },
@@ -1433,7 +1654,7 @@
     initialContentOffset
 )
 
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 @Stable
 private class BottomAppBarStateImpl(
     initialHeightOffsetLimit: Float,
@@ -1481,7 +1702,7 @@
  * @param canScroll a callback used to determine whether scroll events are to be
  * handled by this [ExitAlwaysScrollBehavior]
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 private class ExitAlwaysScrollBehavior(
     override val state: BottomAppBarState,
     override val snapAnimationSpec: AnimationSpec<Float>?,
@@ -1525,7 +1746,7 @@
  * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
  * after the fling settles.
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 private suspend fun settleAppBarBottom(
     state: BottomAppBarState,
     velocity: Float,
@@ -1603,17 +1824,22 @@
     centeredTitle: Boolean,
     navigationIcon: @Composable () -> Unit,
     actions: @Composable RowScope.() -> Unit,
+    expandedHeight: Dp,
     windowInsets: WindowInsets,
     colors: TopAppBarColors,
     scrollBehavior: TopAppBarScrollBehavior?
 ) {
-    // Sets the app bar's height offset to collapse the entire bar's height when content is
-    // scrolled.
-    val heightOffsetLimit =
-        with(LocalDensity.current) { -TopAppBarSmallTokens.ContainerHeight.toPx() }
+    require(expandedHeight.isSpecified && expandedHeight.isFinite) {
+        "The expandedHeight is expected to be specified and finite"
+    }
+    val expandedHeightPx = with(LocalDensity.current) {
+        expandedHeight.toPx().coerceAtLeast(0f)
+    }
     SideEffect {
-        if (scrollBehavior?.state?.heightOffsetLimit != heightOffsetLimit) {
-            scrollBehavior?.state?.heightOffsetLimit = heightOffsetLimit
+        // Sets the app bar's height offset to collapse the entire bar's height when content is
+        // scrolled.
+        if (scrollBehavior?.state?.heightOffsetLimit != -expandedHeightPx) {
+            scrollBehavior?.state?.heightOffsetLimit = -expandedHeightPx
         }
     }
 
@@ -1621,10 +1847,15 @@
     // ensures that the colors will adjust whether the app bar behavior is pinned or scrolled.
     // This may potentially animate or interpolate a transition between the container-color and the
     // container's scrolled-color according to the app bar's scroll state.
-    val colorTransitionFraction = scrollBehavior?.state?.overlappedFraction ?: 0f
-    val fraction = if (colorTransitionFraction > 0.01f) 1f else 0f
+    val colorTransitionFraction by remember {
+        // derivedStateOf to prevent redundant recompositions when the content scrolls.
+        derivedStateOf {
+            val overlappingFraction = scrollBehavior?.state?.overlappedFraction ?: 0f
+            if (overlappingFraction > 0.01f) 1f else 0f
+        }
+    }
     val appBarContainerColor by animateColorAsState(
-        targetValue = colors.containerColor(fraction),
+        targetValue = colors.containerColor(colorTransitionFraction),
         animationSpec = spring(stiffness = Spring.StiffnessMediumLow)
     )
 
@@ -1642,7 +1873,7 @@
         Modifier.draggable(
             orientation = Orientation.Vertical,
             state = rememberDraggableState { delta ->
-                scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+                scrollBehavior.state.heightOffset += delta
             },
             onDragStopped = { velocity ->
                 settleAppBar(
@@ -1662,16 +1893,13 @@
     // The height of the app bar is determined by subtracting the bar's height offset from the
     // app bar's defined constant height value (i.e. the ContainerHeight token).
     Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
-        val height = LocalDensity.current.run {
-            TopAppBarSmallTokens.ContainerHeight.toPx() + (scrollBehavior?.state?.heightOffset
-                ?: 0f)
-        }
         TopAppBarLayout(
             modifier = Modifier
                 .windowInsetsPadding(windowInsets)
                 // clip after padding so we don't show the title over the inset area
-                .clipToBounds(),
-            heightPx = height,
+                .clipToBounds()
+                .heightIn(max = expandedHeight),
+            scrolledOffset = { scrollBehavior?.state?.heightOffset ?: 0f },
             navigationIconContentColor = colors.navigationIconContentColor,
             titleContentColor = colors.titleContentColor,
             actionIconContentColor = colors.actionIconContentColor,
@@ -1692,9 +1920,6 @@
 /**
  * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
  * composables.
- *
- * @throws [IllegalArgumentException] if the given [maxHeight] is equal or smaller than the
- * [pinnedHeight]
  */
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -1707,31 +1932,35 @@
     smallTitleTextStyle: TextStyle,
     navigationIcon: @Composable () -> Unit,
     actions: @Composable RowScope.() -> Unit,
+    collapsedHeight: Dp,
+    expandedHeight: Dp,
     windowInsets: WindowInsets,
     colors: TopAppBarColors,
-    maxHeight: Dp,
-    pinnedHeight: Dp,
     scrollBehavior: TopAppBarScrollBehavior?
 ) {
-    if (maxHeight <= pinnedHeight) {
-        throw IllegalArgumentException(
-            "A TwoRowsTopAppBar max height should be greater than its pinned height"
-        )
+    require(collapsedHeight.isSpecified && collapsedHeight.isFinite) {
+        "The collapsedHeight is expected to be specified and finite"
     }
-    val pinnedHeightPx: Float
-    val maxHeightPx: Float
+    require(expandedHeight.isSpecified && expandedHeight.isFinite) {
+        "The expandedHeight is expected to be specified and finite"
+    }
+    require(expandedHeight >= collapsedHeight) {
+        "The expandedHeight is expected to be greater or equal to the collapsedHeight"
+    }
+    val expandedHeightPx: Float
+    val collapsedHeightPx: Float
     val titleBottomPaddingPx: Int
     LocalDensity.current.run {
-        pinnedHeightPx = pinnedHeight.toPx()
-        maxHeightPx = maxHeight.toPx()
+        expandedHeightPx = expandedHeight.toPx()
+        collapsedHeightPx = collapsedHeight.toPx()
         titleBottomPaddingPx = titleBottomPadding.roundToPx()
     }
 
     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
     // visible when collapsed.
     SideEffect {
-        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
-            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
+        if (scrollBehavior?.state?.heightOffsetLimit != collapsedHeightPx - expandedHeightPx) {
+            scrollBehavior?.state?.heightOffsetLimit = collapsedHeightPx - expandedHeightPx
         }
     }
 
@@ -1763,7 +1992,7 @@
         Modifier.draggable(
             orientation = Orientation.Vertical,
             state = rememberDraggableState { delta ->
-                scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta
+                scrollBehavior.state.heightOffset += delta
             },
             onDragStopped = { velocity ->
                 settleAppBar(
@@ -1784,13 +2013,12 @@
                 modifier = Modifier
                     .windowInsetsPadding(windowInsets)
                     // clip after padding so we don't show the title over the inset area
-                    .clipToBounds(),
-                heightPx = pinnedHeightPx,
-                navigationIconContentColor =
-                colors.navigationIconContentColor,
+                    .clipToBounds()
+                    .heightIn(max = collapsedHeight),
+                scrolledOffset = { 0f },
+                navigationIconContentColor = colors.navigationIconContentColor,
                 titleContentColor = colors.titleContentColor,
-                actionIconContentColor =
-                colors.actionIconContentColor,
+                actionIconContentColor = colors.actionIconContentColor,
                 title = smallTitle,
                 titleTextStyle = smallTitleTextStyle,
                 titleAlpha = topTitleAlpha,
@@ -1806,14 +2034,12 @@
                     // only apply the horizontal sides of the window insets padding, since the top
                     // padding will always be applied by the layout above
                     .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal))
-                    .clipToBounds(),
-                heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
-                    ?: 0f),
-                navigationIconContentColor =
-                colors.navigationIconContentColor,
+                    .clipToBounds()
+                    .heightIn(max = expandedHeight - collapsedHeight),
+                scrolledOffset = { scrollBehavior?.state?.heightOffset ?: 0f },
+                navigationIconContentColor = colors.navigationIconContentColor,
                 titleContentColor = colors.titleContentColor,
-                actionIconContentColor =
-                colors.actionIconContentColor,
+                actionIconContentColor = colors.actionIconContentColor,
                 title = title,
                 titleTextStyle = titleTextStyle,
                 titleAlpha = bottomTitleAlpha,
@@ -1833,7 +2059,9 @@
  * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
  * the actions are optional.
  *
- * @param heightPx the total height this layout is capped to
+ * @param modifier a [Modifier]
+ * @param scrolledOffset a [ScrolledOffset] that provides the app bar offset in pixels (note that
+ * when the app bar is scrolled, the lambda will output negative values)
  * @param navigationIconContentColor the content color that will be applied via a
  * [LocalContentColor] when composing the navigation icon
  * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing
@@ -1842,7 +2070,6 @@
  * when composing the action icons
  * @param title the top app bar title (header)
  * @param titleTextStyle the title's text style
- * @param modifier a [Modifier]
  * @param titleAlpha the title's alpha
  * @param titleVerticalArrangement the title's vertical arrangement
  * @param titleHorizontalArrangement the title's horizontal arrangement
@@ -1857,7 +2084,7 @@
 @Composable
 private fun TopAppBarLayout(
     modifier: Modifier,
-    heightPx: Float,
+    scrolledOffset: ScrolledOffset,
     navigationIconContentColor: Color,
     titleContentColor: Color,
     actionIconContentColor: Color,
@@ -1893,7 +2120,8 @@
                 ProvideContentColorTextStyle(
                     contentColor = titleContentColor,
                     textStyle = titleTextStyle,
-                    content = title)
+                    content = title
+                )
             }
             Box(
                 Modifier
@@ -1933,7 +2161,16 @@
                 0
             }
 
-        val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()
+        // Subtract the scrolledOffset from the maxHeight. The scrolledOffset is expected to be
+        // equal or smaller than zero.
+        val scrolledOffsetValue = scrolledOffset.offset()
+        val heightOffset = if (scrolledOffsetValue.isNaN()) 0 else scrolledOffsetValue.roundToInt()
+
+        val layoutHeight = if (constraints.maxHeight == Constraints.Infinity) {
+            constraints.maxHeight
+        } else {
+            constraints.maxHeight + heightOffset
+        }
 
         layout(constraints.maxWidth, layoutHeight) {
             // Navigation icon
@@ -1975,11 +2212,25 @@
                     // Apply bottom padding from the title's baseline only when the Arrangement is
                     // "Bottom".
                     Arrangement.Bottom ->
-                        if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
-                        else layoutHeight - titlePlaceable.height - max(
-                            0,
-                            titleBottomPadding - titlePlaceable.height + titleBaseline
-                        )
+                        if (titleBottomPadding == 0) {
+                            layoutHeight - titlePlaceable.height
+                        } else {
+                            // Calculate the actual padding from the bottom of the title, taking
+                            // into account its baseline.
+                            val paddingFromBottom =
+                                titleBottomPadding - (titlePlaceable.height - titleBaseline)
+                            // Adjust the bottom padding to a smaller number if there is no room to
+                            // fit the title.
+                            val heightWithPadding = paddingFromBottom + titlePlaceable.height
+                            val adjustedBottomPadding =
+                                if (heightWithPadding > constraints.maxHeight) {
+                                    paddingFromBottom - (heightWithPadding - constraints.maxHeight)
+                                } else {
+                                    paddingFromBottom
+                                }
+
+                            layoutHeight - titlePlaceable.height - max(0, adjustedBottomPadding)
+                        }
                     // Arrangement.Top
                     else -> 0
                 }
@@ -1995,6 +2246,13 @@
 }
 
 /**
+ * A functional interface for providing an app-bar scroll offset.
+ */
+private fun interface ScrolledOffset {
+    fun offset(): Float
+}
+
+/**
  * Returns a [TopAppBarScrollBehavior] that only adjusts its content offset, without adjusting any
  * properties that affect the height of a top app bar.
  *
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
index 854a7a9..a4c1394 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
@@ -448,6 +448,8 @@
 
     internal var defaultNavigationRailItemColorsCached: NavigationRailItemColors? = null
 
+    internal var defaultExpressiveNavigationBarItemColorsCached: NavigationItemColors? = null
+
     internal var defaultRadioButtonColorsCached: RadioButtonColors? = null
 
     @OptIn(ExperimentalMaterial3Api::class)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExpressiveNavigationBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExpressiveNavigationBar.kt
index 8887c16..c7b0179 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExpressiveNavigationBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ExpressiveNavigationBar.kt
@@ -119,19 +119,17 @@
  *
  * TODO: Remove internal.
  */
-@ExperimentalMaterial3Api
 internal object ExpressiveNavigationBarItemDefaults {
     /**
      * Creates a [NavigationItemColors] with the provided colors according to the Material
      * specification.
      */
     @Composable
-    fun colors() = MaterialTheme.colorScheme.expressiveNavigationBarItemColors
+    fun colors() = MaterialTheme.colorScheme.defaultExpressiveNavigationBarItemColors
 
-    // TODO: Add a cached expressiveNavigationBarItemColors.
-    internal val ColorScheme.expressiveNavigationBarItemColors: NavigationItemColors
+    internal val ColorScheme.defaultExpressiveNavigationBarItemColors: NavigationItemColors
         get() {
-            return NavigationItemColors(
+            return defaultExpressiveNavigationBarItemColorsCached ?: NavigationItemColors(
                 selectedIconColor = fromToken(ActiveIconColor),
                 selectedTextColor = fromToken(ActiveLabelTextColor),
                 selectedIndicatorColor = fromToken(ActiveIndicatorColor),
@@ -139,7 +137,9 @@
                 unselectedTextColor = fromToken(InactiveLabelTextColor),
                 disabledIconColor = fromToken(InactiveIconColor).copy(alpha = DisabledAlpha),
                 disabledTextColor = fromToken(InactiveLabelTextColor).copy(alpha = DisabledAlpha),
-            )
+            ).also {
+                defaultExpressiveNavigationBarItemColorsCached = it
+            }
         }
 }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt
index 97c25f6..5a2180c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/InteractiveComponentSize.kt
@@ -20,7 +20,6 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
@@ -30,8 +29,10 @@
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.coerceAtLeast
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.isSpecified
 import kotlin.math.roundToInt
 
 /**
@@ -73,24 +74,23 @@
     Modifier.Node(),
     CompositionLocalConsumerModifierNode,
     LayoutModifierNode {
-
-    @OptIn(ExperimentalMaterial3Api::class)
     override fun MeasureScope.measure(
         measurable: Measurable,
         constraints: Constraints
     ): MeasureResult {
-        val size = minimumInteractiveComponentSize
+        val size = currentValueOf(LocalMinimumInteractiveComponentSize).coerceAtLeast(0.dp)
         val placeable = measurable.measure(constraints)
-        val enforcement = isAttached && currentValueOf(LocalMinimumInteractiveComponentEnforcement)
+        val enforcement = isAttached && (size.isSpecified && size > 0.dp)
 
+        val sizePx = if (size.isSpecified) size.roundToPx() else 0
         // Be at least as big as the minimum dimension in both dimensions
         val width = if (enforcement) {
-            maxOf(placeable.width, size.width.roundToPx())
+            maxOf(placeable.width, sizePx)
         } else {
             placeable.width
         }
         val height = if (enforcement) {
-            maxOf(placeable.height, size.height.roundToPx())
+            maxOf(placeable.height, sizePx)
         } else {
             placeable.height
         }
@@ -114,57 +114,24 @@
 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
 @get:ExperimentalMaterial3Api
 @ExperimentalMaterial3Api
+@Deprecated(
+    message = "Use LocalMinimumInteractiveComponentSize with Dp.Unspecified to turn off " +
+        "enforcement instead.",
+    replaceWith = ReplaceWith(
+        "LocalMinimumInteractiveComponentSize"
+    ),
+    level = DeprecationLevel.WARNING
+)
 val LocalMinimumInteractiveComponentEnforcement: ProvidableCompositionLocal<Boolean> =
     staticCompositionLocalOf { true }
 
 /**
- * CompositionLocal that configures whether Material components that have a visual size that is
- * lower than the minimum touch target size for accessibility (such as [Button]) will include
- * extra space outside the component to ensure that they are accessible. If set to false there
- * will be no extra space, and so it is possible that if the component is placed near the edge of
- * a layout / near to another component without any padding, there will not be enough space for
- * an accessible touch target.
+ * CompositionLocal that configures the minimum touch target size for Material components
+ * (such as [Button]) to ensure they are accessible. If a component has a visual size
+ * that is lower than the minimum touch target size, extra space outside the component will be
+ * included. If set to [Dp.Unspecified] there will be no extra space, and so it is possible that if the
+ * component is placed near the edge of a layout / near to another component without any padding,
+ * there will not be enough space for an accessible touch target.
  */
-@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-@get:ExperimentalMaterial3Api
-@ExperimentalMaterial3Api
-@Deprecated(
-    message = "Use LocalMinimumInteractiveComponentEnforcement instead.",
-    replaceWith = ReplaceWith(
-        "LocalMinimumInteractiveComponentEnforcement"
-    ),
-    level = DeprecationLevel.WARNING
-)
-val LocalMinimumTouchTargetEnforcement: ProvidableCompositionLocal<Boolean> =
-    LocalMinimumInteractiveComponentEnforcement
-
-private class MinimumInteractiveComponentSizeModifier(val size: DpSize) : LayoutModifier {
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-
-        val placeable = measurable.measure(constraints)
-
-        // Be at least as big as the minimum dimension in both dimensions
-        val width = maxOf(placeable.width, size.width.roundToPx())
-        val height = maxOf(placeable.height, size.height.roundToPx())
-
-        return layout(width, height) {
-            val centerX = ((width - placeable.width) / 2f).roundToInt()
-            val centerY = ((height - placeable.height) / 2f).roundToInt()
-            placeable.place(centerX, centerY)
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        val otherModifier = other as? MinimumInteractiveComponentSizeModifier ?: return false
-        return size == otherModifier.size
-    }
-
-    override fun hashCode(): Int {
-        return size.hashCode()
-    }
-}
-
-private val minimumInteractiveComponentSize: DpSize = DpSize(48.dp, 48.dp)
+val LocalMinimumInteractiveComponentSize: ProvidableCompositionLocal<Dp> =
+    staticCompositionLocalOf { 48.dp }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index ca40239..e9da174 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -51,6 +51,7 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -61,8 +62,11 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalDensity
@@ -79,6 +83,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.launch
@@ -106,7 +111,6 @@
  */
 @Suppress("NotCloseable")
 @Stable
-@OptIn(ExperimentalMaterial3Api::class)
 class DrawerState(
     initialValue: DrawerValue,
     confirmStateChange: (DrawerValue) -> Boolean = { true }
@@ -303,7 +307,6 @@
  * @param scrimColor color of the scrim that obscures content when the drawer is open
  * @param content content of the rest of the UI
  */
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ModalNavigationDrawer(
     drawerContent: @Composable () -> Unit,
@@ -406,7 +409,6 @@
  * @param gesturesEnabled whether or not the drawer can be interacted by gestures
  * @param content content of the rest of the UI
  */
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun DismissibleNavigationDrawer(
     drawerContent: @Composable () -> Unit,
@@ -510,6 +512,10 @@
 
 /**
  * Content inside of a modal navigation drawer.
+
+ * Note: This version of [ModalDrawerSheet] does not handle back by default. For automatic back
+ * handling and predictive back animations on Android 14+, use the [ModalDrawerSheet] that accepts
+ * `drawerState` as a param.
  *
  * @param modifier the [Modifier] to be applied to this drawer's content
  * @param drawerShape defines the shape of this drawer's container
@@ -535,6 +541,7 @@
     content: @Composable ColumnScope.() -> Unit
 ) {
     DrawerSheet(
+        drawerPredictiveBackState = null,
         windowInsets,
         modifier,
         drawerShape,
@@ -546,8 +553,58 @@
 }
 
 /**
+ * Content inside of a modal navigation drawer.
+ *
+ * Note: This version of [ModalDrawerSheet] requires a [drawerState] to be provided and will handle
+ * back by default for all Android versions, as well as animate during predictive back on Android
+ * 14+.
+ *
+ * @param drawerState state of the drawer
+ * @param modifier the [Modifier] to be applied to this drawer's content
+ * @param drawerShape defines the shape of this drawer's container
+ * @param drawerContainerColor the color used for the background of this drawer. Use
+ * [Color.Transparent] to have no color.
+ * @param drawerContentColor the preferred color for content inside this drawer. Defaults to either
+ * the matching content color for [drawerContainerColor], or to the current [LocalContentColor] if
+ * [drawerContainerColor] is not a color from the theme.
+ * @param drawerTonalElevation when [drawerContainerColor] is [ColorScheme.surface], a translucent
+ * primary color overlay is applied on top of the container. A higher tonal elevation value will
+ * result in a darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param windowInsets a window insets for the sheet.
+ * @param content content inside of a modal navigation drawer
+ */
+@Composable
+fun ModalDrawerSheet(
+    drawerState: DrawerState,
+    modifier: Modifier = Modifier,
+    drawerShape: Shape = DrawerDefaults.shape,
+    drawerContainerColor: Color = DrawerDefaults.modalContainerColor,
+    drawerContentColor: Color = contentColorFor(drawerContainerColor),
+    drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation,
+    windowInsets: WindowInsets = DrawerDefaults.windowInsets,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    DrawerPredictiveBackHandler(drawerState) { drawerPredictiveBackState ->
+        DrawerSheet(
+            drawerPredictiveBackState,
+            windowInsets,
+            modifier,
+            drawerShape,
+            drawerContainerColor,
+            drawerContentColor,
+            drawerTonalElevation,
+            content
+        )
+    }
+}
+
+/**
  * Content inside of a dismissible navigation drawer.
  *
+ * Note: This version of [DismissibleDrawerSheet] does not handle back by default. For automatic
+ * back handling and predictive back animations on Android 14+, use the [DismissibleDrawerSheet]
+ * that accepts `drawerState` as a param.
+ *
  * @param modifier the [Modifier] to be applied to this drawer's content
  * @param drawerShape defines the shape of this drawer's container
  * @param drawerContainerColor the color used for the background of this drawer. Use
@@ -572,6 +629,7 @@
     content: @Composable ColumnScope.() -> Unit
 ) {
     DrawerSheet(
+        drawerPredictiveBackState = null,
         windowInsets,
         modifier,
         drawerShape,
@@ -583,6 +641,52 @@
 }
 
 /**
+ * Content inside of a dismissible navigation drawer.
+
+ * Note: This version of [DismissibleDrawerSheet] requires a [drawerState] to be provided and will
+ * handle back by default for all Android versions, as well as animate during predictive back on
+ * Android 14+.
+ *
+ * @param drawerState state of the drawer
+ * @param modifier the [Modifier] to be applied to this drawer's content
+ * @param drawerShape defines the shape of this drawer's container
+ * @param drawerContainerColor the color used for the background of this drawer. Use
+ * [Color.Transparent] to have no color.
+ * @param drawerContentColor the preferred color for content inside this drawer. Defaults to either
+ * the matching content color for [drawerContainerColor], or to the current [LocalContentColor] if
+ * [drawerContainerColor] is not a color from the theme.
+ * @param drawerTonalElevation when [drawerContainerColor] is [ColorScheme.surface], a translucent
+ * primary color overlay is applied on top of the container. A higher tonal elevation value will
+ * result in a darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param windowInsets a window insets for the sheet.
+ * @param content content inside of a dismissible navigation drawer
+ */
+@Composable
+fun DismissibleDrawerSheet(
+    drawerState: DrawerState,
+    modifier: Modifier = Modifier,
+    drawerShape: Shape = RectangleShape,
+    drawerContainerColor: Color = DrawerDefaults.standardContainerColor,
+    drawerContentColor: Color = contentColorFor(drawerContainerColor),
+    drawerTonalElevation: Dp = DrawerDefaults.DismissibleDrawerElevation,
+    windowInsets: WindowInsets = DrawerDefaults.windowInsets,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    DrawerPredictiveBackHandler(drawerState) { drawerPredictiveBackState ->
+        DrawerSheet(
+            drawerPredictiveBackState,
+            windowInsets,
+            modifier,
+            drawerShape,
+            drawerContainerColor,
+            drawerContentColor,
+            drawerTonalElevation,
+            content
+        )
+    }
+}
+
+/**
  * Content inside of a permanent navigation drawer.
  *
  * @param modifier the [Modifier] to be applied to this drawer's content
@@ -610,6 +714,7 @@
 ) {
     val navigationMenu = getString(Strings.NavigationMenu)
     DrawerSheet(
+        drawerPredictiveBackState = null,
         windowInsets,
         modifier.semantics {
             paneTitle = navigationMenu
@@ -623,7 +728,8 @@
 }
 
 @Composable
-private fun DrawerSheet(
+internal fun DrawerSheet(
+    drawerPredictiveBackState: DrawerPredictiveBackState?,
     windowInsets: WindowInsets,
     modifier: Modifier = Modifier,
     drawerShape: Shape = RectangleShape,
@@ -632,30 +738,93 @@
     drawerTonalElevation: Dp = DrawerDefaults.PermanentDrawerElevation,
     content: @Composable ColumnScope.() -> Unit
 ) {
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    val predictiveBackDrawerContainerModifier =
+        if (drawerPredictiveBackState != null) Modifier.predictiveBackDrawerContainer(
+            drawerPredictiveBackState,
+            isRtl
+        ) else Modifier
     Surface(
         modifier = modifier
             .sizeIn(
                 minWidth = MinimumDrawerWidth,
                 maxWidth = DrawerDefaults.MaximumDrawerWidth
             )
+            .then(predictiveBackDrawerContainerModifier)
             .fillMaxHeight(),
         shape = drawerShape,
         color = drawerContainerColor,
         contentColor = drawerContentColor,
         tonalElevation = drawerTonalElevation
     ) {
+        val predictiveBackDrawerChildModifier =
+            if (drawerPredictiveBackState != null) Modifier.predictiveBackDrawerChild(
+                drawerPredictiveBackState,
+                isRtl
+            ) else Modifier
         Column(
             Modifier
                 .sizeIn(
                     minWidth = MinimumDrawerWidth,
                     maxWidth = DrawerDefaults.MaximumDrawerWidth
                 )
+                .then(predictiveBackDrawerChildModifier)
                 .windowInsetsPadding(windowInsets),
             content = content
         )
     }
 }
 
+private fun Modifier.predictiveBackDrawerContainer(
+    drawerPredictiveBackState: DrawerPredictiveBackState,
+    isRtl: Boolean
+) = graphicsLayer {
+    scaleX = calculatePredictiveBackScaleX(drawerPredictiveBackState)
+    scaleY = calculatePredictiveBackScaleY(drawerPredictiveBackState)
+    transformOrigin = TransformOrigin(if (isRtl) 1f else 0f, 0.5f)
+}
+
+private fun Modifier.predictiveBackDrawerChild(
+    drawerPredictiveBackState: DrawerPredictiveBackState,
+    isRtl: Boolean
+) = graphicsLayer {
+    // Preserve the original aspect ratio and container alignment of the child
+    // content, and add content margins.
+    val containerScaleX = calculatePredictiveBackScaleX(drawerPredictiveBackState)
+    val containerScaleY = calculatePredictiveBackScaleY(drawerPredictiveBackState)
+    scaleX = if (containerScaleX != 0f) containerScaleY / containerScaleX else 1f
+    transformOrigin = TransformOrigin(if (isRtl) 0f else 1f, 0f)
+}
+
+private fun GraphicsLayerScope.calculatePredictiveBackScaleX(
+    drawerPredictiveBackState: DrawerPredictiveBackState
+): Float {
+    val width = size.width
+    return if (width.isNaN() || width == 0f) {
+        1f
+    } else {
+        val scaleXDirection = if (drawerPredictiveBackState.swipeEdgeMatchesDrawer) 1 else -1
+        1f + drawerPredictiveBackState.scaleXDistance * scaleXDirection / width
+    }
+}
+
+private fun GraphicsLayerScope.calculatePredictiveBackScaleY(
+    drawerPredictiveBackState: DrawerPredictiveBackState
+): Float {
+    val height = size.height
+    return if (height.isNaN() || height == 0f) {
+        1f
+    } else {
+        1f - drawerPredictiveBackState.scaleYDistance / height
+    }
+}
+
+@Composable
+internal expect fun DrawerPredictiveBackHandler(
+    drawerState: DrawerState,
+    content: @Composable (DrawerPredictiveBackState) -> Unit
+)
+
 /**
  * Object to hold default values for [ModalNavigationDrawer]
  */
@@ -861,6 +1030,36 @@
     val ItemPadding = PaddingValues(horizontal = 12.dp)
 }
 
+@Stable
+internal class DrawerPredictiveBackState {
+
+    var swipeEdgeMatchesDrawer by mutableStateOf(true)
+
+    var scaleXDistance by mutableFloatStateOf(0f)
+
+    var scaleYDistance by mutableFloatStateOf(0f)
+
+    fun update(
+        progress: Float,
+        swipeEdgeLeft: Boolean,
+        isRtl: Boolean,
+        maxScaleXDistanceGrow: Float,
+        maxScaleXDistanceShrink: Float,
+        maxScaleYDistance: Float
+    ) {
+        swipeEdgeMatchesDrawer = swipeEdgeLeft != isRtl
+        val maxScaleXDistance =
+            if (swipeEdgeMatchesDrawer) maxScaleXDistanceGrow else maxScaleXDistanceShrink
+        scaleXDistance = lerp(0f, maxScaleXDistance, progress)
+        scaleYDistance = lerp(0f, maxScaleYDistance, progress)
+    }
+    fun clear() {
+        swipeEdgeMatchesDrawer = true
+        scaleXDistance = 0f
+        scaleYDistance = 0f
+    }
+}
+
 private class DefaultDrawerItemsColor(
     val selectedIconColor: Color,
     val unselectedIconColor: Color,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
index 15e6155..1c26998 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationItem.kt
@@ -105,7 +105,6 @@
  * TODO: Remove "internal".
  */
 @Immutable
-@ExperimentalMaterial3Api
 internal class NavigationItemColors constructor(
     val selectedIconColor: Color,
     val selectedTextColor: Color,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index fe9240c..6c210fe 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -400,112 +400,6 @@
     }
 }
 
-@Deprecated("Use overload with prefix and suffix parameters", level = DeprecationLevel.HIDDEN)
-@ExperimentalMaterial3Api
-@Composable
-fun OutlinedTextField(
-    value: String,
-    onValueChange: (String) -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    readOnly: Boolean = false,
-    textStyle: TextStyle = LocalTextStyle.current,
-    label: @Composable (() -> Unit)? = null,
-    placeholder: @Composable (() -> Unit)? = null,
-    leadingIcon: @Composable (() -> Unit)? = null,
-    trailingIcon: @Composable (() -> Unit)? = null,
-    supportingText: @Composable (() -> Unit)? = null,
-    isError: Boolean = false,
-    visualTransformation: VisualTransformation = VisualTransformation.None,
-    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
-    keyboardActions: KeyboardActions = KeyboardActions.Default,
-    singleLine: Boolean = false,
-    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
-    minLines: Int = 1,
-    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    shape: Shape = OutlinedTextFieldDefaults.shape,
-    colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
-) {
-    OutlinedTextField(
-        value = value,
-        onValueChange = onValueChange,
-        modifier = modifier,
-        enabled = enabled,
-        readOnly = readOnly,
-        textStyle = textStyle,
-        label = label,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        prefix = null,
-        suffix = null,
-        supportingText = supportingText,
-        isError = isError,
-        visualTransformation = visualTransformation,
-        keyboardOptions = keyboardOptions,
-        keyboardActions = keyboardActions,
-        singleLine = singleLine,
-        maxLines = maxLines,
-        minLines = minLines,
-        interactionSource = interactionSource,
-        shape = shape,
-        colors = colors,
-    )
-}
-
-@Deprecated("Use overload with prefix and suffix parameters", level = DeprecationLevel.HIDDEN)
-@ExperimentalMaterial3Api
-@Composable
-fun OutlinedTextField(
-    value: TextFieldValue,
-    onValueChange: (TextFieldValue) -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    readOnly: Boolean = false,
-    textStyle: TextStyle = LocalTextStyle.current,
-    label: @Composable (() -> Unit)? = null,
-    placeholder: @Composable (() -> Unit)? = null,
-    leadingIcon: @Composable (() -> Unit)? = null,
-    trailingIcon: @Composable (() -> Unit)? = null,
-    supportingText: @Composable (() -> Unit)? = null,
-    isError: Boolean = false,
-    visualTransformation: VisualTransformation = VisualTransformation.None,
-    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
-    keyboardActions: KeyboardActions = KeyboardActions.Default,
-    singleLine: Boolean = false,
-    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
-    minLines: Int = 1,
-    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    shape: Shape = OutlinedTextFieldDefaults.shape,
-    colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
-) {
-    OutlinedTextField(
-        value = value,
-        onValueChange = onValueChange,
-        modifier = modifier,
-        enabled = enabled,
-        readOnly = readOnly,
-        textStyle = textStyle,
-        label = label,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        prefix = null,
-        suffix = null,
-        supportingText = supportingText,
-        isError = isError,
-        visualTransformation = visualTransformation,
-        keyboardOptions = keyboardOptions,
-        keyboardActions = keyboardActions,
-        singleLine = singleLine,
-        maxLines = maxLines,
-        minLines = minLines,
-        interactionSource = interactionSource,
-        shape = shape,
-        colors = colors,
-    )
-}
-
 /**
  * Layout of the leading and trailing icons and the text field, label and placeholder in
  * [OutlinedTextField].
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
index 0aaf210..2a31c6e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
@@ -126,7 +126,6 @@
  * @param label content to be rendered inside this button
  */
 @Composable
-@ExperimentalMaterial3Api
 fun MultiChoiceSegmentedButtonRowScope.SegmentedButton(
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
@@ -204,7 +203,6 @@
  * @param label content to be rendered inside this button
  */
 @Composable
-@ExperimentalMaterial3Api
 fun SingleChoiceSegmentedButtonRowScope.SegmentedButton(
     selected: Boolean,
     onClick: () -> Unit,
@@ -264,7 +262,6 @@
  * [SegmentedButton]s
  */
 @Composable
-@ExperimentalMaterial3Api
 fun SingleChoiceSegmentedButtonRow(
     modifier: Modifier = Modifier,
     space: Dp = SegmentedButtonDefaults.BorderWidth,
@@ -302,7 +299,6 @@
  *
  */
 @Composable
-@ExperimentalMaterial3Api
 fun MultiChoiceSegmentedButtonRow(
     modifier: Modifier = Modifier,
     space: Dp = SegmentedButtonDefaults.BorderWidth,
@@ -320,7 +316,6 @@
     }
 }
 
-@ExperimentalMaterial3Api
 @Composable
 private fun SegmentedButtonContent(
     icon: @Composable () -> Unit,
@@ -351,7 +346,6 @@
     var animatable: Animatable<Int, AnimationVector1D>? = null
     private var initialOffset: Int? = null
 
-    @OptIn(ExperimentalMaterial3Api::class)
     override fun MeasureScope.measure(
         measurables: List<List<Measurable>>,
         constraints: Constraints
@@ -425,15 +419,12 @@
 }
 
 /** Scope for the children of a [SingleChoiceSegmentedButtonRow] */
-@ExperimentalMaterial3Api
 interface SingleChoiceSegmentedButtonRowScope : RowScope
 
 /** Scope for the children of a [MultiChoiceSegmentedButtonRow] */
-@ExperimentalMaterial3Api
 interface MultiChoiceSegmentedButtonRowScope : RowScope
 
 /* Contains defaults to be used with [SegmentedButtonRow] and [SegmentedButton] */
-@ExperimentalMaterial3Api
 @Stable
 object SegmentedButtonDefaults {
 
@@ -630,7 +621,6 @@
  * @param disabledInactiveBorderColor the color used for the border when disabled and inactive
  */
 @Immutable
-@ExperimentalMaterial3Api
 class SegmentedButtonColors(
     // enabled & active
     val activeContainerColor: Color,
@@ -782,10 +772,8 @@
 private const val CheckedZIndexFactor = 5f
 private val IconSpacing = 8.dp
 
-@OptIn(ExperimentalMaterial3Api::class)
 private class SingleChoiceSegmentedButtonScopeWrapper(scope: RowScope) :
     SingleChoiceSegmentedButtonRowScope, RowScope by scope
 
-@OptIn(ExperimentalMaterial3Api::class)
 private class MultiChoiceSegmentedButtonScopeWrapper(scope: RowScope) :
     MultiChoiceSegmentedButtonRowScope, RowScope by scope
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index 935dc9e..05f80c2 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -53,7 +53,6 @@
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.rotate
@@ -269,7 +268,6 @@
     },
     valueRange: ClosedFloatingPointRange<Float> = 0f..1f
 ) {
-    val onValueChangeFinishedState = rememberUpdatedState(onValueChangeFinished)
     val state = remember(
         steps,
         valueRange
@@ -277,11 +275,12 @@
         SliderState(
             value,
             steps,
-            { onValueChangeFinishedState.value?.invoke() },
+            onValueChangeFinished,
             valueRange
         )
     }
 
+    state.onValueChangeFinished = onValueChangeFinished
     state.onValueChange = onValueChange
     state.value = value
 
@@ -546,7 +545,6 @@
     @IntRange(from = 0)
     steps: Int = 0
 ) {
-    val onValueChangeFinishedState = rememberUpdatedState(onValueChangeFinished)
     val state = remember(
         steps,
         valueRange
@@ -555,11 +553,12 @@
             value.start,
             value.endInclusive,
             steps,
-            { onValueChangeFinishedState.value?.invoke() },
+            onValueChangeFinished,
             valueRange
         )
     }
 
+    state.onValueChangeFinished = onValueChangeFinished
     state.onValueChange = { onValueChange(it.start..it.endInclusive) }
     state.activeRangeStart = value.start
     state.activeRangeEnd = value.endInclusive
@@ -1332,6 +1331,7 @@
         val gap =
             if (thumbTrackGapSize > 0.dp) thumbWidth.toPx() / 2 + thumbTrackGapSize.toPx() else 0f
 
+        // inactive track (range slider)
         if (isRangeSlider && sliderValueStart.x > sliderStart.x + gap + cornerSize) {
             val start = sliderStart.x
             val end = sliderValueStart.x - gap
@@ -1344,6 +1344,7 @@
             )
             drawStopIndicator?.invoke(this, Offset(start + cornerSize, center.y))
         }
+        // inactive track
         if (sliderValueEnd.x < sliderEnd.x - gap - cornerSize) {
             val start = sliderValueEnd.x + gap
             val end = sliderEnd.x
@@ -1356,11 +1357,12 @@
             )
             drawStopIndicator?.invoke(this, Offset(end - cornerSize, center.y))
         }
+        // active track
         val activeTrackStart =
             if (isRangeSlider) sliderValueStart.x + gap else 0f
         val activeTrackEnd = sliderValueEnd.x - gap
         val startCornerRadius = if (isRangeSlider) insideCornerSize else cornerSize
-        if (activeTrackEnd - activeTrackStart > startCornerRadius + gap) {
+        if (activeTrackEnd - activeTrackStart > startCornerRadius) {
             drawTrackPath(
                 Offset(activeTrackStart, 0f),
                 Size(activeTrackEnd - activeTrackStart, trackStrokeWidth),
@@ -1396,29 +1398,51 @@
         startCornerRadius: Float,
         endCornerRadius: Float
     ) {
-        val startCorner = RoundRect(
-            rect = Rect(
-                offset,
-                size = Size(startCornerRadius * 2, size.height)
-            ), cornerRadius = CornerRadius(startCornerRadius)
-        )
+        trackPath.rewind()
+
         val track =
             Rect(
                 Offset(offset.x + startCornerRadius, 0f),
                 size = Size(size.width - startCornerRadius - endCornerRadius, size.height)
             )
-        val endCorner = RoundRect(
-            rect = Rect(
-                Offset(offset.x + startCornerRadius + track.width - endCornerRadius, 0f),
-                size = Size(endCornerRadius * 2, size.height)
-            ), cornerRadius = CornerRadius(endCornerRadius)
-        )
+        trackPath.addRect(track)
 
-        val path = Path()
-        path.addRoundRect(startCorner)
-        path.addRect(track)
-        path.addRoundRect(endCorner)
-        drawPath(path, color)
+        buildCorner(offset, size, startCornerRadius, isStart = true) // start
+        buildCorner(Offset(track.right - endCornerRadius, 0f), size, endCornerRadius) // end
+
+        drawPath(trackPath, color)
+
+        trackPath.rewind()
+    }
+
+    private fun buildCorner(
+        offset: Offset,
+        size: Size,
+        cornerRadius: Float,
+        isStart: Boolean = false
+    ) {
+        cornerPath.rewind()
+        halfRectPath.rewind()
+
+        val corner = RoundRect(
+            rect = Rect(
+                offset,
+                size = Size(cornerRadius * 2, size.height)
+            ), cornerRadius = CornerRadius(cornerRadius)
+        )
+        cornerPath.addRoundRect(corner)
+
+        // delete the unnecessary half of the RoundRect
+        halfRectPath.addRect(
+            Rect(
+                Offset(corner.left + if (isStart) cornerRadius else 0f, 0f),
+                size = Size(cornerRadius, size.height)
+            )
+        )
+        trackPath.addPath(cornerPath - halfRectPath)
+
+        cornerPath.rewind()
+        halfRectPath.rewind()
     }
 
     private fun DrawScope.drawStopIndicator(
@@ -1432,6 +1456,10 @@
             radius = size.toPx() / 2f
         )
     }
+
+    private val trackPath = Path()
+    private val cornerPath = Path()
+    private val halfRectPath = Path()
 }
 
 private fun snapValueToTick(
@@ -1980,13 +2008,12 @@
  * @param valueRange range of values that Slider values can take. [value] will be
  * coerced to this range.
  */
-@Stable
 @ExperimentalMaterial3Api
 class SliderState(
     value: Float = 0f,
     @IntRange(from = 0)
     val steps: Int = 0,
-    val onValueChangeFinished: (() -> Unit)? = null,
+    var onValueChangeFinished: (() -> Unit)? = null,
     val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
 ) : DraggableState {
 
@@ -2110,14 +2137,13 @@
  * @param valueRange range of values that Range Slider values can take. [activeRangeStart]
  * and [activeRangeEnd] will be coerced to this range.
  */
-@Stable
 @ExperimentalMaterial3Api
 class RangeSliderState(
     activeRangeStart: Float = 0f,
     activeRangeEnd: Float = 1f,
     @IntRange(from = 0)
     val steps: Int = 0,
-    val onValueChangeFinished: (() -> Unit)? = null,
+    var onValueChangeFinished: (() -> Unit)? = null,
     val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
 ) {
     private var activeRangeStartState by mutableFloatStateOf(activeRangeStart)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
index 7a6d278..a08f3b3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismissBox.kt
@@ -37,7 +37,6 @@
 /**
  * The directions in which a [SwipeToDismissBox] can be dismissed.
  */
-@ExperimentalMaterial3Api
 enum class SwipeToDismissBoxValue {
     /**
      * Can be dismissed by swiping in the reading direction.
@@ -66,7 +65,6 @@
  * the start of a transition. It will be, depending on the direction of the interaction, added or
  * subtracted from/to the origin offset. It should always be a positive value.
  */
-@ExperimentalMaterial3Api
 class SwipeToDismissBoxState(
     initialValue: SwipeToDismissBoxValue,
     internal val density: Density,
@@ -122,25 +120,6 @@
         }
 
     /**
-     * Whether the component has been dismissed in the given [direction].
-     *
-     * @param direction The dismiss direction.
-     */
-    @Deprecated(
-        message = "DismissDirection is no longer used by SwipeToDismissBoxState. Please compare " +
-            "currentValue against SwipeToDismissValue instead.",
-        level = DeprecationLevel.HIDDEN
-    )
-    @Suppress("DEPRECATION")
-    fun isDismissed(direction: DismissDirection): Boolean {
-        val directionalDismissValue = when (direction) {
-            DismissDirection.StartToEnd -> SwipeToDismissBoxValue.StartToEnd
-            DismissDirection.EndToStart -> SwipeToDismissBoxValue.EndToStart
-        }
-        return currentValue == directionalDismissValue
-    }
-
-    /**
      * Set the state without any animation and suspend until it's set
      *
      * @param targetValue The new target value
@@ -201,7 +180,6 @@
  * subtracted from/to the origin offset. It should always be a positive value.
  */
 @Composable
-@ExperimentalMaterial3Api
 fun rememberSwipeToDismissBoxState(
     initialValue: SwipeToDismissBoxValue = SwipeToDismissBoxValue.Settled,
     confirmValueChange: (SwipeToDismissBoxValue) -> Boolean = { true },
@@ -226,62 +204,22 @@
  * @sample androidx.compose.material3.samples.SwipeToDismissListItems
  *
  * @param state The state of this component.
- * @param background A composable that is stacked behind the content and is exposed when the
- * content is swiped. You can/should use the [state] to have different backgrounds on each side.
- * @param dismissContent The content that can be dismissed.
- * @param modifier Optional [Modifier] for this component.
- * @param directions The set of directions in which the component can be dismissed.
- */
-@Composable
-@Deprecated(
-    level = DeprecationLevel.WARNING,
-    message = "Use SwipeToDismissBox instead",
-    replaceWith =
-    ReplaceWith(
-        "SwipeToDismissBox(state, background, modifier, " +
-            "enableDismissFromStartToEnd, enableDismissFromEndToStart, dismissContent)"
-    )
-)
-@ExperimentalMaterial3Api
-fun SwipeToDismiss(
-    state: SwipeToDismissBoxState,
-    background: @Composable RowScope.() -> Unit,
-    dismissContent: @Composable RowScope.() -> Unit,
-    modifier: Modifier = Modifier,
-    directions: Set<SwipeToDismissBoxValue> = setOf(
-        SwipeToDismissBoxValue.EndToStart,
-        SwipeToDismissBoxValue.StartToEnd
-    ),
-) = SwipeToDismissBox(
-    state = state,
-    backgroundContent = background,
-    modifier = modifier,
-    enableDismissFromStartToEnd = SwipeToDismissBoxValue.StartToEnd in directions,
-    enableDismissFromEndToStart = SwipeToDismissBoxValue.EndToStart in directions,
-    content = dismissContent
-)
-
-/**
- * A composable that can be dismissed by swiping left or right.
- *
- * @sample androidx.compose.material3.samples.SwipeToDismissListItems
- *
- * @param state The state of this component.
  * @param backgroundContent A composable that is stacked behind the [content] and is exposed when
  * the content is swiped. You can/should use the [state] to have different backgrounds on each side.
  * @param modifier Optional [Modifier] for this component.
  * @param enableDismissFromStartToEnd Whether SwipeToDismissBox can be dismissed from start to end.
  * @param enableDismissFromEndToStart Whether SwipeToDismissBox can be dismissed from end to start.
+ * @param gesturesEnabled Whether swipe-to-dismiss can be interacted by gestures.
  * @param content The content that can be dismissed.
  */
 @Composable
-@ExperimentalMaterial3Api
 fun SwipeToDismissBox(
     state: SwipeToDismissBoxState,
     backgroundContent: @Composable RowScope.() -> Unit,
     modifier: Modifier = Modifier,
     enableDismissFromStartToEnd: Boolean = true,
     enableDismissFromEndToStart: Boolean = true,
+    gesturesEnabled: Boolean = true,
     content: @Composable RowScope.() -> Unit,
 ) {
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
@@ -291,7 +229,7 @@
             .anchoredDraggable(
                 state = state.anchoredDraggableState,
                 orientation = Orientation.Horizontal,
-                enabled = state.currentValue == SwipeToDismissBoxValue.Settled,
+                enabled = gesturesEnabled && state.currentValue == SwipeToDismissBoxValue.Settled,
             ),
         propagateMinConstraints = true
     ) {
@@ -321,7 +259,6 @@
 }
 
 /** Contains default values for [SwipeToDismissBox] and [SwipeToDismissBoxState]. */
-@ExperimentalMaterial3Api
 object SwipeToDismissBoxDefaults {
     /** Default positional threshold of 56.dp for [SwipeToDismissBoxState]. */
     val positionalThreshold: (totalDistance: Float) -> Float
@@ -330,51 +267,4 @@
         }
 }
 
-/**
- * The directions in which a [SwipeToDismissBox] can be dismissed.
- */
-@ExperimentalMaterial3Api
-@Deprecated(
-    message = "Dismiss direction is no longer used by SwipeToDismissBoxState. Please use " +
-        "SwipeToDismissBoxValue instead.",
-    level = DeprecationLevel.WARNING
-)
-enum class DismissDirection {
-    /**
-     * Can be dismissed by swiping in the reading direction.
-     */
-    StartToEnd,
-
-    /**
-     * Can be dismissed by swiping in the reverse of the reading direction.
-     */
-    EndToStart,
-}
-
-/**
- * Possible values of [SwipeToDismissBoxState].
- */
-@ExperimentalMaterial3Api
-@Deprecated(
-    message = "DismissValue is no longer used by SwipeToDismissBoxState. Please use " +
-        "SwipeToDismissBoxValue instead.",
-    level = DeprecationLevel.WARNING
-)
-enum class DismissValue {
-    /**
-     * Indicates the component has not been dismissed yet.
-     */
-    Default,
-
-    /**
-     * Indicates the component has been dismissed in the reading direction.
-     */
-    DismissedToEnd,
-
-    /**
-     * Indicates the component has been dismissed in the reverse of the reading direction.
-     */
-    DismissedToStart
-}
-
 private val DismissVelocityThreshold = 125.dp
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
index 94d8ed7..9d77d4b 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
@@ -459,6 +459,7 @@
                     constraints.copy(
                         minWidth = tabWidth,
                         maxWidth = tabWidth,
+                        minHeight = 0,
                         maxHeight = tabRowHeight
                     )
                 )
@@ -567,8 +568,8 @@
             }
         )
 
-        return layout(placeable.width, constraints.maxHeight) {
-            placeable.place(offset.roundToPx(), constraints.maxHeight - placeable.height)
+        return layout(placeable.width, placeable.height) {
+            placeable.place(offset.roundToPx(), 0)
         }
     }
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index 9cbcf10..6f8d337 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -398,112 +398,6 @@
     }
 }
 
-@Deprecated("Use overload with prefix and suffix parameters", level = DeprecationLevel.HIDDEN)
-@ExperimentalMaterial3Api
-@Composable
-fun TextField(
-    value: String,
-    onValueChange: (String) -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    readOnly: Boolean = false,
-    textStyle: TextStyle = LocalTextStyle.current,
-    label: @Composable (() -> Unit)? = null,
-    placeholder: @Composable (() -> Unit)? = null,
-    leadingIcon: @Composable (() -> Unit)? = null,
-    trailingIcon: @Composable (() -> Unit)? = null,
-    supportingText: @Composable (() -> Unit)? = null,
-    isError: Boolean = false,
-    visualTransformation: VisualTransformation = VisualTransformation.None,
-    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
-    keyboardActions: KeyboardActions = KeyboardActions.Default,
-    singleLine: Boolean = false,
-    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
-    minLines: Int = 1,
-    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    shape: Shape = TextFieldDefaults.shape,
-    colors: TextFieldColors = TextFieldDefaults.colors()
-) {
-    TextField(
-        value = value,
-        onValueChange = onValueChange,
-        modifier = modifier,
-        enabled = enabled,
-        readOnly = readOnly,
-        textStyle = textStyle,
-        label = label,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        prefix = null,
-        suffix = null,
-        supportingText = supportingText,
-        isError = isError,
-        visualTransformation = visualTransformation,
-        keyboardOptions = keyboardOptions,
-        keyboardActions = keyboardActions,
-        singleLine = singleLine,
-        maxLines = maxLines,
-        minLines = minLines,
-        interactionSource = interactionSource,
-        shape = shape,
-        colors = colors,
-    )
-}
-
-@Deprecated("Use overload with prefix and suffix parameters", level = DeprecationLevel.HIDDEN)
-@ExperimentalMaterial3Api
-@Composable
-fun TextField(
-    value: TextFieldValue,
-    onValueChange: (TextFieldValue) -> Unit,
-    modifier: Modifier = Modifier,
-    enabled: Boolean = true,
-    readOnly: Boolean = false,
-    textStyle: TextStyle = LocalTextStyle.current,
-    label: @Composable (() -> Unit)? = null,
-    placeholder: @Composable (() -> Unit)? = null,
-    leadingIcon: @Composable (() -> Unit)? = null,
-    trailingIcon: @Composable (() -> Unit)? = null,
-    supportingText: @Composable (() -> Unit)? = null,
-    isError: Boolean = false,
-    visualTransformation: VisualTransformation = VisualTransformation.None,
-    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
-    keyboardActions: KeyboardActions = KeyboardActions.Default,
-    singleLine: Boolean = false,
-    maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
-    minLines: Int = 1,
-    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    shape: Shape = TextFieldDefaults.shape,
-    colors: TextFieldColors = TextFieldDefaults.colors()
-) {
-    TextField(
-        value = value,
-        onValueChange = onValueChange,
-        modifier = modifier,
-        enabled = enabled,
-        readOnly = readOnly,
-        textStyle = textStyle,
-        label = label,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        prefix = null,
-        suffix = null,
-        supportingText = supportingText,
-        isError = isError,
-        visualTransformation = visualTransformation,
-        keyboardOptions = keyboardOptions,
-        keyboardActions = keyboardActions,
-        singleLine = singleLine,
-        maxLines = maxLines,
-        minLines = minLines,
-        interactionSource = interactionSource,
-        shape = shape,
-        colors = colors,
-    )
-}
-
 /**
  * Composable responsible for measuring and laying out leading and trailing icons, label,
  * placeholder and the input field.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
index 735c5b6..ba49cea 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldDefaults.kt
@@ -557,67 +557,6 @@
     val FocusedBorderThickness = FocusedIndicatorThickness
 
     @Deprecated(
-        message = "Renamed to `TextFieldDefaults.ContainerBox`",
-        replaceWith = ReplaceWith("TextFieldDefaults.ContainerBox(\n" +
-            "        enabled = enabled,\n" +
-            "        isError = isError,\n" +
-            "        interactionSource = interactionSource,\n" +
-            "        colors = colors,\n" +
-            "        shape = shape,\n" +
-            "    )"),
-        level = DeprecationLevel.WARNING
-    )
-    @ExperimentalMaterial3Api
-    @Composable
-    fun FilledContainerBox(
-        enabled: Boolean,
-        isError: Boolean,
-        interactionSource: InteractionSource,
-        colors: TextFieldColors,
-        shape: Shape = TextFieldDefaults.shape,
-    ) = ContainerBox(
-        enabled = enabled,
-        isError = isError,
-        interactionSource = interactionSource,
-        colors = colors,
-        shape = shape,
-    )
-
-    @Deprecated(
-        message = "Renamed to `OutlinedTextFieldDefaults.ContainerBox`",
-        replaceWith = ReplaceWith("OutlinedTextFieldDefaults.ContainerBox(\n" +
-            "        enabled = enabled,\n" +
-            "        isError = isError,\n" +
-            "        interactionSource = interactionSource,\n" +
-            "        colors = colors,\n" +
-            "        shape = shape,\n" +
-            "        focusedBorderThickness = focusedBorderThickness,\n" +
-            "        unfocusedBorderThickness = unfocusedBorderThickness,\n" +
-            "    )",
-            "androidx.compose.material.OutlinedTextFieldDefaults"),
-        level = DeprecationLevel.WARNING
-    )
-    @ExperimentalMaterial3Api
-    @Composable
-    fun OutlinedBorderContainerBox(
-        enabled: Boolean,
-        isError: Boolean,
-        interactionSource: InteractionSource,
-        colors: TextFieldColors,
-        shape: Shape = OutlinedTextFieldTokens.ContainerShape.value,
-        focusedBorderThickness: Dp = OutlinedTextFieldDefaults.FocusedBorderThickness,
-        unfocusedBorderThickness: Dp = OutlinedTextFieldDefaults.UnfocusedBorderThickness
-    ) = OutlinedTextFieldDefaults.ContainerBox(
-        enabled = enabled,
-        isError = isError,
-        interactionSource = interactionSource,
-        colors = colors,
-        shape = shape,
-        focusedBorderThickness = focusedBorderThickness,
-        unfocusedBorderThickness = unfocusedBorderThickness,
-    )
-
-    @Deprecated(
         message = "Renamed to `TextFieldDefaults.contentPaddingWithLabel`",
         replaceWith = ReplaceWith("TextFieldDefaults.contentPaddingWithLabel(\n" +
             "        start = start,\n" +
@@ -683,727 +622,6 @@
         end = end,
         bottom = bottom,
     )
-
-    @Deprecated(
-        message = "Renamed to `TextFieldDefaults.colors` with additional parameters to control" +
-            "container color based on state.",
-        replaceWith = ReplaceWith("TextFieldDefaults.colors(\n" +
-            "        focusedTextColor = focusedTextColor,\n" +
-            "        unfocusedTextColor = unfocusedTextColor,\n" +
-            "        disabledTextColor = disabledTextColor,\n" +
-            "        errorTextColor = errorTextColor,\n" +
-            "        focusedContainerColor = containerColor,\n" +
-            "        unfocusedContainerColor = containerColor,\n" +
-            "        disabledContainerColor = containerColor,\n" +
-            "        errorContainerColor = errorContainerColor,\n" +
-            "        cursorColor = cursorColor,\n" +
-            "        errorCursorColor = errorCursorColor,\n" +
-            "        selectionColors = selectionColors,\n" +
-            "        focusedIndicatorColor = focusedIndicatorColor,\n" +
-            "        unfocusedIndicatorColor = unfocusedIndicatorColor,\n" +
-            "        disabledIndicatorColor = disabledIndicatorColor,\n" +
-            "        errorIndicatorColor = errorIndicatorColor,\n" +
-            "        focusedLeadingIconColor = focusedLeadingIconColor,\n" +
-            "        unfocusedLeadingIconColor = unfocusedLeadingIconColor,\n" +
-            "        disabledLeadingIconColor = disabledLeadingIconColor,\n" +
-            "        errorLeadingIconColor = errorLeadingIconColor,\n" +
-            "        focusedTrailingIconColor = focusedTrailingIconColor,\n" +
-            "        unfocusedTrailingIconColor = unfocusedTrailingIconColor,\n" +
-            "        disabledTrailingIconColor = disabledTrailingIconColor,\n" +
-            "        errorTrailingIconColor = errorTrailingIconColor,\n" +
-            "        focusedLabelColor = focusedLabelColor,\n" +
-            "        unfocusedLabelColor = unfocusedLabelColor,\n" +
-            "        disabledLabelColor = disabledLabelColor,\n" +
-            "        errorLabelColor = errorLabelColor,\n" +
-            "        focusedPlaceholderColor = focusedPlaceholderColor,\n" +
-            "        unfocusedPlaceholderColor = unfocusedPlaceholderColor,\n" +
-            "        disabledPlaceholderColor = disabledPlaceholderColor,\n" +
-            "        errorPlaceholderColor = errorPlaceholderColor,\n" +
-            "        focusedSupportingTextColor = focusedSupportingTextColor,\n" +
-            "        unfocusedSupportingTextColor = unfocusedSupportingTextColor,\n" +
-            "        disabledSupportingTextColor = disabledSupportingTextColor,\n" +
-            "        errorSupportingTextColor = errorSupportingTextColor,\n" +
-            "        focusedPrefixColor = focusedPrefixColor,\n" +
-            "        unfocusedPrefixColor = unfocusedPrefixColor,\n" +
-            "        disabledPrefixColor = disabledPrefixColor,\n" +
-            "        errorPrefixColor = errorPrefixColor,\n" +
-            "        focusedSuffixColor = focusedSuffixColor,\n" +
-            "        unfocusedSuffixColor = unfocusedSuffixColor,\n" +
-            "        disabledSuffixColor = disabledSuffixColor,\n" +
-            "        errorSuffixColor = errorSuffixColor,\n" +
-            "    )"),
-        level = DeprecationLevel.WARNING,
-    )
-    @ExperimentalMaterial3Api
-    @Composable
-    fun textFieldColors(
-        focusedTextColor: Color = FilledTextFieldTokens.FocusInputColor.value,
-        unfocusedTextColor: Color = FilledTextFieldTokens.InputColor.value,
-        disabledTextColor: Color = FilledTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        errorTextColor: Color = FilledTextFieldTokens.ErrorInputColor.value,
-        containerColor: Color = FilledTextFieldTokens.ContainerColor.value,
-        errorContainerColor: Color = FilledTextFieldTokens.ContainerColor.value,
-        cursorColor: Color = FilledTextFieldTokens.CaretColor.value,
-        errorCursorColor: Color = FilledTextFieldTokens.ErrorFocusCaretColor.value,
-        selectionColors: TextSelectionColors = LocalTextSelectionColors.current,
-        focusedIndicatorColor: Color = FilledTextFieldTokens.FocusActiveIndicatorColor.value,
-        unfocusedIndicatorColor: Color = FilledTextFieldTokens.ActiveIndicatorColor.value,
-        disabledIndicatorColor: Color = FilledTextFieldTokens.DisabledActiveIndicatorColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledActiveIndicatorOpacity),
-        errorIndicatorColor: Color = FilledTextFieldTokens.ErrorActiveIndicatorColor.value,
-        focusedLeadingIconColor: Color = FilledTextFieldTokens.FocusLeadingIconColor.value,
-        unfocusedLeadingIconColor: Color = FilledTextFieldTokens.LeadingIconColor.value,
-        disabledLeadingIconColor: Color = FilledTextFieldTokens.DisabledLeadingIconColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledLeadingIconOpacity),
-        errorLeadingIconColor: Color = FilledTextFieldTokens.ErrorLeadingIconColor.value,
-        focusedTrailingIconColor: Color = FilledTextFieldTokens.FocusTrailingIconColor.value,
-        unfocusedTrailingIconColor: Color = FilledTextFieldTokens.TrailingIconColor.value,
-        disabledTrailingIconColor: Color = FilledTextFieldTokens.DisabledTrailingIconColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledTrailingIconOpacity),
-        errorTrailingIconColor: Color = FilledTextFieldTokens.ErrorTrailingIconColor.value,
-        focusedLabelColor: Color = FilledTextFieldTokens.FocusLabelColor.value,
-        unfocusedLabelColor: Color = FilledTextFieldTokens.LabelColor.value,
-        disabledLabelColor: Color = FilledTextFieldTokens.DisabledLabelColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledLabelOpacity),
-        errorLabelColor: Color = FilledTextFieldTokens.ErrorLabelColor.value,
-        focusedPlaceholderColor: Color = FilledTextFieldTokens.InputPlaceholderColor.value,
-        unfocusedPlaceholderColor: Color = FilledTextFieldTokens.InputPlaceholderColor.value,
-        disabledPlaceholderColor: Color = FilledTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        errorPlaceholderColor: Color = FilledTextFieldTokens.InputPlaceholderColor.value,
-        focusedSupportingTextColor: Color = FilledTextFieldTokens.FocusSupportingColor.value,
-        unfocusedSupportingTextColor: Color = FilledTextFieldTokens.SupportingColor.value,
-        disabledSupportingTextColor: Color = FilledTextFieldTokens.DisabledSupportingColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledSupportingOpacity),
-        errorSupportingTextColor: Color = FilledTextFieldTokens.ErrorSupportingColor.value,
-        focusedPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value,
-        unfocusedPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value,
-        disabledPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        errorPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value,
-        focusedSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value,
-        unfocusedSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value,
-        disabledSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        errorSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value,
-    ): TextFieldColors = colors(
-        focusedTextColor = focusedTextColor,
-        unfocusedTextColor = unfocusedTextColor,
-        disabledTextColor = disabledTextColor,
-        errorTextColor = errorTextColor,
-        focusedContainerColor = containerColor,
-        unfocusedContainerColor = containerColor,
-        disabledContainerColor = containerColor,
-        errorContainerColor = errorContainerColor,
-        cursorColor = cursorColor,
-        errorCursorColor = errorCursorColor,
-        selectionColors = selectionColors,
-        focusedIndicatorColor = focusedIndicatorColor,
-        unfocusedIndicatorColor = unfocusedIndicatorColor,
-        disabledIndicatorColor = disabledIndicatorColor,
-        errorIndicatorColor = errorIndicatorColor,
-        focusedLeadingIconColor = focusedLeadingIconColor,
-        unfocusedLeadingIconColor = unfocusedLeadingIconColor,
-        disabledLeadingIconColor = disabledLeadingIconColor,
-        errorLeadingIconColor = errorLeadingIconColor,
-        focusedTrailingIconColor = focusedTrailingIconColor,
-        unfocusedTrailingIconColor = unfocusedTrailingIconColor,
-        disabledTrailingIconColor = disabledTrailingIconColor,
-        errorTrailingIconColor = errorTrailingIconColor,
-        focusedLabelColor = focusedLabelColor,
-        unfocusedLabelColor = unfocusedLabelColor,
-        disabledLabelColor = disabledLabelColor,
-        errorLabelColor = errorLabelColor,
-        focusedPlaceholderColor = focusedPlaceholderColor,
-        unfocusedPlaceholderColor = unfocusedPlaceholderColor,
-        disabledPlaceholderColor = disabledPlaceholderColor,
-        errorPlaceholderColor = errorPlaceholderColor,
-        focusedSupportingTextColor = focusedSupportingTextColor,
-        unfocusedSupportingTextColor = unfocusedSupportingTextColor,
-        disabledSupportingTextColor = disabledSupportingTextColor,
-        errorSupportingTextColor = errorSupportingTextColor,
-        focusedPrefixColor = focusedPrefixColor,
-        unfocusedPrefixColor = unfocusedPrefixColor,
-        disabledPrefixColor = disabledPrefixColor,
-        errorPrefixColor = errorPrefixColor,
-        focusedSuffixColor = focusedSuffixColor,
-        unfocusedSuffixColor = unfocusedSuffixColor,
-        disabledSuffixColor = disabledSuffixColor,
-        errorSuffixColor = errorSuffixColor,
-    )
-
-    @Deprecated(
-        message = "Renamed to `OutlinedTextFieldDefaults.colors` with additional parameters to" +
-            "control container color based on state.",
-        replaceWith = ReplaceWith("OutlinedTextFieldDefaults.colors(\n" +
-            "        focusedTextColor = focusedTextColor,\n" +
-            "        unfocusedTextColor = unfocusedTextColor,\n" +
-            "        disabledTextColor = disabledTextColor,\n" +
-            "        errorTextColor = errorTextColor,\n" +
-            "        focusedContainerColor = containerColor,\n" +
-            "        unfocusedContainerColor = containerColor,\n" +
-            "        disabledContainerColor = containerColor,\n" +
-            "        errorContainerColor = errorContainerColor,\n" +
-            "        cursorColor = cursorColor,\n" +
-            "        errorCursorColor = errorCursorColor,\n" +
-            "        selectionColors = selectionColors,\n" +
-            "        focusedBorderColor = focusedBorderColor,\n" +
-            "        unfocusedBorderColor = unfocusedBorderColor,\n" +
-            "        disabledBorderColor = disabledBorderColor,\n" +
-            "        errorBorderColor = errorBorderColor,\n" +
-            "        focusedLeadingIconColor = focusedLeadingIconColor,\n" +
-            "        unfocusedLeadingIconColor = unfocusedLeadingIconColor,\n" +
-            "        disabledLeadingIconColor = disabledLeadingIconColor,\n" +
-            "        errorLeadingIconColor = errorLeadingIconColor,\n" +
-            "        focusedTrailingIconColor = focusedTrailingIconColor,\n" +
-            "        unfocusedTrailingIconColor = unfocusedTrailingIconColor,\n" +
-            "        disabledTrailingIconColor = disabledTrailingIconColor,\n" +
-            "        errorTrailingIconColor = errorTrailingIconColor,\n" +
-            "        focusedLabelColor = focusedLabelColor,\n" +
-            "        unfocusedLabelColor = unfocusedLabelColor,\n" +
-            "        disabledLabelColor = disabledLabelColor,\n" +
-            "        errorLabelColor = errorLabelColor,\n" +
-            "        focusedPlaceholderColor = focusedPlaceholderColor,\n" +
-            "        unfocusedPlaceholderColor = unfocusedPlaceholderColor,\n" +
-            "        disabledPlaceholderColor = disabledPlaceholderColor,\n" +
-            "        errorPlaceholderColor = errorPlaceholderColor,\n" +
-            "        focusedSupportingTextColor = focusedSupportingTextColor,\n" +
-            "        unfocusedSupportingTextColor = unfocusedSupportingTextColor,\n" +
-            "        disabledSupportingTextColor = disabledSupportingTextColor,\n" +
-            "        errorSupportingTextColor = errorSupportingTextColor,\n" +
-            "        focusedPrefixColor = focusedPrefixColor,\n" +
-            "        unfocusedPrefixColor = unfocusedPrefixColor,\n" +
-            "        disabledPrefixColor = disabledPrefixColor,\n" +
-            "        errorPrefixColor = errorPrefixColor,\n" +
-            "        focusedSuffixColor = focusedSuffixColor,\n" +
-            "        unfocusedSuffixColor = unfocusedSuffixColor,\n" +
-            "        disabledSuffixColor = disabledSuffixColor,\n" +
-            "        errorSuffixColor = errorSuffixColor,\n" +
-            "    )",
-            "androidx.compose.material.OutlinedTextFieldDefaults"),
-        level = DeprecationLevel.WARNING,
-    )
-    @ExperimentalMaterial3Api
-    @Composable
-    fun outlinedTextFieldColors(
-        focusedTextColor: Color = OutlinedTextFieldTokens.FocusInputColor.value,
-        unfocusedTextColor: Color = OutlinedTextFieldTokens.InputColor.value,
-        disabledTextColor: Color = OutlinedTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        errorTextColor: Color = OutlinedTextFieldTokens.ErrorInputColor.value,
-        containerColor: Color = Color.Transparent,
-        errorContainerColor: Color = Color.Transparent,
-        cursorColor: Color = OutlinedTextFieldTokens.CaretColor.value,
-        errorCursorColor: Color = OutlinedTextFieldTokens.ErrorFocusCaretColor.value,
-        selectionColors: TextSelectionColors = LocalTextSelectionColors.current,
-        focusedBorderColor: Color = OutlinedTextFieldTokens.FocusOutlineColor.value,
-        unfocusedBorderColor: Color = OutlinedTextFieldTokens.OutlineColor.value,
-        disabledBorderColor: Color = OutlinedTextFieldTokens.DisabledOutlineColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledOutlineOpacity),
-        errorBorderColor: Color = OutlinedTextFieldTokens.ErrorOutlineColor.value,
-        focusedLeadingIconColor: Color = OutlinedTextFieldTokens.FocusLeadingIconColor.value,
-        unfocusedLeadingIconColor: Color = OutlinedTextFieldTokens.LeadingIconColor.value,
-        disabledLeadingIconColor: Color = OutlinedTextFieldTokens.DisabledLeadingIconColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledLeadingIconOpacity),
-        errorLeadingIconColor: Color = OutlinedTextFieldTokens.ErrorLeadingIconColor.value,
-        focusedTrailingIconColor: Color = OutlinedTextFieldTokens.FocusTrailingIconColor.value,
-        unfocusedTrailingIconColor: Color = OutlinedTextFieldTokens.TrailingIconColor.value,
-        disabledTrailingIconColor: Color = OutlinedTextFieldTokens.DisabledTrailingIconColor
-            .value.copy(alpha = OutlinedTextFieldTokens.DisabledTrailingIconOpacity),
-        errorTrailingIconColor: Color = OutlinedTextFieldTokens.ErrorTrailingIconColor.value,
-        focusedLabelColor: Color = OutlinedTextFieldTokens.FocusLabelColor.value,
-        unfocusedLabelColor: Color = OutlinedTextFieldTokens.LabelColor.value,
-        disabledLabelColor: Color = OutlinedTextFieldTokens.DisabledLabelColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledLabelOpacity),
-        errorLabelColor: Color = OutlinedTextFieldTokens.ErrorLabelColor.value,
-        focusedPlaceholderColor: Color = OutlinedTextFieldTokens.InputPlaceholderColor.value,
-        unfocusedPlaceholderColor: Color = OutlinedTextFieldTokens.InputPlaceholderColor.value,
-        disabledPlaceholderColor: Color = OutlinedTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        errorPlaceholderColor: Color = OutlinedTextFieldTokens.InputPlaceholderColor.value,
-        focusedSupportingTextColor: Color = OutlinedTextFieldTokens.FocusSupportingColor.value,
-        unfocusedSupportingTextColor: Color = OutlinedTextFieldTokens.SupportingColor.value,
-        disabledSupportingTextColor: Color = OutlinedTextFieldTokens.DisabledSupportingColor
-            .value.copy(alpha = OutlinedTextFieldTokens.DisabledSupportingOpacity),
-        errorSupportingTextColor: Color = OutlinedTextFieldTokens.ErrorSupportingColor.value,
-        focusedPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value,
-        unfocusedPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value,
-        disabledPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        errorPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value,
-        focusedSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value,
-        unfocusedSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value,
-        disabledSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        errorSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value,
-    ): TextFieldColors = OutlinedTextFieldDefaults.colors(
-        focusedTextColor = focusedTextColor,
-        unfocusedTextColor = unfocusedTextColor,
-        disabledTextColor = disabledTextColor,
-        errorTextColor = errorTextColor,
-        focusedContainerColor = containerColor,
-        unfocusedContainerColor = containerColor,
-        disabledContainerColor = containerColor,
-        errorContainerColor = errorContainerColor,
-        cursorColor = cursorColor,
-        errorCursorColor = errorCursorColor,
-        selectionColors = selectionColors,
-        focusedBorderColor = focusedBorderColor,
-        unfocusedBorderColor = unfocusedBorderColor,
-        disabledBorderColor = disabledBorderColor,
-        errorBorderColor = errorBorderColor,
-        focusedLeadingIconColor = focusedLeadingIconColor,
-        unfocusedLeadingIconColor = unfocusedLeadingIconColor,
-        disabledLeadingIconColor = disabledLeadingIconColor,
-        errorLeadingIconColor = errorLeadingIconColor,
-        focusedTrailingIconColor = focusedTrailingIconColor,
-        unfocusedTrailingIconColor = unfocusedTrailingIconColor,
-        disabledTrailingIconColor = disabledTrailingIconColor,
-        errorTrailingIconColor = errorTrailingIconColor,
-        focusedLabelColor = focusedLabelColor,
-        unfocusedLabelColor = unfocusedLabelColor,
-        disabledLabelColor = disabledLabelColor,
-        errorLabelColor = errorLabelColor,
-        focusedPlaceholderColor = focusedPlaceholderColor,
-        unfocusedPlaceholderColor = unfocusedPlaceholderColor,
-        disabledPlaceholderColor = disabledPlaceholderColor,
-        errorPlaceholderColor = errorPlaceholderColor,
-        focusedSupportingTextColor = focusedSupportingTextColor,
-        unfocusedSupportingTextColor = unfocusedSupportingTextColor,
-        disabledSupportingTextColor = disabledSupportingTextColor,
-        errorSupportingTextColor = errorSupportingTextColor,
-        focusedPrefixColor = focusedPrefixColor,
-        unfocusedPrefixColor = unfocusedPrefixColor,
-        disabledPrefixColor = disabledPrefixColor,
-        errorPrefixColor = errorPrefixColor,
-        focusedSuffixColor = focusedSuffixColor,
-        unfocusedSuffixColor = unfocusedSuffixColor,
-        disabledSuffixColor = disabledSuffixColor,
-        errorSuffixColor = errorSuffixColor,
-    )
-
-    @Deprecated(
-        message = "Renamed to `TextFieldDefaults.DecorationBox`",
-        replaceWith = ReplaceWith("TextFieldDefaults.DecorationBox(\n" +
-            "        value = value,\n" +
-            "        innerTextField = innerTextField,\n" +
-            "        enabled = enabled,\n" +
-            "        singleLine = singleLine,\n" +
-            "        visualTransformation = visualTransformation,\n" +
-            "        interactionSource = interactionSource,\n" +
-            "        isError = isError,\n" +
-            "        label = label,\n" +
-            "        placeholder = placeholder,\n" +
-            "        leadingIcon = leadingIcon,\n" +
-            "        trailingIcon = trailingIcon,\n" +
-            "        prefix = prefix,\n" +
-            "        suffix = suffix,\n" +
-            "        supportingText = supportingText,\n" +
-            "        shape = shape,\n" +
-            "        colors = colors,\n" +
-            "        contentPadding = contentPadding,\n" +
-            "        container = container,\n" +
-            "    )"),
-        level = DeprecationLevel.WARNING
-    )
-    @Composable
-    @ExperimentalMaterial3Api
-    fun TextFieldDecorationBox(
-        value: String,
-        innerTextField: @Composable () -> Unit,
-        enabled: Boolean,
-        singleLine: Boolean,
-        visualTransformation: VisualTransformation,
-        interactionSource: InteractionSource,
-        isError: Boolean = false,
-        label: @Composable (() -> Unit)? = null,
-        placeholder: @Composable (() -> Unit)? = null,
-        leadingIcon: @Composable (() -> Unit)? = null,
-        trailingIcon: @Composable (() -> Unit)? = null,
-        prefix: @Composable (() -> Unit)? = null,
-        suffix: @Composable (() -> Unit)? = null,
-        supportingText: @Composable (() -> Unit)? = null,
-        shape: Shape = TextFieldDefaults.shape,
-        colors: TextFieldColors = colors(),
-        contentPadding: PaddingValues =
-            if (label == null) {
-                contentPaddingWithoutLabel()
-            } else {
-                contentPaddingWithLabel()
-            },
-        container: @Composable () -> Unit = {
-            ContainerBox(enabled, isError, interactionSource, colors, shape)
-        }
-    ) = DecorationBox(
-        value = value,
-        innerTextField = innerTextField,
-        enabled = enabled,
-        singleLine = singleLine,
-        visualTransformation = visualTransformation,
-        interactionSource = interactionSource,
-        isError = isError,
-        label = label,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        prefix = prefix,
-        suffix = suffix,
-        supportingText = supportingText,
-        shape = shape,
-        colors = colors,
-        contentPadding = contentPadding,
-        container = container,
-    )
-
-    @Deprecated(
-        message = "Renamed to `OutlinedTextFieldDefaults.DecorationBox`",
-        replaceWith = ReplaceWith("OutlinedTextFieldDefaults.DecorationBox(\n" +
-            "        value = value,\n" +
-            "        innerTextField = innerTextField,\n" +
-            "        enabled = enabled,\n" +
-            "        singleLine = singleLine,\n" +
-            "        visualTransformation = visualTransformation,\n" +
-            "        interactionSource = interactionSource,\n" +
-            "        isError = isError,\n" +
-            "        label = label,\n" +
-            "        placeholder = placeholder,\n" +
-            "        leadingIcon = leadingIcon,\n" +
-            "        trailingIcon = trailingIcon,\n" +
-            "        prefix = prefix,\n" +
-            "        suffix = suffix,\n" +
-            "        supportingText = supportingText,\n" +
-            "        colors = colors,\n" +
-            "        contentPadding = contentPadding,\n" +
-            "        container = container,\n" +
-            "    )",
-            "androidx.compose.material.OutlinedTextFieldDefaults"),
-        level = DeprecationLevel.WARNING
-    )
-    @Composable
-    @ExperimentalMaterial3Api
-    fun OutlinedTextFieldDecorationBox(
-        value: String,
-        innerTextField: @Composable () -> Unit,
-        enabled: Boolean,
-        singleLine: Boolean,
-        visualTransformation: VisualTransformation,
-        interactionSource: InteractionSource,
-        isError: Boolean = false,
-        label: @Composable (() -> Unit)? = null,
-        placeholder: @Composable (() -> Unit)? = null,
-        leadingIcon: @Composable (() -> Unit)? = null,
-        trailingIcon: @Composable (() -> Unit)? = null,
-        prefix: @Composable (() -> Unit)? = null,
-        suffix: @Composable (() -> Unit)? = null,
-        supportingText: @Composable (() -> Unit)? = null,
-        colors: TextFieldColors = OutlinedTextFieldDefaults.colors(),
-        contentPadding: PaddingValues = OutlinedTextFieldDefaults.contentPadding(),
-        container: @Composable () -> Unit = {
-            OutlinedTextFieldDefaults.ContainerBox(enabled, isError, interactionSource, colors)
-        }
-    ) = OutlinedTextFieldDefaults.DecorationBox(
-        value = value,
-        innerTextField = innerTextField,
-        enabled = enabled,
-        singleLine = singleLine,
-        visualTransformation = visualTransformation,
-        interactionSource = interactionSource,
-        isError = isError,
-        label = label,
-        placeholder = placeholder,
-        leadingIcon = leadingIcon,
-        trailingIcon = trailingIcon,
-        prefix = prefix,
-        suffix = suffix,
-        supportingText = supportingText,
-        colors = colors,
-        contentPadding = contentPadding,
-        container = container,
-    )
-
-    @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
-    @ExperimentalMaterial3Api
-    @Composable
-    fun textFieldColors(
-        textColor: Color = FilledTextFieldTokens.InputColor.value,
-        disabledTextColor: Color = FilledTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        containerColor: Color = FilledTextFieldTokens.ContainerColor.value,
-        cursorColor: Color = FilledTextFieldTokens.CaretColor.value,
-        errorCursorColor: Color = FilledTextFieldTokens.ErrorFocusCaretColor.value,
-        selectionColors: TextSelectionColors = LocalTextSelectionColors.current,
-        focusedIndicatorColor: Color = FilledTextFieldTokens.FocusActiveIndicatorColor.value,
-        unfocusedIndicatorColor: Color = FilledTextFieldTokens.ActiveIndicatorColor.value,
-        disabledIndicatorColor: Color = FilledTextFieldTokens.DisabledActiveIndicatorColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledActiveIndicatorOpacity),
-        errorIndicatorColor: Color = FilledTextFieldTokens.ErrorActiveIndicatorColor.value,
-        focusedLeadingIconColor: Color = FilledTextFieldTokens.FocusLeadingIconColor.value,
-        unfocusedLeadingIconColor: Color = FilledTextFieldTokens.LeadingIconColor.value,
-        disabledLeadingIconColor: Color = FilledTextFieldTokens.DisabledLeadingIconColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledLeadingIconOpacity),
-        errorLeadingIconColor: Color = FilledTextFieldTokens.ErrorLeadingIconColor.value,
-        focusedTrailingIconColor: Color = FilledTextFieldTokens.FocusTrailingIconColor.value,
-        unfocusedTrailingIconColor: Color = FilledTextFieldTokens.TrailingIconColor.value,
-        disabledTrailingIconColor: Color = FilledTextFieldTokens.DisabledTrailingIconColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledTrailingIconOpacity),
-        errorTrailingIconColor: Color = FilledTextFieldTokens.ErrorTrailingIconColor.value,
-        focusedLabelColor: Color = FilledTextFieldTokens.FocusLabelColor.value,
-        unfocusedLabelColor: Color = FilledTextFieldTokens.LabelColor.value,
-        disabledLabelColor: Color = FilledTextFieldTokens.DisabledLabelColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledLabelOpacity),
-        errorLabelColor: Color = FilledTextFieldTokens.ErrorLabelColor.value,
-        placeholderColor: Color = FilledTextFieldTokens.InputPlaceholderColor.value,
-        disabledPlaceholderColor: Color = FilledTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        focusedSupportingTextColor: Color = FilledTextFieldTokens.FocusSupportingColor.value,
-        unfocusedSupportingTextColor: Color = FilledTextFieldTokens.SupportingColor.value,
-        disabledSupportingTextColor: Color = FilledTextFieldTokens.DisabledSupportingColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledSupportingOpacity),
-        errorSupportingTextColor: Color = FilledTextFieldTokens.ErrorSupportingColor.value,
-        focusedPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value,
-        unfocusedPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value,
-        disabledPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        errorPrefixColor: Color = FilledTextFieldTokens.InputPrefixColor.value,
-        focusedSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value,
-        unfocusedSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value,
-        disabledSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value
-            .copy(alpha = FilledTextFieldTokens.DisabledInputOpacity),
-        errorSuffixColor: Color = FilledTextFieldTokens.InputSuffixColor.value,
-    ): TextFieldColors = colors(
-        focusedTextColor = textColor,
-        unfocusedTextColor = textColor,
-        disabledTextColor = disabledTextColor,
-        errorTextColor = textColor,
-        focusedContainerColor = containerColor,
-        unfocusedContainerColor = containerColor,
-        disabledContainerColor = containerColor,
-        errorContainerColor = containerColor,
-        cursorColor = cursorColor,
-        errorCursorColor = errorCursorColor,
-        selectionColors = selectionColors,
-        focusedIndicatorColor = focusedIndicatorColor,
-        unfocusedIndicatorColor = unfocusedIndicatorColor,
-        disabledIndicatorColor = disabledIndicatorColor,
-        errorIndicatorColor = errorIndicatorColor,
-        focusedLeadingIconColor = focusedLeadingIconColor,
-        unfocusedLeadingIconColor = unfocusedLeadingIconColor,
-        disabledLeadingIconColor = disabledLeadingIconColor,
-        errorLeadingIconColor = errorLeadingIconColor,
-        focusedTrailingIconColor = focusedTrailingIconColor,
-        unfocusedTrailingIconColor = unfocusedTrailingIconColor,
-        disabledTrailingIconColor = disabledTrailingIconColor,
-        errorTrailingIconColor = errorTrailingIconColor,
-        focusedLabelColor = focusedLabelColor,
-        unfocusedLabelColor = unfocusedLabelColor,
-        disabledLabelColor = disabledLabelColor,
-        errorLabelColor = errorLabelColor,
-        focusedPlaceholderColor = placeholderColor,
-        unfocusedPlaceholderColor = placeholderColor,
-        disabledPlaceholderColor = disabledPlaceholderColor,
-        errorPlaceholderColor = placeholderColor,
-        focusedSupportingTextColor = focusedSupportingTextColor,
-        unfocusedSupportingTextColor = unfocusedSupportingTextColor,
-        disabledSupportingTextColor = disabledSupportingTextColor,
-        errorSupportingTextColor = errorSupportingTextColor,
-        focusedPrefixColor = focusedPrefixColor,
-        unfocusedPrefixColor = unfocusedPrefixColor,
-        disabledPrefixColor = disabledPrefixColor,
-        errorPrefixColor = errorPrefixColor,
-        focusedSuffixColor = focusedSuffixColor,
-        unfocusedSuffixColor = unfocusedSuffixColor,
-        disabledSuffixColor = disabledSuffixColor,
-        errorSuffixColor = errorSuffixColor,
-    )
-
-    @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
-    @ExperimentalMaterial3Api
-    @Composable
-    fun outlinedTextFieldColors(
-        textColor: Color = OutlinedTextFieldTokens.InputColor.value,
-        disabledTextColor: Color = OutlinedTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        containerColor: Color = Color.Transparent,
-        cursorColor: Color = OutlinedTextFieldTokens.CaretColor.value,
-        errorCursorColor: Color = OutlinedTextFieldTokens.ErrorFocusCaretColor.value,
-        selectionColors: TextSelectionColors = LocalTextSelectionColors.current,
-        focusedBorderColor: Color = OutlinedTextFieldTokens.FocusOutlineColor.value,
-        unfocusedBorderColor: Color = OutlinedTextFieldTokens.OutlineColor.value,
-        disabledBorderColor: Color = OutlinedTextFieldTokens.DisabledOutlineColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledOutlineOpacity),
-        errorBorderColor: Color = OutlinedTextFieldTokens.ErrorOutlineColor.value,
-        focusedLeadingIconColor: Color = OutlinedTextFieldTokens.FocusLeadingIconColor.value,
-        unfocusedLeadingIconColor: Color = OutlinedTextFieldTokens.LeadingIconColor.value,
-        disabledLeadingIconColor: Color = OutlinedTextFieldTokens.DisabledLeadingIconColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledLeadingIconOpacity),
-        errorLeadingIconColor: Color = OutlinedTextFieldTokens.ErrorLeadingIconColor.value,
-        focusedTrailingIconColor: Color = OutlinedTextFieldTokens.FocusTrailingIconColor.value,
-        unfocusedTrailingIconColor: Color = OutlinedTextFieldTokens.TrailingIconColor.value,
-        disabledTrailingIconColor: Color = OutlinedTextFieldTokens.DisabledTrailingIconColor
-            .value.copy(alpha = OutlinedTextFieldTokens.DisabledTrailingIconOpacity),
-        errorTrailingIconColor: Color = OutlinedTextFieldTokens.ErrorTrailingIconColor.value,
-        focusedLabelColor: Color = OutlinedTextFieldTokens.FocusLabelColor.value,
-        unfocusedLabelColor: Color = OutlinedTextFieldTokens.LabelColor.value,
-        disabledLabelColor: Color = OutlinedTextFieldTokens.DisabledLabelColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledLabelOpacity),
-        errorLabelColor: Color = OutlinedTextFieldTokens.ErrorLabelColor.value,
-        placeholderColor: Color = OutlinedTextFieldTokens.InputPlaceholderColor.value,
-        disabledPlaceholderColor: Color = OutlinedTextFieldTokens.DisabledInputColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        focusedSupportingTextColor: Color = OutlinedTextFieldTokens.FocusSupportingColor.value,
-        unfocusedSupportingTextColor: Color = OutlinedTextFieldTokens.SupportingColor.value,
-        disabledSupportingTextColor: Color = OutlinedTextFieldTokens.DisabledSupportingColor
-            .value.copy(alpha = OutlinedTextFieldTokens.DisabledSupportingOpacity),
-        errorSupportingTextColor: Color = OutlinedTextFieldTokens.ErrorSupportingColor.value,
-        focusedPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value,
-        unfocusedPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value,
-        disabledPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        errorPrefixColor: Color = OutlinedTextFieldTokens.InputPrefixColor.value,
-        focusedSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value,
-        unfocusedSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value,
-        disabledSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value
-            .copy(alpha = OutlinedTextFieldTokens.DisabledInputOpacity),
-        errorSuffixColor: Color = OutlinedTextFieldTokens.InputSuffixColor.value,
-    ): TextFieldColors = OutlinedTextFieldDefaults.colors(
-        focusedTextColor = textColor,
-        unfocusedTextColor = textColor,
-        disabledTextColor = disabledTextColor,
-        errorTextColor = textColor,
-        focusedContainerColor = containerColor,
-        unfocusedContainerColor = containerColor,
-        disabledContainerColor = containerColor,
-        errorContainerColor = containerColor,
-        cursorColor = cursorColor,
-        errorCursorColor = errorCursorColor,
-        selectionColors = selectionColors,
-        focusedBorderColor = focusedBorderColor,
-        unfocusedBorderColor = unfocusedBorderColor,
-        disabledBorderColor = disabledBorderColor,
-        errorBorderColor = errorBorderColor,
-        focusedLeadingIconColor = focusedLeadingIconColor,
-        unfocusedLeadingIconColor = unfocusedLeadingIconColor,
-        disabledLeadingIconColor = disabledLeadingIconColor,
-        errorLeadingIconColor = errorLeadingIconColor,
-        focusedTrailingIconColor = focusedTrailingIconColor,
-        unfocusedTrailingIconColor = unfocusedTrailingIconColor,
-        disabledTrailingIconColor = disabledTrailingIconColor,
-        errorTrailingIconColor = errorTrailingIconColor,
-        focusedLabelColor = focusedLabelColor,
-        unfocusedLabelColor = unfocusedLabelColor,
-        disabledLabelColor = disabledLabelColor,
-        errorLabelColor = errorLabelColor,
-        focusedPlaceholderColor = placeholderColor,
-        unfocusedPlaceholderColor = placeholderColor,
-        disabledPlaceholderColor = disabledPlaceholderColor,
-        errorPlaceholderColor = placeholderColor,
-        focusedSupportingTextColor = focusedSupportingTextColor,
-        unfocusedSupportingTextColor = unfocusedSupportingTextColor,
-        disabledSupportingTextColor = disabledSupportingTextColor,
-        errorSupportingTextColor = errorSupportingTextColor,
-        focusedPrefixColor = focusedPrefixColor,
-        unfocusedPrefixColor = unfocusedPrefixColor,
-        disabledPrefixColor = disabledPrefixColor,
-        errorPrefixColor = errorPrefixColor,
-        focusedSuffixColor = focusedSuffixColor,
-        unfocusedSuffixColor = unfocusedSuffixColor,
-        disabledSuffixColor = disabledSuffixColor,
-        errorSuffixColor = errorSuffixColor,
-    )
-
-    @Deprecated("Use overload with prefix and suffix parameters", level = DeprecationLevel.HIDDEN)
-    @Composable
-    @ExperimentalMaterial3Api
-    fun TextFieldDecorationBox(
-        value: String,
-        innerTextField: @Composable () -> Unit,
-        enabled: Boolean,
-        singleLine: Boolean,
-        visualTransformation: VisualTransformation,
-        interactionSource: InteractionSource,
-        isError: Boolean = false,
-        label: @Composable (() -> Unit)? = null,
-        placeholder: @Composable (() -> Unit)? = null,
-        leadingIcon: @Composable (() -> Unit)? = null,
-        trailingIcon: @Composable (() -> Unit)? = null,
-        supportingText: @Composable (() -> Unit)? = null,
-        shape: Shape = TextFieldDefaults.shape,
-        colors: TextFieldColors = colors(),
-        contentPadding: PaddingValues =
-            if (label == null) {
-                contentPaddingWithoutLabel()
-            } else {
-                contentPaddingWithLabel()
-            },
-        container: @Composable () -> Unit = {
-            ContainerBox(enabled, isError, interactionSource, colors, shape)
-        }
-    ) {
-        DecorationBox(
-            value = value,
-            innerTextField = innerTextField,
-            enabled = enabled,
-            singleLine = singleLine,
-            visualTransformation = visualTransformation,
-            interactionSource = interactionSource,
-            isError = isError,
-            label = label,
-            placeholder = placeholder,
-            leadingIcon = leadingIcon,
-            trailingIcon = trailingIcon,
-            prefix = null,
-            suffix = null,
-            supportingText = supportingText,
-            shape = shape,
-            colors = colors,
-            contentPadding = contentPadding,
-            container = container,
-        )
-    }
-
-    @Deprecated("Use overload with prefix and suffix parameters", level = DeprecationLevel.HIDDEN)
-    @Composable
-    @ExperimentalMaterial3Api
-    fun OutlinedTextFieldDecorationBox(
-        value: String,
-        innerTextField: @Composable () -> Unit,
-        enabled: Boolean,
-        singleLine: Boolean,
-        visualTransformation: VisualTransformation,
-        interactionSource: InteractionSource,
-        isError: Boolean = false,
-        label: @Composable (() -> Unit)? = null,
-        placeholder: @Composable (() -> Unit)? = null,
-        leadingIcon: @Composable (() -> Unit)? = null,
-        trailingIcon: @Composable (() -> Unit)? = null,
-        supportingText: @Composable (() -> Unit)? = null,
-        colors: TextFieldColors = OutlinedTextFieldDefaults.colors(),
-        contentPadding: PaddingValues = OutlinedTextFieldDefaults.contentPadding(),
-        container: @Composable () -> Unit = {
-            OutlinedTextFieldDefaults.ContainerBox(enabled, isError, interactionSource, colors)
-        }
-    ) {
-        OutlinedTextFieldDefaults.DecorationBox(
-            value = value,
-            innerTextField = innerTextField,
-            enabled = enabled,
-            singleLine = singleLine,
-            visualTransformation = visualTransformation,
-            interactionSource = interactionSource,
-            isError = isError,
-            label = label,
-            placeholder = placeholder,
-            leadingIcon = leadingIcon,
-            trailingIcon = trailingIcon,
-            prefix = null,
-            suffix = null,
-            supportingText = supportingText,
-            colors = colors,
-            contentPadding = contentPadding,
-            container = container
-        )
-    }
 }
 
 /**
@@ -1694,8 +912,9 @@
                 focusedSupportingTextColor =
                 fromToken(OutlinedTextFieldTokens.FocusSupportingColor),
                 unfocusedSupportingTextColor = fromToken(OutlinedTextFieldTokens.SupportingColor),
-                disabledSupportingTextColor = OutlinedTextFieldTokens.DisabledSupportingColor
-                    .value.copy(alpha = OutlinedTextFieldTokens.DisabledSupportingOpacity),
+                disabledSupportingTextColor =
+                fromToken(OutlinedTextFieldTokens.DisabledSupportingColor)
+                    .copy(alpha = OutlinedTextFieldTokens.DisabledSupportingOpacity),
                 errorSupportingTextColor = fromToken(OutlinedTextFieldTokens.ErrorSupportingColor),
                 focusedPrefixColor = fromToken(OutlinedTextFieldTokens.InputPrefixColor),
                 unfocusedPrefixColor = fromToken(OutlinedTextFieldTokens.InputPrefixColor),
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt
index d410938..11f3179 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Arrangement.kt
@@ -84,6 +84,7 @@
          * fitting the arrangement to the size of the carousel.
          *
          * @param availableSpace the space the arrangement needs to fit
+         * @param itemSpacing the space between items in the arrangement
          * @param targetSmallSize the size small items would like to be
          * @param minSmallSize the minimum size of which small item sizes are allowed to be
          * @param maxSmallSize the maximum size of which small item sizes are allowed to be
@@ -100,6 +101,7 @@
          */
         fun findLowestCostArrangement(
             availableSpace: Float,
+            itemSpacing: Float,
             targetSmallSize: Float,
             minSmallSize: Float,
             maxSmallSize: Float,
@@ -117,6 +119,7 @@
                         val arrangement = fit(
                             priority = priority,
                             availableSpace = availableSpace,
+                            itemSpacing = itemSpacing,
                             smallCount = smallCount,
                             smallSize = targetSmallSize,
                             minSmallSize = minSmallSize,
@@ -155,8 +158,9 @@
          * adjusting small items as much as possible, then adjusting medium items as much as
          * possible, and finally adjusting large items if the arrangement is still unable to fit.
          *
-         * @param priority The priority to place on this particular arrangement of item counts
-         * @param availableSpace The space in which to fit the arrangement
+         * @param priority the priority to place on this particular arrangement of item counts
+         * @param availableSpace the space in which to fit the arrangement
+         * @param itemSpacing the space between itens
          * @param smallCount the number of small items to fit
          * @param smallSize the size of each small item
          * @param minSmallSize the minimum size a small item is allowed to be
@@ -170,6 +174,7 @@
         private fun fit(
             priority: Int,
             availableSpace: Float,
+            itemSpacing: Float,
             smallCount: Int,
             smallSize: Float,
             minSmallSize: Float,
@@ -179,6 +184,8 @@
             largeCount: Int,
             largeSize: Float
         ): Arrangement {
+            val totalItemCount = largeCount + mediumCount + smallCount
+            val availableSpaceWithoutSpacing = availableSpace - ((totalItemCount - 1) * itemSpacing)
             var arrangedSmallSize = smallSize.coerceIn(
                 minSmallSize,
                 maxSmallSize
@@ -188,7 +195,7 @@
 
             val totalSpaceTakenByArrangement = arrangedLargeSize * largeCount +
                 arrangedMediumSize * mediumCount + arrangedSmallSize * smallCount
-            val delta = availableSpace - totalSpaceTakenByArrangement
+            val delta = availableSpaceWithoutSpacing - totalSpaceTakenByArrangement
             // First, resize small items within their allowable min-max range to try to fit the
             // arrangement into the available space.
             if (smallCount > 0 && delta > 0) {
@@ -208,7 +215,7 @@
             // Zero out small size if there are no small items
             arrangedSmallSize = if (smallCount > 0) arrangedSmallSize else 0f
             arrangedLargeSize = calculateLargeSize(
-                availableSpace, smallCount, arrangedSmallSize,
+                availableSpaceWithoutSpacing, smallCount, arrangedSmallSize,
                 mediumCount, largeCount
             )
             arrangedMediumSize = (arrangedLargeSize + arrangedSmallSize) / 2f
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
index b34fc8f..b81a1fe 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
@@ -16,14 +16,26 @@
 
 package androidx.compose.material3.carousel
 
+import androidx.annotation.VisibleForTesting
 import androidx.collection.IntIntMap
-import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.TargetedFlingBehavior
+import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
+import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.PageSize
 import androidx.compose.foundation.pager.PagerDefaults
+import androidx.compose.foundation.pager.PagerSnapDistance
 import androidx.compose.foundation.pager.VerticalPager
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -45,6 +57,9 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastForEach
+import kotlin.math.ceil
 import kotlin.math.roundToInt
 
 /**
@@ -53,56 +68,73 @@
  * A horizontal carousel meant to display many items at once for quick browsing of smaller content
  * like album art or photo thumbnails.
  *
- * Note that this carousel may adjust the size of large items. In order to ensure a mix of large,
+ * Note that this carousel may adjust the size of items in order to ensure a mix of large,
  * medium, and small items fit perfectly into the available space and are arranged in a
- * visually pleasing way, this carousel finds the nearest number of large items that
- * will fit the container and adjusts their size to fit, if necessary.
+ * visually pleasing way. Carousel then lays out items using the large item size and clips
+ * (or masks) items depending on their scroll offset to create items which smoothly expand
+ * and collapse between the large, medium, and small sizes.
  *
  * For more information, see <a href="https://material.io/components/carousel/overview">design
  * guidelines</a>.
  *
+ * Example of a multi-browse carousel:
+ * @sample androidx.compose.material3.samples.HorizontalMultiBrowseCarouselSample
+ *
  * @param state The state object to be used to control the carousel's state
- * @param preferredItemSize The size fully visible items would like to be in the main axis. This
- * size is a target and will likely be adjusted by carousel in order to fit a whole number of
- * items within the container
+ * @param preferredItemWidth The width that large, fully visible items would like to be in the
+ * horizontal axis. This width is a target and will likely be adjusted by carousel in order to fit
+ * a whole number of items within the container. Carousel adjusts small items first (between the
+ * [minSmallItemWidth] and [maxSmallItemWidth]) then medium items when present, and finally large
+ * items if necessary.
  * @param modifier A modifier instance to be applied to this carousel container
  * @param itemSpacing The amount of space used to separate items in the carousel
- * @param minSmallSize The minimum allowable size of small masked items
- * @param maxSmallSize The maximum allowable size of small masked items
+ * @param flingBehavior The [TargetedFlingBehavior] to be used for post scroll gestures
+ * @param minSmallItemWidth The minimum allowable width of small items in dp. Depending on the
+ * [preferredItemWidth] and the width of the carousel, the small item width will be chosen from a
+ * range of [minSmallItemWidth] and [maxSmallItemWidth]
+ * @param maxSmallItemWidth The maximum allowable width of small items in dp. Depending on the
+ * [preferredItemWidth] and the width of the carousel, the small item width will be chosen from a
+ * range of [minSmallItemWidth] and [maxSmallItemWidth]
+ * @param contentPadding a padding around the whole content. This will add padding for the
+ * content after it has been clipped. You can use it to add a padding before the first item or
+ * after the last one. Use [itemSpacing] to add spacing between the items.
  * @param content The carousel's content Composable
- *
- * TODO: Add sample link
  */
 @ExperimentalMaterial3Api
 @Composable
-internal fun HorizontalMultiBrowseCarousel(
+fun HorizontalMultiBrowseCarousel(
     state: CarouselState,
-    preferredItemSize: Dp,
+    preferredItemWidth: Dp,
     modifier: Modifier = Modifier,
     itemSpacing: Dp = 0.dp,
-    minSmallSize: Dp = StrategyDefaults.MinSmallSize,
-    maxSmallSize: Dp = StrategyDefaults.MaxSmallSize,
+    flingBehavior: TargetedFlingBehavior =
+        CarouselDefaults.singleAdvanceFlingBehavior(state = state),
+    minSmallItemWidth: Dp = CarouselDefaults.MinSmallItemSize,
+    maxSmallItemWidth: Dp = CarouselDefaults.MaxSmallItemSize,
+    contentPadding: PaddingValues = PaddingValues(0.dp),
     content: @Composable CarouselScope.(itemIndex: Int) -> Unit
 ) {
     val density = LocalDensity.current
     Carousel(
         state = state,
         orientation = Orientation.Horizontal,
-        keylineList = { availableSpace ->
+        keylineList = { availableSpace, itemSpacingPx ->
             with(density) {
                 multiBrowseKeylineList(
                     density = this,
                     carouselMainAxisSize = availableSpace,
-                    preferredItemSize = preferredItemSize.toPx(),
-                    itemSpacing = itemSpacing.toPx(),
+                    preferredItemSize = preferredItemWidth.toPx(),
                     itemCount = state.itemCountState.value.invoke(),
-                    minSmallSize = minSmallSize.toPx(),
-                    maxSmallSize = maxSmallSize.toPx(),
+                    itemSpacing = itemSpacingPx,
+                    minSmallItemSize = minSmallItemWidth.toPx(),
+                    maxSmallItemSize = maxSmallItemWidth.toPx(),
                 )
             }
         },
+        contentPadding = contentPadding,
         modifier = modifier,
         itemSpacing = itemSpacing,
+        flingBehavior = flingBehavior,
         content = content
     )
 }
@@ -120,40 +152,48 @@
  * For more information, see <a href="https://material.io/components/carousel/overview">design
  * guidelines</a>.
  *
+ * Example of an uncontained carousel:
+ * @sample androidx.compose.material3.samples.HorizontalUncontainedCarouselSample
+ *
  * @param state The state object to be used to control the carousel's state
- * @param itemSize The size of items in the carousel
+ * @param itemWidth The width of items in the carousel
  * @param modifier A modifier instance to be applied to this carousel container
  * @param itemSpacing The amount of space used to separate items in the carousel
+ * @param flingBehavior The [TargetedFlingBehavior] to be used for post scroll gestures
+ * @param contentPadding a padding around the whole content. This will add padding for the
+ * content after it has been clipped. You can use it to add a padding before the first item or
+ * after the last one. Use [itemSpacing] to add spacing between the items.
  * @param content The carousel's content Composable
- *
- * TODO: Add sample link
  */
 @ExperimentalMaterial3Api
 @Composable
-internal fun HorizontalUncontainedCarousel(
+fun HorizontalUncontainedCarousel(
     state: CarouselState,
-    itemSize: Dp,
+    itemWidth: Dp,
     modifier: Modifier = Modifier,
     itemSpacing: Dp = 0.dp,
+    flingBehavior: TargetedFlingBehavior = CarouselDefaults.noSnapFlingBehavior(),
+    contentPadding: PaddingValues = PaddingValues(0.dp),
     content: @Composable CarouselScope.(itemIndex: Int) -> Unit
 ) {
     val density = LocalDensity.current
     Carousel(
         state = state,
         orientation = Orientation.Horizontal,
-        keylineList = {
+        keylineList = { availableSpace, itemSpacingPx ->
             with(density) {
                 uncontainedKeylineList(
                     density = this,
-                    carouselMainAxisSize = state.pagerState.layoutInfo.viewportSize.width.toFloat(),
-                    itemSize = itemSize.toPx(),
-                    itemSpacing = itemSpacing.toPx(),
+                    carouselMainAxisSize = availableSpace,
+                    itemSize = itemWidth.toPx(),
+                    itemSpacing = itemSpacingPx,
                 )
             }
         },
+        contentPadding = contentPadding,
         modifier = modifier,
         itemSpacing = itemSpacing,
-        flingBehavior = rememberDecaySnapFlingBehavior(),
+        flingBehavior = flingBehavior,
         content = content
     )
 }
@@ -165,31 +205,41 @@
  * chosen strategy.
  *
  * @param state The state object to be used to control the carousel's state.
- * @param modifier A modifier instance to be applied to this carousel outer layout
+ * @param orientation The layout orientation of the carousel
  * @param keylineList The list of keylines that are fixed positions along the scrolling axis which
  * define the state an item should be in when its center is co-located with the keyline's position.
+ * @param contentPadding a padding around the whole content. This will add padding for the
+ * @param modifier A modifier instance to be applied to this carousel outer layout
+ * content after it has been clipped. You can use it to add a padding before the first item or
+ * after the last one. Use [itemSpacing] to add spacing between the items.
  * @param itemSpacing The amount of space used to separate items in the carousel
- * @param orientation The layout orientation of the carousel
+ * @param flingBehavior The [TargetedFlingBehavior] to be used for post scroll gestures
  * @param content The carousel's content Composable where each call is passed the index, from the
  * total item count, of the item being composed
- * TODO: Add sample link
  */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 internal fun Carousel(
     state: CarouselState,
     orientation: Orientation,
-    keylineList: (availableSpace: Float) -> KeylineList?,
+    keylineList: (availableSpace: Float, itemSpacing: Float) -> KeylineList?,
+    contentPadding: PaddingValues,
     modifier: Modifier = Modifier,
     itemSpacing: Dp = 0.dp,
-    flingBehavior: TargetedFlingBehavior = PagerDefaults.flingBehavior(state = state.pagerState),
+    flingBehavior: TargetedFlingBehavior =
+        CarouselDefaults.singleAdvanceFlingBehavior(state = state),
     content: @Composable CarouselScope.(itemIndex: Int) -> Unit
 ) {
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    val pageSize = remember(keylineList) { CarouselPageSize(keylineList) }
+    val beforeContentPadding = contentPadding.calculateBeforeContentPadding(orientation)
+    val afterContentPadding = contentPadding.calculateAfterContentPadding(orientation)
+    val pageSize = remember(keylineList) {
+        CarouselPageSize(keylineList, beforeContentPadding, afterContentPadding)
+    }
 
-    // TODO: Update beyond bounds numbers according to Strategy
-    val outOfBoundsPageCount = 2
+    val outOfBoundsPageCount = remember(pageSize.strategy.itemMainAxisSize) {
+        calculateOutOfBounds(pageSize.strategy)
+    }
     val carouselScope = CarouselScopeImpl
 
     val snapPositionMap = remember(pageSize.strategy.itemMainAxisSize) {
@@ -203,6 +253,11 @@
     if (orientation == Orientation.Horizontal) {
         HorizontalPager(
             state = state.pagerState,
+            // Only pass cross axis padding as main axis padding will be handled by the strategy
+            contentPadding = PaddingValues(
+                top = contentPadding.calculateTopPadding(),
+                bottom = contentPadding.calculateBottomPadding()
+            ),
             pageSize = pageSize,
             pageSpacing = itemSpacing,
             outOfBoundsPageCount = outOfBoundsPageCount,
@@ -225,6 +280,11 @@
     } else if (orientation == Orientation.Vertical) {
         VerticalPager(
             state = state.pagerState,
+            // Only pass cross axis padding as main axis padding will be handled by the strategy
+            contentPadding = PaddingValues(
+                start = contentPadding.calculateStartPadding(LocalLayoutDirection.current),
+                end = contentPadding.calculateEndPadding(LocalLayoutDirection.current)
+            ),
             pageSize = pageSize,
             pageSpacing = itemSpacing,
             outOfBoundsPageCount = outOfBoundsPageCount,
@@ -247,6 +307,45 @@
     }
 }
 
+@Composable
+private fun PaddingValues.calculateBeforeContentPadding(orientation: Orientation): Float {
+    val dpValue = if (orientation == Orientation.Vertical) {
+        calculateTopPadding()
+    } else {
+        calculateStartPadding(LocalLayoutDirection.current)
+    }
+
+    return with(LocalDensity.current) { dpValue.toPx() }
+}
+
+@Composable
+private fun PaddingValues.calculateAfterContentPadding(orientation: Orientation): Float {
+    val dpValue = if (orientation == Orientation.Vertical) {
+        calculateBottomPadding()
+    } else {
+        calculateEndPadding(LocalLayoutDirection.current)
+    }
+
+    return with(LocalDensity.current) { dpValue.toPx() }
+}
+
+internal fun calculateOutOfBounds(strategy: Strategy): Int {
+    if (!strategy.isValid()) {
+        return PagerDefaults.OutOfBoundsPageCount
+    }
+    var totalKeylineSpace = 0f
+    var totalNonAnchorKeylines = 0
+    strategy.defaultKeylines.fastFilter { !it.isAnchor }.fastForEach {
+        totalKeylineSpace += it.size
+        totalNonAnchorKeylines += 1
+    }
+    val itemsLoaded = ceil(totalKeylineSpace / strategy.itemMainAxisSize).toInt()
+    val itemsToLoad = totalNonAnchorKeylines - itemsLoaded
+
+    // We must also load the next item when scrolling
+    return itemsToLoad + 1
+}
+
 /**
  * A [PageSize] implementation that maintains a strategy that is kept up-to-date with the
  * latest available space of the container.
@@ -254,10 +353,19 @@
  * @param keylineList The list of keylines that are fixed positions along the scrolling axis which
  * define the state an item should be in when its center is co-located with the keyline's position.
  */
-private class CarouselPageSize(keylineList: (availableSpace: Float) -> KeylineList?) : PageSize {
+private class CarouselPageSize(
+    keylineList: (availableSpace: Float, itemSpacing: Float) -> KeylineList?,
+    private val beforeContentPadding: Float,
+    private val afterContentPadding: Float
+) : PageSize {
     val strategy = Strategy(keylineList)
     override fun Density.calculateMainAxisPageSize(availableSpace: Int, pageSpacing: Int): Int {
-        strategy.apply(availableSpace.toFloat())
+        strategy.apply(
+            availableSpace.toFloat(),
+            pageSpacing.toFloat(),
+            beforeContentPadding,
+            afterContentPadding
+        )
         return if (strategy.isValid()) {
             strategy.itemMainAxisSize.roundToInt()
         } else {
@@ -293,9 +401,9 @@
  * @param state the carousel state
  * @param strategy the strategy used to mask and translate items in the carousel
  * @param itemPositionMap the position of each index when it is the current item
- * @param isRtl whether or not the carousel is rtl
+ * @param isRtl true if the layout direction is right-to-left
  */
-@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class)
 internal fun Modifier.carouselItem(
     index: Int,
     state: CarouselState,
@@ -303,17 +411,8 @@
     itemPositionMap: IntIntMap,
     isRtl: Boolean
 ): Modifier {
-    val viewportSize = state.pagerState.layoutInfo.viewportSize
-    val orientation = state.pagerState.layoutInfo.orientation
-    val isVertical = orientation == Orientation.Vertical
-    val mainAxisCarouselSize = if (isVertical) viewportSize.height else viewportSize.width
-
-    if (mainAxisCarouselSize == 0 || !strategy.isValid()) {
-        return this
-    }
-    val itemsCount = state.pagerState.pageCount
-    val maxScrollOffset =
-        itemsCount * strategy.itemMainAxisSize - mainAxisCarouselSize
+    if (!strategy.isValid()) return this
+    val isVertical = state.pagerState.layoutInfo.orientation == Orientation.Vertical
 
     return layout { measurable, constraints ->
         // Force the item to use the strategy's itemMainAxisSize along its main axis
@@ -339,16 +438,15 @@
             placeable.place(0, 0)
         }
     }.graphicsLayer {
-        val currentItemScrollOffset =
-            (state.pagerState.currentPage * strategy.itemMainAxisSize) +
-                (state.pagerState.currentPageOffsetFraction * strategy.itemMainAxisSize)
-        val scrollOffset = currentItemScrollOffset -
-            (if (itemPositionMap.size > 0) itemPositionMap[state.pagerState.currentPage] else 0)
+        val scrollOffset = calculateCurrentScrollOffset(state, strategy, itemPositionMap)
+        val maxScrollOffset = calculateMaxScrollOffset(state, strategy)
+        // TODO: Reduce the number of times a keyline for the same scroll offset is calculated
         val keylines = strategy.getKeylineListForScrollOffset(scrollOffset, maxScrollOffset)
 
         // Find center of the item at this index
+        val itemSizeWithSpacing = strategy.itemMainAxisSize + strategy.itemSpacing
         val unadjustedCenter =
-            (index * strategy.itemMainAxisSize) + (strategy.itemMainAxisSize / 2f) - scrollOffset
+            (index * itemSizeWithSpacing) + (strategy.itemMainAxisSize / 2f) - scrollOffset
 
         // Find the keyline before and after this item's center and create an interpolated
         // keyline that the item should use for its clip shape and offset
@@ -364,6 +462,7 @@
         clip = true
         shape = object : Shape {
             // TODO: Find a way to use the shape of the item set by the client for each item
+            // TODO: Allow corner size customization
             val roundedCornerShape = RoundedCornerShape(ShapeDefaults.ExtraLarge.topStart)
             override fun createOutline(
                 size: Size,
@@ -420,6 +519,32 @@
     }
 }
 
+/** Calculates the current scroll offset given item count, sizing, spacing, and snap position. */
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun calculateCurrentScrollOffset(
+    state: CarouselState,
+    strategy: Strategy,
+    snapPositionMap: IntIntMap
+): Float {
+    val itemSizeWithSpacing = strategy.itemMainAxisSize + strategy.itemSpacing
+    val currentItemScrollOffset =
+        (state.pagerState.currentPage * itemSizeWithSpacing) +
+            (state.pagerState.currentPageOffsetFraction * itemSizeWithSpacing)
+    return currentItemScrollOffset -
+        (if (snapPositionMap.size > 0) snapPositionMap[state.pagerState.currentPage] else 0)
+}
+
+/** Returns the max scroll offset given the item count, sizing, and spacing. */
+@OptIn(ExperimentalMaterial3Api::class)
+@VisibleForTesting
+internal fun calculateMaxScrollOffset(state: CarouselState, strategy: Strategy): Float {
+    val itemCount = state.pagerState.pageCount.toFloat()
+    val maxScrollPossible = (strategy.itemMainAxisSize * itemCount) +
+        (strategy.itemSpacing * (itemCount - 1))
+
+    return (maxScrollPossible - strategy.availableSpace).coerceAtLeast(0f)
+}
+
 /**
  * Returns a float between 0 and 1 that represents how far [unadjustedOffset] is between
  * [before] and [after].
@@ -437,3 +562,111 @@
     val total = after.unadjustedOffset - before.unadjustedOffset
     return (unadjustedOffset - before.unadjustedOffset) / total
 }
+
+/**
+ * Contains the default values used by [Carousel].
+ */
+@ExperimentalMaterial3Api
+object CarouselDefaults {
+
+    /**
+     * A [TargetedFlingBehavior] that limits a fling to one item at a time. [snapAnimationSpec] can
+     * be used to control the snap animation.
+     *
+     * @param state The [CarouselState] that controls which Carousel this TargetedFlingBehavior will
+     * be applied to.
+     * @param snapAnimationSpec The animation spec used to finally snap to the position.
+     * @return An instance of [TargetedFlingBehavior] that performs snapping to the next item.
+     * The animation will be governed by the post scroll velocity and the Carousel will use
+     * [snapAnimationSpec] to approach the snapped position
+     */
+    @Composable
+    fun singleAdvanceFlingBehavior(
+        state: CarouselState,
+        snapAnimationSpec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
+    ): TargetedFlingBehavior {
+        return PagerDefaults.flingBehavior(
+            state = state.pagerState,
+            pagerSnapDistance = PagerSnapDistance.atMost(1),
+            snapAnimationSpec = snapAnimationSpec,
+        )
+    }
+
+    /**
+     * A [TargetedFlingBehavior] that flings and snaps according to the gesture velocity.
+     * [snapAnimationSpec] and [decayAnimationSpec] can be used to control the animation specs.
+     *
+     * The Carousel may use [decayAnimationSpec] or [snapAnimationSpec] to approach the target item
+     * post-scroll, depending on the gesture velocity.
+     * If the gesture has a high enough velocity to approach the target item, the Carousel will use
+     * [decayAnimationSpec] followed by [snapAnimationSpec] for the final step of the animation.
+     * If the gesture doesn't have enough velocity, it will use [snapAnimationSpec] +
+     * [snapAnimationSpec] in a similar fashion.
+     *
+     * @param state The [CarouselState] that controls which Carousel this TargetedFlingBehavior will
+     * be applied to.
+     * @param decayAnimationSpec The animation spec used to approach the target offset when the
+     * the fling velocity is large enough to naturally decay.
+     * @param snapAnimationSpec The animation spec used to finally snap to the position.
+     * @return An instance of [TargetedFlingBehavior] that performs flinging based on the gesture
+     * velocity and then snapping to the closest item post-fling.
+     * The animation will be governed by the post scroll velocity and the Carousel will use
+     * [snapAnimationSpec] to approach the snapped position
+     */
+    @Composable
+    fun multiBrowseFlingBehavior(
+        state: CarouselState,
+        decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
+        snapAnimationSpec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
+    ): TargetedFlingBehavior {
+        val pagerSnapDistance = object : PagerSnapDistance {
+            override fun calculateTargetPage(
+                startPage: Int,
+                suggestedTargetPage: Int,
+                velocity: Float,
+                pageSize: Int,
+                pageSpacing: Int
+            ): Int {
+                return suggestedTargetPage
+            }
+        }
+        return PagerDefaults.flingBehavior(
+            state = state.pagerState,
+            pagerSnapDistance = pagerSnapDistance,
+            decayAnimationSpec = decayAnimationSpec,
+            snapAnimationSpec = snapAnimationSpec,
+        )
+    }
+
+    /**
+     * A [TargetedFlingBehavior] that flings according to the gesture velocity and does not snap
+     * post-fling.
+     *
+     * @return An instance of [TargetedFlingBehavior] that performs flinging based on the gesture
+     * velocity and does not snap to anything post-fling.
+     */
+    @Composable
+    fun noSnapFlingBehavior(): TargetedFlingBehavior {
+        val splineDecay = rememberSplineBasedDecay<Float>()
+        val decayLayoutInfoProvider = remember {
+            object : SnapLayoutInfoProvider {
+                override fun calculateApproachOffset(initialVelocity: Float): Float {
+                    return splineDecay.calculateTargetValue(0f, initialVelocity)
+                }
+
+                override fun calculateSnappingOffset(currentVelocity: Float): Float = 0f
+            }
+        }
+
+        return rememberSnapFlingBehavior(snapLayoutInfoProvider = decayLayoutInfoProvider)
+    }
+
+    /** The minimum size that a carousel strategy can choose its small items to be. **/
+    internal val MinSmallItemSize = 40.dp
+
+    /** The maximum size that a carousel strategy can choose its small items to be. **/
+    internal val MaxSmallItemSize = 56.dp
+
+    internal val AnchorSize = 10.dp
+    internal const val MediumLargeItemDiffThreshold = 0.85f
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselScope.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselScope.kt
index 94dbda2..467d8a5 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselScope.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselScope.kt
@@ -22,7 +22,7 @@
  * Receiver scope for [Carousel].
  */
 @ExperimentalMaterial3Api
-internal sealed interface CarouselScope
+sealed interface CarouselScope
 
 @ExperimentalMaterial3Api
 internal object CarouselScopeImpl : CarouselScope
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
index b6d989a..4c26f8a 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/CarouselState.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.material3.carousel
 
-import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.annotation.FloatRange
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableState
@@ -32,14 +32,15 @@
  * The state that can be used to control all types of carousels.
  *
  * @param currentItem the current item to be scrolled to.
- * @param currentItemOffsetFraction the current item offset as a fraction of the item size.
+ * @param currentItemOffsetFraction the offset of the current item as a fraction of the item's size.
+ * This should vary between -0.5 and 0.5 and indicates how to offset the current item from the
+ * snapped position.
  * @param itemCount the number of items this Carousel will have.
  */
-@OptIn(ExperimentalFoundationApi::class)
 @ExperimentalMaterial3Api
-internal class CarouselState(
+class CarouselState(
     currentItem: Int = 0,
-    currentItemOffsetFraction: Float = 0F,
+    @FloatRange(from = -0.5, to = 0.5) currentItemOffsetFraction: Float = 0f,
     itemCount: () -> Int
 ) : ScrollableState {
     var itemCountState = mutableStateOf(itemCount)
@@ -61,6 +62,7 @@
         pagerState.scroll(scrollPriority, block)
     }
 
+    @ExperimentalMaterial3Api
     companion object {
         /**
          * To keep current item and item offset saved
@@ -92,7 +94,7 @@
  */
 @ExperimentalMaterial3Api
 @Composable
-internal fun rememberCarouselState(
+fun rememberCarouselState(
     initialItem: Int = 0,
     itemCount: () -> Int,
 ): CarouselState {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt
index 88a1ecc..a0125af 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineList.kt
@@ -229,12 +229,17 @@
  */
 internal fun keylineListOf(
     carouselMainAxisSize: Float,
+    itemSpacing: Float,
     carouselAlignment: CarouselAlignment,
     keylines: KeylineListScope.() -> Unit
 ): KeylineList {
     val keylineListScope = KeylineListScopeImpl()
     keylines.invoke(keylineListScope)
-    return keylineListScope.createWithAlignment(carouselMainAxisSize, carouselAlignment)
+    return keylineListScope.createWithAlignment(
+        carouselMainAxisSize,
+        itemSpacing,
+        carouselAlignment
+    )
 }
 
 /**
@@ -243,13 +248,19 @@
  */
 internal fun keylineListOf(
     carouselMainAxisSize: Float,
+    itemSpacing: Float,
     pivotIndex: Int,
     pivotOffset: Float,
     keylines: KeylineListScope.() -> Unit
 ): KeylineList {
     val keylineListScope = KeylineListScopeImpl()
     keylines.invoke(keylineListScope)
-    return keylineListScope.createWithPivot(carouselMainAxisSize, pivotIndex, pivotOffset)
+    return keylineListScope.createWithPivot(
+        carouselMainAxisSize,
+        itemSpacing,
+        pivotIndex,
+        pivotOffset
+    )
 }
 
 /** Receiver scope for creating a [KeylineList] using [keylineListOf] */
@@ -290,6 +301,7 @@
 
     fun createWithPivot(
         carouselMainAxisSize: Float,
+        itemSpacing: Float,
         pivotIndex: Int,
         pivotOffset: Float
     ): KeylineList {
@@ -300,6 +312,7 @@
             findLastFocalIndex(),
             itemMainAxisSize = focalItemSize,
             carouselMainAxisSize = carouselMainAxisSize,
+            itemSpacing,
             tmpKeylines
         )
         return KeylineList(keylines)
@@ -307,6 +320,7 @@
 
     fun createWithAlignment(
         carouselMainAxisSize: Float,
+        itemSpacing: Float,
         carouselAlignment: CarouselAlignment
     ): KeylineList {
         val lastFocalIndex = findLastFocalIndex()
@@ -315,7 +329,16 @@
         pivotIndex = firstFocalIndex
         pivotOffset = when (carouselAlignment) {
             CarouselAlignment.Center -> {
-                (carouselMainAxisSize / 2) - ((focalItemSize / 2) * focalItemCount)
+                // If there is an even number of keylines, the itemSpacing will be placed in the
+                // center of the container. Divide the item spacing by half before subtracting
+                // the pivot item's center.
+                val itemSpacingSplit = if (itemSpacing == 0f || focalItemCount.mod(2) == 0) {
+                    0f
+                } else {
+                    itemSpacing / 2f
+                }
+                (carouselMainAxisSize / 2) - ((focalItemSize / 2) * focalItemCount) -
+                    itemSpacingSplit
             }
             CarouselAlignment.End -> carouselMainAxisSize - (focalItemSize / 2)
             // Else covers and defaults to CarouselAlignment.Start
@@ -329,6 +352,7 @@
             lastFocalIndex,
             itemMainAxisSize = focalItemSize,
             carouselMainAxisSize = carouselMainAxisSize,
+            itemSpacing,
             tmpKeylines
         )
         return KeylineList(keylines)
@@ -375,6 +399,7 @@
         lastFocalIndex: Int,
         itemMainAxisSize: Float,
         carouselMainAxisSize: Float,
+        itemSpacing: Float,
         tmpKeylines: List<TmpKeyline>
     ): List<Keyline> {
         val pivot = tmpKeylines[pivotIndex]
@@ -406,8 +431,8 @@
         // Convert all TmpKeylines before the pivot to Keylines by calculating their offset,
         // unadjustedOffset, and cutoff and insert them at the beginning of the keyline list,
         // maintaining the tmpKeyline list's original order.
-        var offset = pivotOffset - (itemMainAxisSize / 2)
-        var unadjustedOffset = pivotOffset - (itemMainAxisSize / 2)
+        var offset = pivotOffset - (itemMainAxisSize / 2) - itemSpacing
+        var unadjustedOffset = pivotOffset - (itemMainAxisSize / 2) - itemSpacing
         (pivotIndex - 1 downTo 0).forEach { originalIndex ->
             val tmp = tmpKeylines[originalIndex]
             val tmpOffset = offset - (tmp.size / 2)
@@ -426,15 +451,15 @@
                 )
             )
 
-            offset -= tmp.size
-            unadjustedOffset -= itemMainAxisSize
+            offset -= tmp.size + itemSpacing
+            unadjustedOffset -= itemMainAxisSize + itemSpacing
         }
 
         // Convert all TmpKeylines after the pivot to Keylines by calculating their offset,
         // unadjustedOffset, and cutoff and inserting them at the end of the keyline list,
         // maintaining the tmpKeyline list's original order.
-        offset = pivotOffset + (itemMainAxisSize / 2)
-        unadjustedOffset = pivotOffset + (itemMainAxisSize / 2)
+        offset = pivotOffset + (itemMainAxisSize / 2) + itemSpacing
+        unadjustedOffset = pivotOffset + (itemMainAxisSize / 2) + itemSpacing
         (pivotIndex + 1 until tmpKeylines.size).forEach { originalIndex ->
             val tmp = tmpKeylines[originalIndex]
             val tmpOffset = offset + (tmp.size / 2)
@@ -456,8 +481,8 @@
                 )
             )
 
-            offset += tmp.size
-            unadjustedOffset += itemMainAxisSize
+            offset += tmp.size + itemSpacing
+            unadjustedOffset += itemMainAxisSize + itemSpacing
         }
 
         return keylines
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
index 7dfc4b9..9caf196 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
@@ -19,15 +19,7 @@
 import androidx.collection.IntIntMap
 import androidx.collection.emptyIntIntMap
 import androidx.collection.mutableIntIntMapOf
-import androidx.compose.animation.core.calculateTargetValue
-import androidx.compose.animation.rememberSplineBasedDecay
-import androidx.compose.foundation.gestures.TargetedFlingBehavior
-import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.gestures.snapping.SnapPosition
-import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.roundToInt
@@ -52,21 +44,21 @@
     val endStepsSize = endKeylineSteps.size + numOfFocalKeylines
 
     for (itemIndex in 0 until itemCount) {
-        map[itemIndex] = (defaultKeylines.firstFocal.offset -
-            defaultKeylines.firstFocal.size / 2F).roundToInt()
+        map[itemIndex] = (defaultKeylines.firstFocal.unadjustedOffset -
+            strategy.itemMainAxisSize / 2F).roundToInt()
         if (itemIndex < startStepsSize) {
             var startIndex = max(0, startStepsSize - 1 - itemIndex)
             startIndex = min(startKeylineSteps.size - 1, startIndex)
             val startKeylines = startKeylineSteps[startIndex]
-            map[itemIndex] = (startKeylines.firstFocal.offset -
-                startKeylines.firstFocal.size / 2f).roundToInt()
+            map[itemIndex] = (startKeylines.firstFocal.unadjustedOffset -
+                strategy.itemMainAxisSize / 2f).roundToInt()
         }
         if (itemCount > numOfFocalKeylines + 1 && itemIndex >= itemCount - endStepsSize) {
             var endIndex = max(0, itemIndex - itemCount + endStepsSize)
             endIndex = min(endKeylineSteps.size - 1, endIndex)
             val endKeylines = endKeylineSteps[endIndex]
-            map[itemIndex] = (endKeylines.firstFocal.offset -
-                endKeylines.firstFocal.size / 2f).roundToInt()
+            map[itemIndex] = (endKeylines.firstFocal.unadjustedOffset -
+                strategy.itemMainAxisSize / 2f).roundToInt()
         }
     }
     return map
@@ -85,20 +77,3 @@
             return if (snapPositions.size > 0) snapPositions[itemIndex] else 0
         }
     }
-
-@ExperimentalMaterial3Api
-@Composable
-internal fun rememberDecaySnapFlingBehavior(): TargetedFlingBehavior {
-    val splineDecay = rememberSplineBasedDecay<Float>()
-    val decayLayoutInfoProvider = remember {
-        object : SnapLayoutInfoProvider {
-            override fun calculateApproachOffset(initialVelocity: Float): Float {
-                return splineDecay.calculateTargetValue(0f, initialVelocity)
-            }
-
-            override fun calculateSnappingOffset(currentVelocity: Float): Float = 0f
-        }
-    }
-
-    return rememberSnapFlingBehavior(snapLayoutInfoProvider = decayLayoutInfoProvider)
-}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
index 0ddfab3..6b47ff5 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keylines.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.material3.carousel
 
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.ui.unit.Density
 import kotlin.math.ceil
 import kotlin.math.floor
@@ -39,17 +40,18 @@
  * @param preferredItemSize the desired size of large items, in pixels, in the main scrolling axis
  * @param itemSpacing the spacing between items in pixels
  * @param itemCount the number of items in the carousel
- * @param minSmallSize the minimum allowable size of small items in pixels
- * @param maxSmallSize the maximum allowable size of small items in pixels
+ * @param minSmallItemSize the minimum allowable size of small items in pixels
+ * @param maxSmallItemSize the maximum allowable size of small items in pixels
  */
+@OptIn(ExperimentalMaterial3Api::class)
 internal fun multiBrowseKeylineList(
     density: Density,
     carouselMainAxisSize: Float,
     preferredItemSize: Float,
     itemSpacing: Float,
     itemCount: Int,
-    minSmallSize: Float = with(density) { StrategyDefaults.MinSmallSize.toPx() },
-    maxSmallSize: Float = with(density) { StrategyDefaults.MaxSmallSize.toPx() },
+    minSmallItemSize: Float = with(density) { CarouselDefaults.MinSmallItemSize.toPx() },
+    maxSmallItemSize: Float = with(density) { CarouselDefaults.MaxSmallItemSize.toPx() },
 ): KeylineList? {
     if (carouselMainAxisSize == 0f || preferredItemSize == 0f) {
         return null
@@ -58,18 +60,15 @@
     var smallCounts: IntArray = intArrayOf(1)
     val mediumCounts: IntArray = intArrayOf(1, 0)
 
-    val targetLargeSize: Float = min(preferredItemSize + itemSpacing, carouselMainAxisSize)
+    val targetLargeSize: Float = min(preferredItemSize, carouselMainAxisSize)
     // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size
     // of the large item and medium items are sized between large and small items. Clamp the
     // small target size within our min-max range and as close to 1/3 of the target large item
     // size as possible.
-    val targetSmallSize: Float = (targetLargeSize / 3f + itemSpacing).coerceIn(
-        minSmallSize + itemSpacing,
-        maxSmallSize + itemSpacing
-    )
+    val targetSmallSize: Float = (targetLargeSize / 3f).coerceIn(minSmallItemSize, maxSmallItemSize)
     val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f
 
-    if (carouselMainAxisSize < minSmallSize * 2) {
+    if (carouselMainAxisSize < minSmallItemSize * 2) {
         // If the available space is too small to fit a large item and small item (where a large
         // item is bigger than a small item), allow arrangements with
         // no small items.
@@ -79,19 +78,20 @@
     // Find the minimum space left for large items after filling the carousel with the most
     // permissible medium and small items to determine a plausible minimum large count.
     val minAvailableLargeSpace = carouselMainAxisSize - targetMediumSize * mediumCounts.max() -
-        maxSmallSize * smallCounts.max()
+        maxSmallItemSize * smallCounts.max()
     val minLargeCount = max(
         1,
         floor(minAvailableLargeSpace / targetLargeSize).toInt())
     val maxLargeCount = ceil(carouselMainAxisSize / targetLargeSize).toInt()
 
     val largeCounts = IntArray(maxLargeCount - minLargeCount + 1) { maxLargeCount - it }
-    val anchorSize = with(density) { StrategyDefaults.AnchorSize.toPx() }
+    val anchorSize = with(density) { CarouselDefaults.AnchorSize.toPx() }
     var arrangement = Arrangement.findLowestCostArrangement(
         availableSpace = carouselMainAxisSize,
+        itemSpacing = itemSpacing,
         targetSmallSize = targetSmallSize,
-        minSmallSize = minSmallSize,
-        maxSmallSize = maxSmallSize,
+        minSmallSize = minSmallItemSize,
+        maxSmallSize = maxSmallItemSize,
         smallCounts = smallCounts,
         targetMediumSize = targetMediumSize,
         mediumCounts = mediumCounts,
@@ -105,21 +105,22 @@
         var mediumCount = arrangement.mediumCount
         while (keylineSurplus > 0) {
             if (smallCount > 0) {
-                smallCount -= 1;
+                smallCount -= 1
             } else if (mediumCount > 1) {
                 // Keep at least 1 medium so the large items don't fill the entire carousel in new
                 // strategy.
-                mediumCount -= 1;
+                mediumCount -= 1
             }
             // large items don't need to be removed even if they are a surplus because large items
             // are already fully unmasked.
-            keylineSurplus -= 1;
+            keylineSurplus -= 1
         }
         arrangement = Arrangement.findLowestCostArrangement(
             availableSpace = carouselMainAxisSize,
+            itemSpacing = itemSpacing,
             targetSmallSize = targetSmallSize,
-            minSmallSize = minSmallSize,
-            maxSmallSize = maxSmallSize,
+            minSmallSize = minSmallItemSize,
+            maxSmallSize = maxSmallItemSize,
             smallCounts = intArrayOf(smallCount),
             targetMediumSize = targetMediumSize,
             mediumCounts = intArrayOf(mediumCount),
@@ -134,6 +135,7 @@
 
     return createLeftAlignedKeylineList(
         carouselMainAxisSize = carouselMainAxisSize,
+        itemSpacing = itemSpacing,
         rightAnchorSize = anchorSize,
         leftAnchorSize = anchorSize,
         arrangement = arrangement
@@ -142,11 +144,12 @@
 
 internal fun createLeftAlignedKeylineList(
     carouselMainAxisSize: Float,
+    itemSpacing: Float,
     leftAnchorSize: Float,
     rightAnchorSize: Float,
     arrangement: Arrangement
 ): KeylineList {
-    return keylineListOf(carouselMainAxisSize, CarouselAlignment.Start) {
+    return keylineListOf(carouselMainAxisSize, itemSpacing, CarouselAlignment.Start) {
         add(leftAnchorSize, isAnchor = true)
 
         repeat(arrangement.largeCount) { add(arrangement.largeSize) }
@@ -171,6 +174,7 @@
  * @param itemSize the size of large items, in pixels, in the main scrolling axis
  * @param itemSpacing the spacing between items in pixels
  */
+@OptIn(ExperimentalMaterial3Api::class)
 internal fun uncontainedKeylineList(
     density: Density,
     carouselMainAxisSize: Float,
@@ -188,7 +192,7 @@
 
     val mediumCount = if (remainingSpace > 0) 1 else 0
 
-    val defaultAnchorSize = with(density) { StrategyDefaults.AnchorSize.toPx() }
+    val defaultAnchorSize = with(density) { CarouselDefaults.AnchorSize.toPx() }
     val mediumItemSize = calculateMediumChildSize(
         minimumMediumSize = defaultAnchorSize,
         largeItemSize = largeItemSize,
@@ -209,9 +213,11 @@
     val leftAnchorSize: Float = max(xSmallSize, mediumItemSize * 0.5f)
     return createLeftAlignedKeylineList(
         carouselMainAxisSize = carouselMainAxisSize,
+        itemSpacing = itemSpacing,
         leftAnchorSize = leftAnchorSize,
         rightAnchorSize = defaultAnchorSize,
-        arrangement = arrangement)
+        arrangement = arrangement
+    )
 }
 
 /**
@@ -219,6 +225,7 @@
  * size, and arbitrarily chooses a size small enough such that there is a size disparity between
  * the medium and large sizes, but large enough to have a sufficient percentage cut off.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 private fun calculateMediumChildSize(
     minimumMediumSize: Float,
     largeItemSize: Float,
@@ -235,7 +242,7 @@
     // it's too similar and won't create sufficient motion when scrolling items between the large
     // items and the medium item.
     val largeItemThreshold: Float =
-        largeItemSize * StrategyDefaults.MediumLargeItemDiffThreshold
+        largeItemSize * CarouselDefaults.MediumLargeItemDiffThreshold
     if (mediumItemSize > largeItemThreshold) {
         // Choose whichever is bigger between the maximum threshold of the medium child size, or
         // a size such that only 20% of the space is cut off.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
index 0048b14..916efe0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Strategy.kt
@@ -22,23 +22,15 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMapIndexed
 import androidx.compose.ui.util.lerp
+import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.roundToInt
 
 /**
- * Contains default values used across Strategies
- */
-internal object StrategyDefaults {
-    val MinSmallSize = 40.dp
-    val MaxSmallSize = 56.dp
-    val AnchorSize = 10.dp
-    const val MediumLargeItemDiffThreshold = 0.85f
-}
-
-/**
  * A class responsible for supplying carousel with a [KeylineList] that is corrected for scroll
  * offset, layout direction, and snapping behaviors.
  *
@@ -59,7 +51,7 @@
  * carousel's available space. This function will be called anytime availableSpace changes.
  */
 internal class Strategy(
-    private val keylineList: (availableSpace: Float) -> KeylineList?
+    private val keylineList: (availableSpace: Float, itemSpacing: Float) -> KeylineList?
 ) {
 
     /** The keylines generated from the [keylineList] block. */
@@ -92,7 +84,11 @@
     private lateinit var endShiftPoints: FloatList
 
     /** The available space in the main axis used in the most recent call to [apply]. */
-    private var availableSpace: Float = 0f
+    internal var availableSpace: Float = 0f
+    /** The spacing between each item. */
+    internal var itemSpacing: Float = 0f
+    internal var beforeContentPadding: Float = 0f
+    internal var afterContentPadding: Float = 0f
     /** The size of items when in focus and fully unmasked. */
     internal var itemMainAxisSize by mutableFloatStateOf(0f)
 
@@ -110,24 +106,32 @@
      * This method must be called before a strategy can be used by carousel.
      *
      * @param availableSpace the size of the carousel container in scrolling axis
+     * @param beforeContentPadding the padding to add before the list content
+     * @param afterContentPadding the padding to add after the list content
      */
-    internal fun apply(availableSpace: Float): Strategy {
+    internal fun apply(
+        availableSpace: Float,
+        itemSpacing: Float,
+        beforeContentPadding: Float,
+        afterContentPadding: Float
+    ): Strategy {
         // Skip computing new keylines and updating this strategy if
         // available space has not changed.
-        if (this.availableSpace == availableSpace) {
+        if (this.availableSpace == availableSpace && this.itemSpacing == itemSpacing) {
             return this
         }
 
-        val keylineList = keylineList.invoke(availableSpace) ?: return this
-        val startKeylineSteps = getStartKeylineSteps(keylineList, availableSpace)
+        val keylineList = keylineList.invoke(availableSpace, itemSpacing) ?: return this
+        val startKeylineSteps =
+            getStartKeylineSteps(keylineList, availableSpace, itemSpacing, beforeContentPadding)
         val endKeylineSteps =
-            getEndKeylineSteps(keylineList, availableSpace)
+            getEndKeylineSteps(keylineList, availableSpace, itemSpacing, afterContentPadding)
 
         // TODO: Update this to use the first/last focal keylines to calculate shift?
-        val startShiftDistance = startKeylineSteps.last().first().unadjustedOffset -
-            keylineList.first().unadjustedOffset
-        val endShiftDistance = keylineList.last().unadjustedOffset -
-            endKeylineSteps.last().last().unadjustedOffset
+        val startShiftDistance = max(startKeylineSteps.last().first().unadjustedOffset -
+            keylineList.first().unadjustedOffset, beforeContentPadding)
+        val endShiftDistance = max(keylineList.last().unadjustedOffset -
+            endKeylineSteps.last().last().unadjustedOffset, afterContentPadding)
 
         this.defaultKeylines = keylineList
         this.defaultKeylines = keylineList
@@ -146,6 +150,9 @@
             false
         )
         this.availableSpace = availableSpace
+        this.itemSpacing = itemSpacing
+        this.beforeContentPadding = beforeContentPadding
+        this.afterContentPadding = afterContentPadding
         this.itemMainAxisSize = defaultKeylines.firstFocal.size
 
         return this
@@ -236,6 +243,9 @@
 
         if (isValid() != other.isValid()) return false
         if (availableSpace != other.availableSpace) return false
+        if (itemSpacing != other.itemSpacing) return false
+        if (beforeContentPadding != other.beforeContentPadding) return false
+        if (afterContentPadding != other.afterContentPadding) return false
         if (itemMainAxisSize != other.itemMainAxisSize) return false
         if (startShiftDistance != other.startShiftDistance) return false
         if (endShiftDistance != other.endShiftDistance) return false
@@ -253,6 +263,9 @@
 
         var result = isValid().hashCode()
         result = 31 * result + availableSpace.hashCode()
+        result = 31 * result + itemSpacing.hashCode()
+        result = 31 * result + beforeContentPadding.hashCode()
+        result = 31 * result + afterContentPadding.hashCode()
         result = 31 * result + itemMainAxisSize.hashCode()
         result = 31 * result + startShiftDistance.hashCode()
         result = 31 * result + endShiftDistance.hashCode()
@@ -282,12 +295,24 @@
          */
         private fun getStartKeylineSteps(
             defaultKeylines: KeylineList,
-            carouselMainAxisSize: Float
+            carouselMainAxisSize: Float,
+            itemSpacing: Float,
+            beforeContentPadding: Float
         ): List<KeylineList> {
             val steps: MutableList<KeylineList> = mutableListOf()
             steps.add(defaultKeylines)
 
             if (defaultKeylines.isFirstFocalItemAtStartOfContainer()) {
+                if (beforeContentPadding != 0f) {
+                    steps.add(
+                        createShiftedKeylineListForContentPadding(
+                            defaultKeylines,
+                            carouselMainAxisSize,
+                            itemSpacing,
+                            beforeContentPadding
+                        )
+                    )
+                }
                 return steps
             }
 
@@ -303,7 +328,8 @@
                         from = defaultKeylines,
                         srcIndex = 0,
                         dstIndex = 0,
-                        carouselMainAxisSize = carouselMainAxisSize
+                        carouselMainAxisSize = carouselMainAxisSize,
+                        itemSpacing = itemSpacing
                     )
                 )
                 return steps
@@ -326,12 +352,22 @@
                         from = prevStep,
                         srcIndex = defaultKeylines.firstNonAnchorIndex,
                         dstIndex = dstIndex,
-                        carouselMainAxisSize = carouselMainAxisSize
+                        carouselMainAxisSize = carouselMainAxisSize,
+                        itemSpacing = itemSpacing
                     )
                 )
                 i++
             }
 
+            if (beforeContentPadding != 0f) {
+                steps[steps.lastIndex] = createShiftedKeylineListForContentPadding(
+                    steps.last(),
+                    carouselMainAxisSize,
+                    itemSpacing,
+                    beforeContentPadding
+                )
+            }
+
             return steps
         }
 
@@ -353,12 +389,22 @@
          */
         private fun getEndKeylineSteps(
             defaultKeylines: KeylineList,
-            carouselMainAxisSize: Float
+            carouselMainAxisSize: Float,
+            itemSpacing: Float,
+            afterContentPadding: Float
         ): List<KeylineList> {
             val steps: MutableList<KeylineList> = mutableListOf()
             steps.add(defaultKeylines)
 
             if (defaultKeylines.isLastFocalItemAtEndOfContainer(carouselMainAxisSize)) {
+                if (afterContentPadding != 0f) {
+                    steps.add(createShiftedKeylineListForContentPadding(
+                        defaultKeylines,
+                        carouselMainAxisSize,
+                        itemSpacing,
+                        -afterContentPadding
+                    ))
+                }
                 return steps
             }
 
@@ -374,7 +420,8 @@
                         from = defaultKeylines,
                         srcIndex = 0,
                         dstIndex = 0,
-                        carouselMainAxisSize = carouselMainAxisSize
+                        carouselMainAxisSize = carouselMainAxisSize,
+                        itemSpacing = itemSpacing
                     )
                 )
                 return steps
@@ -397,16 +444,61 @@
                     from = prevStep,
                     srcIndex = defaultKeylines.lastNonAnchorIndex,
                     dstIndex = dstIndex,
-                    carouselMainAxisSize = carouselMainAxisSize
+                    carouselMainAxisSize = carouselMainAxisSize,
+                    itemSpacing = itemSpacing
                 )
                 steps.add(keylines)
                 i++
             }
 
+            if (afterContentPadding != 0f) {
+                steps[steps.lastIndex] = createShiftedKeylineListForContentPadding(
+                    steps.last(),
+                    carouselMainAxisSize,
+                    itemSpacing,
+                    -afterContentPadding
+                )
+            }
+
             return steps
         }
 
         /**
+         * Returns a new [KeylineList] identical to [from] but with each keyline's offset shifted
+         * by [contentPadding].
+         */
+        private fun createShiftedKeylineListForContentPadding(
+            from: KeylineList,
+            carouselMainAxisSize: Float,
+            itemSpacing: Float,
+            contentPadding: Float
+        ): KeylineList {
+            val numberOfNonAnchorKeylines = from.fastFilter { !it.isAnchor }.count()
+            val sizeReduction = contentPadding / numberOfNonAnchorKeylines
+            // Let keylineListOf create a new keyline list with offsets adjusted for each item's
+            // reduction in size
+            val newKeylines = keylineListOf(
+                carouselMainAxisSize = carouselMainAxisSize,
+                itemSpacing = itemSpacing,
+                pivotIndex = from.pivotIndex,
+                pivotOffset = from.pivot.offset + contentPadding - (sizeReduction / 2f)
+            ) {
+                from.fastForEach { k -> add(k.size - abs(sizeReduction), k.isAnchor) }
+            }
+
+            // Then reset each item's unadjusted offset back to their original value from the
+            // incoming keyline list. This is necessary because Pager will still be laying out items
+            // end-to-end with the original page size and not the new reduced size.
+            return KeylineList(
+                newKeylines.fastMapIndexed { i, k ->
+                    k.copy(
+                        unadjustedOffset = from[i].unadjustedOffset
+                    )
+                }
+            )
+        }
+
+        /**
          * Returns a new [KeylineList] where the keyline at [srcIndex] is moved to [dstIndex] and
          * with updated pivot and offsets that reflect any change in focal shift.
          */
@@ -414,14 +506,15 @@
             from: KeylineList,
             srcIndex: Int,
             dstIndex: Int,
-            carouselMainAxisSize: Float
+            carouselMainAxisSize: Float,
+            itemSpacing: Float
         ): KeylineList {
             // -1 if the pivot is shifting left/top, 1 if shifting right/bottom
             val pivotDir = if (srcIndex > dstIndex) 1 else -1
-            val pivotDelta = (from[srcIndex].size - from[srcIndex].cutoff) * pivotDir
+            val pivotDelta = (from[srcIndex].size - from[srcIndex].cutoff + itemSpacing) * pivotDir
             val newPivotIndex = from.pivotIndex + pivotDir
             val newPivotOffset = from.pivot.offset + pivotDelta
-            return keylineListOf(carouselMainAxisSize, newPivotIndex, newPivotOffset) {
+            return keylineListOf(carouselMainAxisSize, itemSpacing, newPivotIndex, newPivotOffset) {
                 from.toMutableList()
                     .move(srcIndex, dstIndex)
                     .fastForEach { k -> add(k.size, k.isAnchor) }
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/NavigationDrawer.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/NavigationDrawer.desktop.kt
new file mode 100644
index 0000000..94baaa8
--- /dev/null
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/NavigationDrawer.desktop.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+
+/**
+ * A predictive back handler that does nothing when running in a desktop context, since predictive
+ * back is only supported on Android.
+ *
+ * @param drawerState state of the drawer
+ * @param content content of the rest of the UI
+ */
+@Composable
+internal actual fun DrawerPredictiveBackHandler(
+    drawerState: DrawerState,
+    content: @Composable (DrawerPredictiveBackState) -> Unit
+) {
+    content(remember { DrawerPredictiveBackState() })
+}
diff --git a/compose/runtime/runtime-livedata/src/androidTest/java/androidx/compose/runtime/livedata/LiveDataAdapterTest.kt b/compose/runtime/runtime-livedata/src/androidTest/java/androidx/compose/runtime/livedata/LiveDataAdapterTest.kt
index 9733e52..756ccab 100644
--- a/compose/runtime/runtime-livedata/src/androidTest/java/androidx/compose/runtime/livedata/LiveDataAdapterTest.kt
+++ b/compose/runtime/runtime-livedata/src/androidTest/java/androidx/compose/runtime/livedata/LiveDataAdapterTest.kt
@@ -20,10 +20,10 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index fe7f66b..0163760 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -723,7 +723,7 @@
 
 package androidx.compose.runtime.internal {
 
-  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambda extends kotlin.jvm.functions.Function2<androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function10<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function11<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function13<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function14<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function15<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function16<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function17<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function18<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function19<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function20<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function21<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function3<java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function4<java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function5<java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function6<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function7<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function8<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function9<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> {
+  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambda extends kotlin.jvm.functions.Function2<androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function10<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function11<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function13<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function14<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function15<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function16<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function17<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function18<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function19<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function20<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function21<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function3<java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function4<java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function5<java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function6<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function7<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function8<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function9<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> {
   }
 
   public final class ComposableLambdaKt {
@@ -732,7 +732,7 @@
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda rememberComposableLambda(int key, boolean tracked, Object block);
   }
 
-  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
+  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object?> {
   }
 
   public final class ComposableLambdaN_jvmKt {
@@ -864,12 +864,14 @@
     property public abstract boolean readOnly;
     property public abstract androidx.compose.runtime.snapshots.Snapshot root;
     field public static final androidx.compose.runtime.snapshots.Snapshot.Companion Companion;
+    field public static final int PreexistingSnapshotId = 1; // 0x1
   }
 
   public static final class Snapshot.Companion {
     method public androidx.compose.runtime.snapshots.Snapshot getCurrent();
     method public inline <T> T global(kotlin.jvm.functions.Function0<? extends T> block);
     method public boolean isApplyObserverNotificationPending();
+    method public boolean isInSnapshot();
     method public void notifyObjectsInitialized();
     method public <T> T observe(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver, kotlin.jvm.functions.Function0<? extends T> block);
     method @SuppressCompatibility @androidx.compose.runtime.InternalComposeApi public int openSnapshotCount();
@@ -882,6 +884,7 @@
     method public inline <T> T withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
     property public final boolean isApplyObserverNotificationPending;
+    property public final boolean isInSnapshot;
   }
 
   public final class SnapshotApplyConflictException extends java.lang.Exception {
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 63348ec..5240627 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -758,7 +758,7 @@
 
 package androidx.compose.runtime.internal {
 
-  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambda extends kotlin.jvm.functions.Function2<androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function10<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function11<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function13<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function14<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function15<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function16<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function17<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function18<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function19<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function20<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function21<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function3<java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function4<java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function5<java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function6<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function7<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function8<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> kotlin.jvm.functions.Function9<java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object> {
+  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambda extends kotlin.jvm.functions.Function2<androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function10<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function11<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function13<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function14<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function15<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function16<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function17<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function18<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function19<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function20<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function21<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function3<java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function4<java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function5<java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function6<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function7<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function8<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> kotlin.jvm.functions.Function9<java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,java.lang.Object?,androidx.compose.runtime.Composer,java.lang.Integer,java.lang.Object?> {
   }
 
   public final class ComposableLambdaKt {
@@ -767,7 +767,7 @@
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ComposeCompilerApi public static androidx.compose.runtime.internal.ComposableLambda rememberComposableLambda(int key, boolean tracked, Object block);
   }
 
-  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object> {
+  @androidx.compose.runtime.ComposeCompilerApi @androidx.compose.runtime.Stable public interface ComposableLambdaN extends kotlin.jvm.functions.FunctionN<java.lang.Object?> {
   }
 
   public final class ComposableLambdaN_jvmKt {
@@ -901,6 +901,7 @@
     property public abstract boolean readOnly;
     property public abstract androidx.compose.runtime.snapshots.Snapshot root;
     field public static final androidx.compose.runtime.snapshots.Snapshot.Companion Companion;
+    field public static final int PreexistingSnapshotId = 1; // 0x1
   }
 
   public static final class Snapshot.Companion {
@@ -908,6 +909,7 @@
     method public androidx.compose.runtime.snapshots.Snapshot getCurrent();
     method public inline <T> T global(kotlin.jvm.functions.Function0<? extends T> block);
     method public boolean isApplyObserverNotificationPending();
+    method public boolean isInSnapshot();
     method @kotlin.PublishedApi internal androidx.compose.runtime.snapshots.Snapshot makeCurrentNonObservable(androidx.compose.runtime.snapshots.Snapshot? previous);
     method public void notifyObjectsInitialized();
     method public <T> T observe(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver, kotlin.jvm.functions.Function0<? extends T> block);
@@ -925,6 +927,7 @@
     property public final androidx.compose.runtime.snapshots.Snapshot current;
     property @kotlin.PublishedApi internal final androidx.compose.runtime.snapshots.Snapshot? currentThreadSnapshot;
     property public final boolean isApplyObserverNotificationPending;
+    property public final boolean isInSnapshot;
   }
 
   public final class SnapshotApplyConflictException extends java.lang.Exception {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
index 1718d92..df5f37a 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
@@ -48,10 +48,14 @@
     private var awaiters = mutableListOf<FrameAwaiter<*>>()
     private var spareList = mutableListOf<FrameAwaiter<*>>()
 
+    // Uses AtomicInt to avoid adding AtomicBoolean to the Expect/Actual requirements of the
+    // runtime.
+    private val hasAwaitersUnlocked = AtomicInt(0)
+
     /**
      * `true` if there are any callers of [withFrameNanos] awaiting to run for a pending frame.
      */
-    val hasAwaiters: Boolean get() = synchronized(lock) { awaiters.isNotEmpty() }
+    val hasAwaiters: Boolean get() = hasAwaitersUnlocked.get() != 0
 
     /**
      * Send a frame for time [timeNanos] to all current callers of [withFrameNanos].
@@ -66,6 +70,7 @@
             val toResume = awaiters
             awaiters = spareList
             spareList = toResume
+            hasAwaitersUnlocked.set(0)
 
             for (i in 0 until toResume.size) {
                 toResume[i].resume(timeNanos)
@@ -87,12 +92,14 @@
             awaiter = FrameAwaiter(onFrame, co)
             val hadAwaiters = awaiters.isNotEmpty()
             awaiters.add(awaiter)
+            if (!hadAwaiters) hasAwaitersUnlocked.set(1)
             !hadAwaiters
         }
 
         co.invokeOnCancellation {
             synchronized(lock) {
                 awaiters.remove(awaiter)
+                if (awaiters.isEmpty()) hasAwaitersUnlocked.set(0)
             }
         }
 
@@ -116,6 +123,7 @@
                 awaiter.continuation.resumeWithException(cause)
             }
             awaiters.clear()
+            hasAwaitersUnlocked.set(0)
         }
     }
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index 02f6601..d2c2079 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
      * IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
      * `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
      */
-    const val version: Int = 12400
+    const val version: Int = 12500
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index d57031f..d21e787 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -2219,7 +2219,9 @@
         val providers: PersistentCompositionLocalMap
         val invalid: Boolean
         if (inserting) {
-            providers = parentScope.putValue(local, state)
+            providers = if (value.canOverride || !parentScope.contains(local)) {
+                parentScope.putValue(local, state)
+            } else { parentScope }
             invalid = false
             writerHasAProvider = true
         } else {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotDoubleState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotDoubleState.kt
index cdea706..3354ba0 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotDoubleState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotDoubleState.kt
@@ -130,7 +130,13 @@
     value: Double
 ) : StateObjectImpl(), MutableDoubleState, SnapshotMutableState<Double> {
 
-    private var next = DoubleStateStateRecord(value)
+    private var next = DoubleStateStateRecord(value).also {
+        if (Snapshot.isInSnapshot) {
+            it.next = DoubleStateStateRecord(value).also { next ->
+                next.snapshotId = Snapshot.PreexistingSnapshotId
+            }
+        }
+    }
 
     override val firstStateRecord: StateRecord
         get() = next
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt
index e5212f1..ff9a419 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt
@@ -126,7 +126,13 @@
     value: Float
 ) : StateObjectImpl(), MutableFloatState, SnapshotMutableState<Float> {
 
-    private var next = FloatStateStateRecord(value)
+    private var next = FloatStateStateRecord(value).also {
+        if (Snapshot.isInSnapshot) {
+            it.next = FloatStateStateRecord(value).also { next ->
+                next.snapshotId = Snapshot.PreexistingSnapshotId
+            }
+        }
+    }
 
     override val firstStateRecord: StateRecord
         get() = next
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotIntState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotIntState.kt
index 75a7db0..b01dc0d 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotIntState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotIntState.kt
@@ -129,7 +129,13 @@
     value: Int
 ) : StateObjectImpl(), MutableIntState, SnapshotMutableState<Int> {
 
-    private var next = IntStateStateRecord(value)
+    private var next = IntStateStateRecord(value).also {
+        if (Snapshot.isInSnapshot) {
+            it.next = IntStateStateRecord(value).also { next ->
+                next.snapshotId = Snapshot.PreexistingSnapshotId
+            }
+        }
+    }
 
     override val firstStateRecord: StateRecord
         get() = next
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotLongState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotLongState.kt
index f3b3678..f1270a2 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotLongState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotLongState.kt
@@ -126,7 +126,13 @@
     value: Long
 ) : StateObjectImpl(), MutableLongState, SnapshotMutableState<Long> {
 
-    private var next = LongStateStateRecord(value)
+    private var next = LongStateStateRecord(value).also {
+        if (Snapshot.isInSnapshot) {
+            it.next = LongStateStateRecord(value).also { next ->
+                next.snapshotId = Snapshot.PreexistingSnapshotId
+            }
+        }
+    }
 
     override val firstStateRecord: StateRecord
         get() = next
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
index 0bd7bd7..9fab395 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
@@ -139,7 +139,13 @@
             }
         }
 
-    private var next: StateStateRecord<T> = StateStateRecord(value)
+    private var next: StateStateRecord<T> = StateStateRecord(value).also {
+        if (Snapshot.isInSnapshot) {
+            it.next = StateStateRecord(value).also { next ->
+                next.snapshotId = Snapshot.PreexistingSnapshotId
+            }
+        }
+    }
 
     override val firstStateRecord: StateRecord
         get() = next
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 23e538b..5383c37 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -298,6 +298,11 @@
         val current get() = currentSnapshot()
 
         /**
+         * Return `true` if the thread is currently in the context of a snapshot.
+         */
+        val isInSnapshot: Boolean get() = threadSnapshot.get() != null
+
+        /**
          * Returns whether any threads are currently in the process of notifying observers about
          * changes to the global snapshot.
          */
@@ -305,6 +310,14 @@
             get() = pendingApplyObserverCount.get() > 0
 
         /**
+         * All new state objects initial state records should be [PreexistingSnapshotId] which then
+         * allows snapshots outside the creating snapshot to access the object with its initial
+         * state.
+         */
+        @Suppress("ConstPropertyName")
+        const val PreexistingSnapshotId = 1
+
+        /**
          * Take a snapshot of the current value of all state objects. The values are preserved until
          * [Snapshot.dispose] is called on the result.
          *
@@ -1008,6 +1021,15 @@
             // in a nested snapshot that was committed then changed.
             val current = readable(first, snapshotId, invalidSnapshots) ?: return@forEach
             val previous = readable(first, id, start) ?: return@forEach
+            if (previous.snapshotId == PreexistingSnapshotId) {
+                // A previous record might not be found if the state object was created in a
+                // nested snapshot that didn't have any other modifications. The `apply()` for
+                // a nested snapshot considers such snapshots no-op snapshots and just closes them
+                // which allows this object's previous record to be missing or be the record created
+                // during initial construction. In these cases taking applied is the right choice
+                // this indicates there was no conflicting writes.
+                return@forEach
+            }
             if (current != previous) {
                 val applied = readable(first, id, this.invalid) ?: readError()
                 val merged = optimisticMerges?.get(current) ?: run {
@@ -1824,8 +1846,9 @@
  */
 private var openSnapshots = SnapshotIdSet.EMPTY
 
-/** The first snapshot created must be at least on more than the INVALID_SNAPSHOT */
-private var nextSnapshotId = INVALID_SNAPSHOT + 1
+/** The first snapshot created must be at least on more than the
+ * [Snapshot.PreexistingSnapshotId] */
+private var nextSnapshotId = Snapshot.PreexistingSnapshotId + 1
 
 /**
  * A tracking table for pinned snapshots. A pinned snapshot is the lowest snapshot id that the
@@ -2169,9 +2192,16 @@
 
     // Otherwise, make a copy of the readable data and mark it as born in this snapshot, making it
     // writable.
-    val newData = readData.newWritableRecord(state, snapshot)
+    @Suppress("UNCHECKED_CAST")
+    val newData = sync {
+        // Verify that some other thread didn't already create this.
+        val newReadData = readable(state.firstStateRecord, id, snapshot.invalid) ?: readError()
+        if (newReadData.snapshotId == id)
+            newReadData
+        else newReadData.newWritableRecordLocked(state, snapshot)
+    } as T
 
-    snapshot.recordModified(state)
+    if (readData.snapshotId != Snapshot.PreexistingSnapshotId) snapshot.recordModified(state)
 
     return newData
 }
@@ -2192,7 +2222,7 @@
     val newData = sync { newOverwritableRecordLocked(state) }
     newData.snapshotId = id
 
-    snapshot.recordModified(state)
+    if (candidate.snapshotId != Snapshot.PreexistingSnapshotId) snapshot.recordModified(state)
 
     return newData
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
index 22fda4b..c661d37 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt
@@ -33,7 +33,15 @@
 @Stable
 class SnapshotStateList<T> : StateObject, MutableList<T>, RandomAccess {
     override var firstStateRecord: StateRecord =
-        StateListStateRecord<T>(persistentListOf())
+        persistentListOf<T>().let { list ->
+            StateListStateRecord(list).also {
+                if (Snapshot.isInSnapshot) {
+                    it.next = StateListStateRecord(list).also { next ->
+                        next.snapshotId = Snapshot.PreexistingSnapshotId
+                    }
+                }
+            }
+        }
         private set
 
     override fun prependStateRecord(value: StateRecord) {
@@ -213,7 +221,7 @@
                     oldList = current.list
                 }
                 val newList = block(oldList!!)
-                if (newList == oldList) {
+                if (newList === oldList) {
                     result = false
                     break
                 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
index 0f015a4..42701db 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
@@ -33,7 +33,15 @@
 @Stable
 class SnapshotStateMap<K, V> : StateObject, MutableMap<K, V> {
     override var firstStateRecord: StateRecord =
-        StateMapStateRecord<K, V>(persistentHashMapOf())
+        persistentHashMapOf<K, V>().let { map ->
+            StateMapStateRecord(map).also {
+                if (Snapshot.isInSnapshot) {
+                    it.next = StateMapStateRecord(map).also { next ->
+                        next.snapshotId = Snapshot.PreexistingSnapshotId
+                    }
+                }
+            }
+        }
         private set
 
     override fun prependStateRecord(value: StateRecord) {
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
index 4144ccd..ddfcc02 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionLocalTests.kt
@@ -702,6 +702,18 @@
 
         revalidate()
     }
+
+    @Test // Regression test for: b/330036209
+    fun testSingleProvideDefaultValue() = compositionTest {
+        val local = compositionLocalOf { 0 }
+        compose {
+            CompositionLocalProvider(local provides 1) {
+                CompositionLocalProvider(local providesDefault 2) {
+                    assertEquals(1, local.current)
+                }
+            }
+        }
+    }
 }
 
 val cacheLocal = staticCompositionLocalOf { "Unset" }
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
index 50fa39f..a6e59b3 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
@@ -23,16 +23,19 @@
 import androidx.compose.runtime.mock.expectNoChanges
 import androidx.compose.runtime.snapshots.Snapshot
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.test.Ignore
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.newSingleThreadContext
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -437,6 +440,41 @@
         assertEquals<List<Set<Any>>>(listOf(setOf(countFromEffect)), applications)
     }
 
+    @Ignore // b/329682091
+    @OptIn(DelicateCoroutinesApi::class)
+    @Test // b/329011032
+    fun validatePotentialDeadlock() = compositionTest {
+        var state by mutableIntStateOf(0)
+        compose {
+            repeat(1000) {
+                Text("This is some text: $state")
+            }
+            LaunchedEffect(Unit) {
+                newSingleThreadContext("other thread").use {
+                    while (true) {
+                        withContext(it) {
+                            state++
+                            Snapshot.registerGlobalWriteObserver { }.dispose()
+                        }
+                    }
+                }
+            }
+            LaunchedEffect(Unit) {
+                while (true) {
+                    withFrameNanos {
+                        state++
+                        Snapshot.sendApplyNotifications()
+                    }
+                }
+            }
+        }
+
+        repeat(10) {
+            state++
+            advance(ignorePendingWork = true)
+        }
+    }
+
     @Test
     fun pausingTheFrameClockStopShouldBlockWithFrameNanos() {
         val dispatcher = StandardTestDispatcher()
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateListTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateListTests.kt
index 9d6add5..c662be9 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateListTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateListTests.kt
@@ -587,7 +587,7 @@
 
             repeat(100) { index ->
                 repeat(10) {
-                    assertTrue(list.contains(index * 100 + it))
+                    assertTrue(list.contains(index * 100 + it), "Missing ${index * 100 + it}")
                 }
             }
         }
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
index 340b2b7..687e8d3b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
@@ -22,6 +22,12 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableDoubleStateOf
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateMapOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.referentialEqualityPolicy
@@ -37,6 +43,7 @@
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
 import kotlin.test.assertNotSame
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
@@ -1265,6 +1272,47 @@
         assertEquals(1, current.writeCount)
     }
 
+    @Test
+    fun testSnapshotStateIsBornAccessible() {
+        fun <T, V> test(create: () -> T, read: (T) -> V, update: (T) -> V) {
+            val snapshot = takeMutableSnapshot()
+            val created: Any? = null
+            val modified = observeChanges(snapshot) {
+                val (state, initial) = snapshot.enter {
+                    val state = create()
+                    state to read(state)
+                }
+
+                // Ensure the value is accessible and has its initial state
+                assertEquals(initial, read(state))
+                val newValue = snapshot.enter {
+                    update(state)
+                }
+
+                // Ensure the test actually modified it
+                assertNotEquals(initial, newValue)
+
+                // Ensure the value still has its initial state
+                assertEquals(initial, read(state))
+                snapshot.apply().check()
+
+                // Ensure the value now has the modified state
+                assertEquals(newValue, read(state))
+            }
+
+            // The object is not considered modified
+            assertFalse(created in modified)
+        }
+
+        test({ mutableStateOf("A") }, { it.value }) { it.value = "B"; "B" }
+        test({ mutableIntStateOf(1) }, { it.value }, { it.value = 2; 2 })
+        test({ mutableLongStateOf(1L) }, { it.value }, { it.value = 2L; 2L })
+        test({ mutableFloatStateOf(1f) }, { it.value }, { it.value = 2f; 2f })
+        test({ mutableDoubleStateOf(1.0) }, { it.value }, { it.value = 2.0; 2.0 })
+        test({ mutableStateListOf<Int>() }, { it.isEmpty() }, { it.add(1); it.isEmpty() })
+        test({ mutableStateMapOf<Int, Int>() }, { it.isEmpty() }, { it[23] = 42; it.isEmpty() })
+    }
+
     private fun usedRecords(state: StateObject): Int {
         var used = 0
         var current: StateRecord? = state.firstStateRecord
@@ -1305,6 +1353,20 @@
     return changes
 }
 
+internal fun observeChanges(snapshot: Snapshot, block: () -> Unit): Set<Any> {
+    var changes = setOf<Any>()
+    val removeObserver = Snapshot.registerApplyObserver { states, changedSnapshot ->
+        if (changedSnapshot == snapshot) changes = states
+    }
+    try {
+        block()
+        Snapshot.sendApplyNotifications()
+    } finally {
+        removeObserver.dispose()
+    }
+    return changes
+}
+
 internal fun readsOf(block: () -> Unit): Int {
     var reads = 0
     val snapshot = takeSnapshot(readObserver = { reads++ })
diff --git a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt
index 8c2e559..b2f3744 100644
--- a/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt
+++ b/compose/test-utils/src/commonMain/kotlin/androidx/compose/testutils/Expect.kt
@@ -26,7 +26,7 @@
  * [expectedMessage] is a regex with just the option [DOT_MATCHES_ALL] enabled.
  */
 fun expectAssertionError(
-    expectError: Boolean,
+    expectError: Boolean = true,
     expectedMessage: String = ".*",
     block: () -> Unit
 ) {
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index bd8f832..f65a14b 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -733,11 +733,11 @@
   public final class PathHitTester {
     ctor public PathHitTester();
     method public operator boolean contains(long position);
-    method public void updatePath(androidx.compose.ui.graphics.Path path, optional float tolerance);
+    method public void updatePath(androidx.compose.ui.graphics.Path path, optional @FloatRange(from=0.0) float tolerance);
   }
 
   public final class PathHitTesterKt {
-    method public static androidx.compose.ui.graphics.PathHitTester PathHitTester(androidx.compose.ui.graphics.Path path, optional float tolerance);
+    method public static androidx.compose.ui.graphics.PathHitTester PathHitTester(androidx.compose.ui.graphics.Path path, optional @FloatRange(from=0.0) float tolerance);
   }
 
   public interface PathIterator extends java.util.Iterator<androidx.compose.ui.graphics.PathSegment> kotlin.jvm.internal.markers.KMappedMarker {
diff --git a/compose/ui/ui-graphics/api/res-current.txt b/compose/ui/ui-graphics/api/res-current.txt
index e69de29..4553236 100644
--- a/compose/ui/ui-graphics/api/res-current.txt
+++ b/compose/ui/ui-graphics/api/res-current.txt
@@ -0,0 +1 @@
+id hide_graphics_layer_in_inspector_tag
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index fb5ad0c..6d5ec8b 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -804,11 +804,11 @@
   public final class PathHitTester {
     ctor public PathHitTester();
     method public operator boolean contains(long position);
-    method public void updatePath(androidx.compose.ui.graphics.Path path, optional float tolerance);
+    method public void updatePath(androidx.compose.ui.graphics.Path path, optional @FloatRange(from=0.0) float tolerance);
   }
 
   public final class PathHitTesterKt {
-    method public static androidx.compose.ui.graphics.PathHitTester PathHitTester(androidx.compose.ui.graphics.Path path, optional float tolerance);
+    method public static androidx.compose.ui.graphics.PathHitTester PathHitTester(androidx.compose.ui.graphics.Path path, optional @FloatRange(from=0.0) float tolerance);
   }
 
   public interface PathIterator extends java.util.Iterator<androidx.compose.ui.graphics.PathSegment> kotlin.jvm.internal.markers.KMappedMarker {
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
index 8b37777..0b88a20 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.testutils.captureToImage
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.RoundRect
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.center
 import androidx.compose.ui.graphics.BlendMode
@@ -35,6 +36,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter.Companion.tint
 import androidx.compose.ui.graphics.GraphicsContext
+import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.PixelMap
 import androidx.compose.ui.graphics.TestActivity
@@ -1126,6 +1128,28 @@
         )
     }
 
+    @Test
+    fun setOutlineExtensionAppliesValuesCorrectly() {
+        graphicsLayerTest(
+            block = { graphicsContext ->
+                val layer = graphicsContext.createGraphicsLayer()
+
+                val rectangle = Outline.Rectangle(Rect(1f, 2f, 3f, 4f))
+                layer.setOutline(rectangle)
+                assertEquals(rectangle, layer.outline)
+
+                val rounded = Outline.Rounded(RoundRect(10f, 20f, 30f, 40f, 5f, 5f))
+                layer.setOutline(rounded)
+                assertEquals(rounded, layer.outline)
+
+                val path = Path().also { it.addOval(Rect(1f, 2f, 3f, 4f)) }
+                val generic = Outline.Generic(path)
+                layer.setOutline(generic)
+                assertEquals(generic, layer.outline)
+            }
+        )
+    }
+
     private fun PixelMap.verifyQuadrants(
         topLeft: Color,
         topRight: Color,
@@ -1144,7 +1168,7 @@
 
     private fun graphicsLayerTest(
         block: DrawScope.(GraphicsContext) -> Unit,
-        verify: (PixelMap) -> Unit,
+        verify: ((PixelMap) -> Unit)? = null,
         entireScene: Boolean = false
     ) {
         var scenario: ActivityScenario<TestActivity>? = null
@@ -1184,12 +1208,14 @@
                 }
             Assert.assertTrue(resumed.await(3000, TimeUnit.MILLISECONDS))
 
-            val target = if (entireScene) {
-                container!!
-            } else {
-                contentView!!
+            if (verify != null) {
+                val target = if (entireScene) {
+                    container!!
+                } else {
+                    contentView!!
+                }
+                verify(target.captureToImage().toPixelMap())
             }
-            verify(target.captureToImage().toPixelMap())
         } finally {
             scenario?.moveToState(Lifecycle.State.DESTROYED)
         }
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
index d2658da..bf70701 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
@@ -193,7 +193,7 @@
             renderNode.setTranslationX(value)
         }
 
-    override var translationY: Float = 1f
+    override var translationY: Float = 0f
         set(value) {
             field = value
             renderNode.setTranslationY(value)
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt
index 23185dd..3aed02d 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/view/ViewLayerContainer.android.kt
@@ -52,7 +52,7 @@
         clipToPadding = false
 
         // Hide this view and its children in tools:
-        setTag(R.id.hide_in_inspector_tag, true)
+        setTag(R.id.hide_graphics_layer_in_inspector_tag, true)
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml b/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml
index 989568a..453cbdb 100644
--- a/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml
+++ b/compose/ui/ui-graphics/src/androidMain/res/values/ids.xml
@@ -16,5 +16,5 @@
   -->
 
 <resources>
-    <item name="hide_in_inspector_tag" type="id" />
+    <item name="hide_graphics_layer_in_inspector_tag" type="id" />
 </resources>
diff --git a/compose/ui/ui-graphics/src/androidMain/res/values/public.xml b/compose/ui/ui-graphics/src/androidMain/res/values/public.xml
new file mode 100644
index 0000000..1a24359
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidMain/res/values/public.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <public name="hide_graphics_layer_in_inspector_tag" type="id"/>
+</resources>
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 29a7c76..873cedd 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
@@ -84,10 +84,10 @@
  * | A         | Alpha       | 10 bits | `[0..1023]`           |
  * |           | Color space | 6 bits  | `[0..63]`             |
  * | [SRGB][ColorSpaces.Srgb] color space                      |
+ * | A         | Alpha       | 8 bits  | `[0..255]`            |
  * | R         | Red         | 8 bits  | `[0..255]`            |
  * | G         | Green       | 8 bits  | `[0..255]`            |
  * | B         | Blue        | 8 bits  | `[0..255]`            |
- * | A         | Alpha       | 8 bits  | `[0..255]`            |
  * | X         | Unused      | 32 bits | `[0]`                 |
  * | [XYZ][ColorSpace.Model.Xyz] color model                   |
  * | X         | X           | 16 bits | `[-65504.0, 65504.0]` |
@@ -102,7 +102,7 @@
  * | A         | Alpha       | 10 bits | `[0..1023]`           |
  * |           | Color space | 6 bits  | `[0..63]`             |
  * ```
- * The components in this table are listed in encoding order (see below),
+ * The components in this table are listed in encoding order,
  * which is why color longs in the RGB model are called RGBA colors (even if
  * this doesn't quite hold for the special case of sRGB colors).
  *
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt
index a380113..96df70f 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathHitTester.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.graphics
 
+import androidx.annotation.FloatRange
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 
@@ -36,13 +37,16 @@
  * instance if the path is defined in pixels, 0.5 (half a pixel) or 1.0 (a pixel) are
  * appropriate tolerances. If the path is normalized and defined in the domain 0..1,
  * the caller should choose a more appropriate tolerance close to or equal to one
- * "query unit".
+ * "query unit". The tolerance must be >= 0.
  *
  * @param path The [Path] to run queries against.
  * @param tolerance When [path] contains conic curves, defines the maximum distance between
  *        the original conic curve and its quadratic approximations. Set to 0.5 by default.
  */
-fun PathHitTester(path: Path, tolerance: Float = 0.5f) = PathHitTester().apply {
+fun PathHitTester(
+    path: Path,
+    @FloatRange(from = 0.0) tolerance: Float = 0.5f
+) = PathHitTester().apply {
     updatePath(path, tolerance)
 }
 
@@ -79,13 +83,13 @@
      * For instance if the path is defined in pixels, 0.5 (half a pixel) or 1.0 (a pixel)
      * are appropriate tolerances. If the path is normalized and defined in the domain 0..1,
      * the caller should choose a more appropriate tolerance close to or equal to one
-     * "query unit".
+     * "query unit". The tolerance must be >= 0.
      *
      * @param path The [Path] to run queries against.
      * @param tolerance When [path] contains conic curves, defines the maximum distance between
      *        the original conic curve and its quadratic approximations. Set to 0.5 by default.
      */
-    fun updatePath(path: Path, tolerance: Float = 0.5f) {
+    fun updatePath(path: Path, @FloatRange(from = 0.0) tolerance: Float = 0.5f) {
         this.path = path
         this.tolerance = tolerance
         bounds = path.getBounds()
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSegment.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSegment.kt
index 31ce2ba..2dc028f 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSegment.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathSegment.kt
@@ -21,7 +21,7 @@
  * a fully formed [Path] object.
  *
  * A segment is identified by a [type][PathSegment.Type] which in turns defines how many
- * [points] are available (from 0 to 3 points, each point is represented by 2 floats) and
+ * [points] are available (from 0 to 4 points, each point is represented by 2 floats) and
  * whether the [weight] is meaningful. Please refer to the documentation of each
  * [type][PathSegment.Type] for more information.
  *
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.kt
index 6cb880e..633aa51 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayer.kt
@@ -412,7 +412,7 @@
 fun GraphicsLayer.setOutline(outline: Outline) {
     when (outline) {
         is Outline.Rectangle -> setRectOutline(
-            IntOffset(outline.rect.top.fastRoundToInt(), outline.rect.left.fastRoundToInt()),
+            IntOffset(outline.rect.left.fastRoundToInt(), outline.rect.top.fastRoundToInt()),
             IntSize(outline.rect.width.fastRoundToInt(), outline.rect.height.fastRoundToInt())
         )
         is Outline.Generic -> setPathOutline(outline.path)
@@ -426,7 +426,7 @@
             } else {
                 val rr = outline.roundRect
                 setRoundRectOutline(
-                    IntOffset(rr.top.fastRoundToInt(), rr.left.fastRoundToInt()),
+                    IntOffset(rr.left.fastRoundToInt(), rr.top.fastRoundToInt()),
                     IntSize(rr.width.fastRoundToInt(), rr.height.fastRoundToInt()),
                     rr.bottomLeftCornerRadius.x
                 )
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
index 0140b6d..a1a2615 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
@@ -21,7 +21,7 @@
 import android.view.ViewGroup
 import androidx.collection.LongList
 import androidx.collection.mutableLongListOf
-import androidx.compose.ui.graphics.R
+import androidx.compose.ui.R
 import androidx.compose.ui.inspection.framework.ancestors
 import androidx.compose.ui.inspection.framework.getChildren
 import androidx.compose.ui.inspection.framework.isAndroidComposeView
@@ -113,10 +113,14 @@
     private fun createViewsToSkip(viewGroup: ViewGroup): LongList {
         val result = mutableLongListOf()
         viewGroup.getChildren().forEach { view ->
-            if (view.getTag(R.id.hide_in_inspector_tag) != null) {
+            if (view.hasHideFromInspectionTag()) {
                 result.add(view.uniqueDrawingId)
             }
         }
         return result
     }
+
+    private fun View.hasHideFromInspectionTag(): Boolean =
+        getTag(R.id.hide_in_inspector_tag) != null ||
+            getTag(androidx.compose.ui.graphics.R.id.hide_graphics_layer_in_inspector_tag) != null
 }
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AssertExistsTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AssertExistsTest.kt
index 4b564b4..b18f876 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AssertExistsTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/AssertExistsTest.kt
@@ -66,7 +66,7 @@
         rule.onNodeWithText("Hello")
             .assertExists()
 
-        expectAssertionError(true) {
+        expectAssertionError {
             rule.onNodeWithText("Hello")
                 .assertDoesNotExist()
         }
@@ -83,12 +83,12 @@
         cachedResult
             .assertDoesNotExist()
 
-        expectAssertionError(true) {
+        expectAssertionError {
             rule.onNodeWithText("Hello")
                 .assertExists()
         }
 
-        expectAssertionError(true) {
+        expectAssertionError {
             cachedResult.assertExists()
         }
 
@@ -99,7 +99,7 @@
         rule.onNodeWithText("Hello")
             .assertExists()
 
-        expectAssertionError(true) {
+        expectAssertionError {
             rule.onNodeWithText("Hello")
                 .assertDoesNotExist()
         }
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 0cad628..af80153 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -117,6 +117,10 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTextApi {
   }
 
+  public final class Html_androidKt {
+    method public static androidx.compose.ui.text.AnnotatedString parseAsHtml(String);
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This is internal API that may change frequently and without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface InternalTextApi {
   }
 
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 8fd548b..675ad44 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -117,6 +117,10 @@
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTextApi {
   }
 
+  public final class Html_androidKt {
+    method public static androidx.compose.ui.text.AnnotatedString parseAsHtml(String);
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.ERROR, message="This is internal API that may change frequently and without warning.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface InternalTextApi {
   }
 
diff --git a/compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/AnnotatedStringFromHtmlSamples.kt b/compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/AnnotatedStringFromHtmlSamples.kt
new file mode 100644
index 0000000..c068272
--- /dev/null
+++ b/compose/ui/ui-text/samples/src/main/java/androidx/compose/ui/text/samples/AnnotatedStringFromHtmlSamples.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.parseAsHtml
+
+@Composable
+@Sampled
+fun AnnotatedStringFromHtml() {
+    // First, download a string as a plain text using one of the resources' methods. At this stage
+    // you will be handling plurals and formatted strings in needed. Moreover, the string will be
+    // resolved with respect to the current locale and available translations.
+    val string = stringResource(id = R.string.example)
+
+    // Next, convert a string marked with HTML tags into AnnotatedString to be displayed by Text
+    val styledAnnotatedString = string.parseAsHtml()
+
+    BasicText(styledAnnotatedString)
+}
diff --git a/compose/ui/ui-text/samples/src/main/res/values/styled-string-for-sample.xml b/compose/ui/ui-text/samples/src/main/res/values/styled-string-for-sample.xml
new file mode 100644
index 0000000..781b3fb
--- /dev/null
+++ b/compose/ui/ui-text/samples/src/main/res/values/styled-string-for-sample.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="example" translatable="false">
+        &lt;b>bold&lt;/b>
+        &lt;i>italic&lt;/i>
+        &lt;big>big&lt;/big>
+        &lt;small>small&lt;/small>
+        &lt;tt>monospace&lt;/tt>
+        &lt;font face="serif">serif&lt;/font>
+        &lt;font face="serif-monospace">serif-monospace&lt;/font>
+        &lt;font face="sans_serif">sans_serif&lt;/font>
+        &lt;font face="cursive">cursive&lt;/font>
+        &lt;font face="casual">casual&lt;/font>
+        &lt;font face="sans-serif-smallcaps">sans-serif-smallcaps&lt;/font>
+        &lt;font face="sans-serif-condensed-light">sans-serif-condensed-light&lt;/font>
+        &lt;font face="sans-serif-condensed">sans-serif-condensed&lt;/font>
+        &lt;font face="sans-serif-condensed-medium">sans-serif-condensed-medium&lt;/font>
+        &lt;font color="#00ff00">green&lt;/font>
+        &lt;sup>superscript&lt;/sup>
+        &lt;strike>strikethrough&lt;/strike>
+        &lt;sub>subscript&lt;/sub>
+        &lt;u>underline&lt;/u>
+        &lt;span style="background-color:#ff0000">span&lt;/span>
+        &lt;p dir="rtl">right to left&lt;/p>
+        &lt;p dir="ltr">left to right&lt;/p>
+        I am &lt;div>div&lt;/div> element.&lt;br>
+        &lt;a href="https://developer.android.com">Link&lt;/a>
+    </string>
+</resources>
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/AndroidManifest.xml b/compose/ui/ui-text/src/androidInstrumentedTest/AndroidManifest.xml
new file mode 100644
index 0000000..934bf4b
--- /dev/null
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+        <activity android:name="androidx.activity.ComponentActivity" />
+    </application>
+</manifest>
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AnnotatedStringFromHtmlTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AnnotatedStringFromHtmlTest.kt
new file mode 100644
index 0000000..8762ee1
--- /dev/null
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AnnotatedStringFromHtmlTest.kt
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+import android.graphics.Typeface
+import android.text.Layout
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.AlignmentSpan
+import android.text.style.BackgroundColorSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.RelativeSizeSpan
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.SubscriptSpan
+import android.text.style.SuperscriptSpan
+import android.text.style.TypefaceSpan
+import android.text.style.URLSpan
+import android.text.style.UnderlineSpan
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.BaselineShift
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.em
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+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
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AnnotatedStringFromHtmlTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    // pre-N block-level elements were separated with two new lines
+    @SdkSuppress(minSdkVersion = 24)
+    fun buildAnnotatedString_fromHtml() {
+        rule.setContent {
+            val expected = buildAnnotatedString {
+                fun add(block: () -> Unit) {
+                    block()
+                    append("a")
+                    pop()
+                    append(" ")
+                }
+                fun addStyle(style: SpanStyle) {
+                    add { pushStyle(style) }
+                }
+
+                add { pushLink(LinkAnnotation.Url("https://example.com")) }
+                add { pushStringAnnotation("foo", "Bar") }
+                addStyle(SpanStyle(fontWeight = FontWeight.Bold))
+                addStyle(SpanStyle(fontSize = 1.25.em))
+                append("\na\n") // <div>
+                addStyle(SpanStyle(fontFamily = FontFamily.Serif))
+                addStyle(SpanStyle(color = Color.Green))
+                addStyle(SpanStyle(fontStyle = FontStyle.Italic))
+                append("\na\n") // <p>
+                addStyle(SpanStyle(textDecoration = TextDecoration.LineThrough))
+                addStyle(SpanStyle(fontSize = 0.8.em))
+                addStyle(SpanStyle(background = Color.Red))
+                addStyle(SpanStyle(baselineShift = BaselineShift.Subscript))
+                addStyle(SpanStyle(baselineShift = BaselineShift.Superscript))
+                addStyle(SpanStyle(fontFamily = FontFamily.Monospace))
+                addStyle(SpanStyle(textDecoration = TextDecoration.Underline))
+            }
+
+            val actual = stringResource(androidx.compose.ui.text.test.R.string.html).parseAsHtml()
+
+            assertThat(actual.text).isEqualTo(expected.text)
+            assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles).inOrder()
+            assertThat(actual.paragraphStyles)
+                .containsExactlyElementsIn(expected.paragraphStyles)
+                .inOrder()
+            assertThat(actual.getStringAnnotations(0, actual.length))
+                .containsExactlyElementsIn(expected.getStringAnnotations(0, expected.length))
+                .inOrder()
+            assertThat(actual.getLinkAnnotations(0, actual.length))
+                .containsExactlyElementsIn(expected.getLinkAnnotations(0, expected.length))
+                .inOrder()
+        }
+    }
+
+    @Test
+    fun formattedString_withStyling() {
+        rule.setContent {
+            val actual = stringResource(
+                androidx.compose.ui.text.test.R.string.formatting,
+                "computer"
+            ).parseAsHtml()
+            assertThat(actual.text).isEqualTo("Hello, computer!")
+            assertThat(actual.spanStyles).containsExactly(
+                AnnotatedString.Range(SpanStyle(fontWeight = FontWeight.Bold), 7, 15)
+            )
+        }
+    }
+
+    @Test
+    fun annotationTag_withNoText_noStringAnnotation() {
+        rule.setContent {
+            val actual = "a<annotation key1=value1></annotation>".parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("a")
+            assertThat(actual.getStringAnnotations(0, actual.length)).isEmpty()
+        }
+    }
+
+    @Test
+    fun annotationTag_withNoAttributes_noStringAnnotation() {
+        rule.setContent {
+            val actual = "<annotation>a</annotation>".parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("a")
+            assertThat(actual.getStringAnnotations(0, actual.length)).isEmpty()
+        }
+    }
+
+    @Test
+    fun annotationTag_withOneAttribute_oneStringAnnotation() {
+        rule.setContent {
+            val actual = "<annotation key1=value1>a</annotation>".parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("a")
+            assertThat(actual.getStringAnnotations(0, actual.length)).containsExactly(
+                AnnotatedString.Range("value1", 0, 1, "key1")
+            )
+        }
+    }
+
+    @Test
+    fun annotationTag_withMultipleAttributes_multipleStringAnnotations() {
+        rule.setContent {
+            val actual = """
+                <annotation key1="value1" key2=value2 keyThree="valueThree">a</annotation>
+            """.trimIndent().parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("a")
+            assertThat(actual.getStringAnnotations(0, actual.length)).containsExactly(
+                AnnotatedString.Range("value1", 0, 1, "key1"),
+                AnnotatedString.Range("value2", 0, 1, "key2"),
+                AnnotatedString.Range("valueThree", 0, 1, "keythree")
+            )
+        }
+    }
+
+    @Test
+    fun annotationTag_withMultipleAnnotations_multipleStringAnnotations() {
+        rule.setContent {
+            val actual = """
+                <annotation key1=val1>a</annotation>a<annotation key2="val2">a</annotation>
+                """.trimIndent().parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("aaa")
+            assertThat(actual.getStringAnnotations(0, actual.length)).containsExactly(
+                AnnotatedString.Range("val1", 0, 1, "key1"),
+                AnnotatedString.Range("val2", 2, 3, "key2")
+            )
+        }
+    }
+
+    @Test
+    fun annotationTag_withOtherTag() {
+        rule.setContent {
+            val actual = "<annotation key1=\"value1\">a</annotation><b>a</b>".parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("aa")
+            assertThat(actual.spanStyles).containsExactly(
+                AnnotatedString.Range(SpanStyle(fontWeight = FontWeight.Bold), 1, 2),
+            )
+            assertThat(actual.getStringAnnotations(0, actual.length)).containsExactly(
+                AnnotatedString.Range("value1", 0, 1, "key1")
+            )
+        }
+    }
+
+    @Test
+    fun annotationTag_wrappedByOtherTag() {
+        rule.setContent {
+            val actual = "<b><annotation key1=\"value1\">a</annotation></b>".parseAsHtml()
+
+            assertThat(actual.text).isEqualTo("a")
+            assertThat(actual.spanStyles).containsExactly(
+                AnnotatedString.Range(SpanStyle(fontWeight = FontWeight.Bold), 0, 1)
+            )
+            assertThat(actual.getStringAnnotations(0, actual.length)).containsExactly(
+                AnnotatedString.Range("value1", 0, 1, "key1")
+            )
+        }
+    }
+
+    fun verify_alignmentSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(ParagraphStyle(textAlign = TextAlign.Center)) { append("a") }
+        }
+        val actual = buildSpannableString(
+            AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER)
+        ).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.paragraphStyles).containsExactlyElementsIn(
+            expected.paragraphStyles
+        )
+    }
+
+    fun verify_backgroundColorSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(background = Color.Red)) { append("a") }
+        }
+        val actual =
+            buildSpannableString(BackgroundColorSpan(Color.Red.toArgb())).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_foregroundColorSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(color = Color.Blue)) { append("a") }
+        }
+        val actual =
+            buildSpannableString(ForegroundColorSpan(Color.Blue.toArgb())).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_relativeSizeSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(fontSize = 0.6f.em)) { append("a") }
+        }
+        val actual = buildSpannableString(RelativeSizeSpan(0.6f)).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_strikeThroughSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough)) { append("a") }
+        }
+        val actual = buildSpannableString(StrikethroughSpan()).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_styleSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)) {
+                append("a")
+            }
+        }
+        val actual = buildSpannableString(StyleSpan(Typeface.BOLD_ITALIC)).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    fun verify_subscriptSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(baselineShift = BaselineShift.Subscript)) { append("a") }
+        }
+        val actual = buildSpannableString(SubscriptSpan()).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_superScriptSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(baselineShift = BaselineShift.Superscript)) { append("a") }
+        }
+        val actual = buildSpannableString(SuperscriptSpan()).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_typefaceSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(fontFamily = FontFamily.Monospace)) { append("a") }
+        }
+        val actual = buildSpannableString(TypefaceSpan("monospace")).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    @Test
+    fun verify_underlineSpan() {
+        val expected = buildAnnotatedString {
+            withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) { append("a") }
+        }
+        val actual = buildSpannableString(UnderlineSpan()).toAnnotatedString()
+
+        assertThat(actual.text).isEqualTo(expected.text)
+        assertThat(actual.spanStyles).containsExactlyElementsIn(expected.spanStyles)
+    }
+
+    fun verify_urlSpan() {
+        val spannable = SpannableStringBuilder()
+        spannable.append("a", URLSpan("url"), Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+
+        val expected = buildAnnotatedString {
+            withAnnotation(LinkAnnotation.Url("url")) { append("a") }
+        }
+        assertThat(spannable.toAnnotatedString().text).isEqualTo(expected.text)
+        assertThat(spannable.toAnnotatedString().getLinkAnnotations(0, 1))
+            .containsExactlyElementsIn(expected.getLinkAnnotations(0, 1))
+    }
+
+    private fun buildSpannableString(span: Any) = SpannableStringBuilder().also {
+        it.append("a", span, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+    }
+}
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/res/values/styled-string-for-test.xml b/compose/ui/ui-text/src/androidInstrumentedTest/res/values/styled-string-for-test.xml
new file mode 100644
index 0000000..c20f742
--- /dev/null
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/res/values/styled-string-for-test.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="html" translatable="false">
+        &lt;a href="https://example.com">a&lt;/a>
+        &lt;annotation Foo=Bar>a&lt;/annotation>
+        &lt;b>a&lt;/b>
+        &lt;big>a&lt;/big>
+        &lt;div>a&lt;/div>
+        &lt;font face="serif">a&lt;/font>
+        &lt;font color="#00ff00">a&lt;/font>
+        &lt;i>a&lt;/i>
+        &lt;p>a&lt;/p>
+        &lt;s>a&lt;/s>
+        &lt;small>a&lt;/small>
+        &lt;span style="background-color:red">a&lt;/span>
+        &lt;sub>a&lt;/sub>
+        &lt;sup>a&lt;/sup>
+        &lt;tt>a&lt;/tt>
+        &lt;u>a&lt;/u>
+    </string>
+    <string name="formatting">Hello, &lt;b>%s&lt;/b>!</string>
+</resources>
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
index 68fbac8..5b73220 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
@@ -402,7 +402,8 @@
         )
     }
 
-    private val wordIterator: WordIterator = layout.wordIterator
+    private val wordIterator: WordIterator
+        get() = layout.wordIterator
 
     override fun getWordBoundary(offset: Int): TextRange {
         val wordIterator = layout.wordIterator
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt
new file mode 100644
index 0000000..41b3baa
--- /dev/null
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Html.android.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+import android.graphics.Typeface
+import android.text.Editable
+import android.text.Html.TagHandler
+import android.text.Layout
+import android.text.Spanned
+import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+import android.text.Spanned.SPAN_MARK_MARK
+import android.text.style.AbsoluteSizeSpan
+import android.text.style.AlignmentSpan
+import android.text.style.BackgroundColorSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.RelativeSizeSpan
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.SubscriptSpan
+import android.text.style.SuperscriptSpan
+import android.text.style.TypefaceSpan
+import android.text.style.URLSpan
+import android.text.style.UnderlineSpan
+import androidx.annotation.VisibleForTesting
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.BaselineShift
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.util.fastForEach
+import androidx.core.text.HtmlCompat
+import org.xml.sax.Attributes
+import org.xml.sax.ContentHandler
+import org.xml.sax.XMLReader
+
+actual fun String.parseAsHtml(): AnnotatedString {
+    // Check ContentHandlerReplacementTag kdoc for more details
+    val stringToParse = "<$ContentHandlerReplacementTag />$this"
+    val spanned = HtmlCompat.fromHtml(
+        stringToParse,
+        HtmlCompat.FROM_HTML_MODE_COMPACT,
+        null,
+        TagHandler
+    )
+    return spanned.toAnnotatedString()
+}
+
+@VisibleForTesting
+internal fun Spanned.toAnnotatedString(): AnnotatedString {
+    return AnnotatedString.Builder(capacity = length)
+        .append(this)
+        .also { it.addSpans(this) }
+        .toAnnotatedString()
+}
+
+private fun AnnotatedString.Builder.addSpans(spanned: Spanned) {
+    spanned.getSpans(0, length, Any::class.java).forEach { span ->
+        val range = TextRange(spanned.getSpanStart(span), spanned.getSpanEnd(span))
+        addSpan(span, range.start, range.end)
+    }
+}
+
+private fun AnnotatedString.Builder.addSpan(span: Any, start: Int, end: Int) {
+    when (span) {
+        is AbsoluteSizeSpan -> {
+            // TODO(soboleva) need density object or make dip/px new units in TextUnit
+        }
+        is AlignmentSpan -> {
+            addStyle(span.toParagraphStyle(), start, end)
+        }
+        is AnnotationSpan -> {
+            addStringAnnotation(span.key, span.value, start, end)
+        }
+        is BackgroundColorSpan -> {
+            addStyle(SpanStyle(background = Color(span.backgroundColor)), start, end)
+        }
+        is ForegroundColorSpan -> {
+            addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end)
+        }
+        is RelativeSizeSpan -> {
+            addStyle(SpanStyle(fontSize = span.sizeChange.em), start, end)
+        }
+        is StrikethroughSpan -> {
+            addStyle(SpanStyle(textDecoration = TextDecoration.LineThrough), start, end)
+        }
+        is StyleSpan -> {
+            span.toSpanStyle()?.let { addStyle(it, start, end) }
+        }
+        is SubscriptSpan -> {
+            addStyle(SpanStyle(baselineShift = BaselineShift.Subscript), start, end)
+        }
+        is SuperscriptSpan -> {
+            addStyle(SpanStyle(baselineShift = BaselineShift.Superscript), start, end)
+        }
+        is TypefaceSpan -> {
+            addStyle(span.toSpanStyle(), start, end)
+        }
+        is UnderlineSpan -> {
+            addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end)
+        }
+        is URLSpan -> {
+            span.url?.let {
+                addLink(LinkAnnotation.Url(it), start, end)
+            }
+        }
+    }
+}
+
+private fun AlignmentSpan.toParagraphStyle(): ParagraphStyle {
+    val alignment = when (this.alignment) {
+        Layout.Alignment.ALIGN_NORMAL -> TextAlign.Start
+        Layout.Alignment.ALIGN_CENTER -> TextAlign.Center
+        Layout.Alignment.ALIGN_OPPOSITE -> TextAlign.End
+        else -> TextAlign.Unspecified
+    }
+    return ParagraphStyle(textAlign = alignment)
+}
+
+private fun StyleSpan.toSpanStyle(): SpanStyle? {
+    /** StyleSpan doc: styles are cumulative -- if both bold and italic are set in
+     * separate spans, or if the base style is bold and a span calls for italic,
+     * you get bold italic.  You can't turn off a style from the base style.
+     */
+    return when (style) {
+        Typeface.BOLD -> {
+            SpanStyle(fontWeight = FontWeight.Bold)
+        }
+        Typeface.ITALIC -> {
+            SpanStyle(fontStyle = FontStyle.Italic)
+        }
+        Typeface.BOLD_ITALIC -> {
+            SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic)
+        }
+        else -> null
+    }
+}
+
+private fun TypefaceSpan.toSpanStyle(): SpanStyle {
+    val fontFamily = when (family) {
+        FontFamily.Cursive.name -> FontFamily.Cursive
+        FontFamily.Monospace.name -> FontFamily.Monospace
+        FontFamily.SansSerif.name -> FontFamily.SansSerif
+        FontFamily.Serif.name -> FontFamily.Serif
+        else -> { optionalFontFamilyFromName(family) }
+    }
+    return SpanStyle(fontFamily = fontFamily)
+}
+
+/**
+ * Mirrors [androidx.compose.ui.text.font.PlatformTypefaces.optionalOnDeviceFontFamilyByName]
+ * behavior with both font weight and font style being Normal in this case */
+private fun optionalFontFamilyFromName(familyName: String?): FontFamily? {
+    if (familyName.isNullOrEmpty()) return null
+    val typeface = Typeface.create(familyName, Typeface.NORMAL)
+    return typeface.takeIf { typeface != Typeface.DEFAULT &&
+        typeface != Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
+    }?.let { FontFamily(it) }
+}
+
+private val TagHandler = object : TagHandler {
+    override fun handleTag(
+        opening: Boolean,
+        tag: String?,
+        output: Editable?,
+        xmlReader: XMLReader?
+    ) {
+        if (xmlReader == null || output == null) return
+
+        if (opening && tag == ContentHandlerReplacementTag) {
+            val currentContentHandler = xmlReader.contentHandler
+            xmlReader.contentHandler = AnnotationContentHandler(currentContentHandler, output)
+        }
+    }
+}
+
+private class AnnotationContentHandler(
+    private val contentHandler: ContentHandler,
+    private val output: Editable
+) : ContentHandler by contentHandler {
+    override fun startElement(uri: String?, localName: String?, qName: String?, atts: Attributes?) {
+        if (localName == AnnotationTag) {
+            atts?.let { handleAnnotationStart(it) }
+        } else {
+            contentHandler.startElement(uri, localName, qName, atts)
+        }
+    }
+
+    override fun endElement(uri: String?, localName: String?, qName: String?) {
+        if (localName == AnnotationTag) {
+            handleAnnotationEnd()
+        } else {
+            contentHandler.endElement(uri, localName, qName)
+        }
+    }
+
+    private fun handleAnnotationStart(attributes: Attributes) {
+        // Each annotation can have several key/value attributes. So for
+        // <annotation key1=value1 key2=value2>...<annotation>
+        // example we will add two [AnnotationSpan]s which we'll later read
+        for (i in 0 until attributes.length) {
+            val key = attributes.getLocalName(i).orEmpty()
+            val value = attributes.getValue(i).orEmpty()
+            if (key.isNotEmpty() && value.isNotEmpty()) {
+                val start = output.length
+                // add temporary AnnotationSpan to the output to read it when handling
+                // the closing tag
+                output.setSpan(AnnotationSpan(key, value), start, start, SPAN_MARK_MARK)
+            }
+        }
+    }
+
+    private fun handleAnnotationEnd() {
+        // iterate through all of the spans that we added when handling the opening tag. Calculate
+        // the true position of the span and make a replacement
+        output.getSpans(0, output.length, AnnotationSpan::class.java)
+            .filter { output.getSpanFlags(it) == SPAN_MARK_MARK }
+            .fastForEach { annotation ->
+                val start = output.getSpanStart(annotation)
+                val end = output.length
+
+                output.removeSpan(annotation)
+                // only add the annotation if there's a text in between the opening and closing tags
+                if (start != end) {
+                    output.setSpan(annotation, start, end, SPAN_EXCLUSIVE_EXCLUSIVE)
+                }
+            }
+    }
+}
+
+private class AnnotationSpan(val key: String, val value: String)
+
+/**
+ * This tag is added at the beginning of a string fed to the HTML parser in order to trigger
+ * a TagHandler's callback early on so we can replace the ContentHandler with our
+ * own [AnnotationContentHandler]. This is needed to handle the opening <annotation> tags since by
+ * the time TagHandler is triggered, the parser already visited and left the opening <annotation>
+ * tag which contains the attributes. Note that closing tag doesn't have the attributes and
+ * therefore not enough to construct the intermediate [AnnotationSpan] object that is later
+ * transformed into [AnnotatedString]'s string annotation.
+ */
+private const val ContentHandlerReplacementTag = "ContentHandlerReplacementTag"
+private const val AnnotationTag = "annotation"
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Html.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Html.kt
new file mode 100644
index 0000000..3332e6d
--- /dev/null
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Html.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text
+
+/**
+ * Converts a string with HTML tags into [AnnotatedString].
+ *
+ * If you define your string in the resources, make sure to use HTML-escaped opening brackets
+ * "&lt;" instead of "<".
+ *
+ * For a list of supported tags go check
+ * [Styling with HTML markup](https://developer.android.com/guide/topics/resources/string-resource#StylingWithHTML)
+ * guide. Note that bullet lists and custom annotations are not **yet** available.
+ *
+ * Example of displaying styled string from resources
+ * @sample androidx.compose.ui.text.samples.AnnotatedStringFromHtml
+ */
+expect fun String.parseAsHtml(): AnnotatedString
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Html.skiko.kt
similarity index 73%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Html.skiko.kt
index 3e91597..20a8f27 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Html.skiko.kt
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.compose.ui.text
+
+/**
+ * TBD: not yet implemented.
+ *
+ * Converts a string with HTML tags into [AnnotatedString].
+ */
+actual fun String.parseAsHtml(): AnnotatedString {
+    return AnnotatedString(this)
+}
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 67395d6..e0e50d7 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -71,7 +71,7 @@
                 api("androidx.annotation:annotation:1.1.0")
                 implementation(project(":compose:animation:animation"))
                 implementation("androidx.savedstate:savedstate-ktx:1.2.1")
-                implementation(project(":compose:material:material"))
+                implementation("androidx.compose.material:material:1.0.0")
                 implementation("androidx.activity:activity-compose:1.7.0")
                 implementation("androidx.lifecycle:lifecycle-common:2.6.1")
 
diff --git a/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/SimpleComposablePreview.kt b/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/SimpleComposablePreview.kt
index e09ba8d..bb18d5b 100644
--- a/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/SimpleComposablePreview.kt
+++ b/compose/ui/ui-tooling/src/androidInstrumentedTest/kotlin/androidx/compose/ui/tooling/SimpleComposablePreview.kt
@@ -28,7 +28,6 @@
 import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.tooling.preview.PreviewDynamicColors
 import androidx.compose.ui.tooling.preview.PreviewFontScale
@@ -37,6 +36,7 @@
 import androidx.compose.ui.tooling.preview.PreviewParameterProvider
 import androidx.compose.ui.tooling.preview.PreviewScreenSizes
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
 
 @Preview
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 0e97748..45b08d7 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -920,7 +920,7 @@
   public abstract sealed class VectorProperty<T> {
   }
 
-  public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+  public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush?> {
     field public static final androidx.compose.ui.graphics.vector.VectorProperty.Fill INSTANCE;
   }
 
@@ -952,7 +952,7 @@
     field public static final androidx.compose.ui.graphics.vector.VectorProperty.ScaleY INSTANCE;
   }
 
-  public static final class VectorProperty.Stroke extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+  public static final class VectorProperty.Stroke extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush?> {
     field public static final androidx.compose.ui.graphics.vector.VectorProperty.Stroke INSTANCE;
   }
 
@@ -2387,6 +2387,7 @@
     method public int getMeasuredWidth();
     method protected final long getMeasurementConstraints();
     method public final int getWidth();
+    method protected void placeAt(long position, float zIndex, androidx.compose.ui.graphics.layer.GraphicsLayer layer);
     method protected abstract void placeAt(long position, float zIndex, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit>? layerBlock);
     method protected final void setMeasuredSize(long);
     method protected final void setMeasurementConstraints(long);
@@ -2409,9 +2410,13 @@
     method public final void place(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
     method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
     method public final void placeRelative(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
+    method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
+    method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, long position, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, long position, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
+    method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
+    method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, long position, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, long position, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
     property public androidx.compose.ui.layout.LayoutCoordinates? coordinates;
     property protected abstract androidx.compose.ui.unit.LayoutDirection parentLayoutDirection;
@@ -2497,7 +2502,7 @@
     method public void getSlotsToRetain(androidx.compose.ui.layout.SubcomposeSlotReusePolicy.SlotIdsSet slotIds);
   }
 
-  public static final class SubcomposeSlotReusePolicy.SlotIdsSet implements java.util.Collection<java.lang.Object> kotlin.jvm.internal.markers.KMappedMarker {
+  public static final class SubcomposeSlotReusePolicy.SlotIdsSet implements java.util.Collection<java.lang.Object?> kotlin.jvm.internal.markers.KMappedMarker {
     method public void clear();
     method public java.util.Iterator<java.lang.Object?> iterator();
     method public boolean remove(Object? slotId);
@@ -2842,6 +2847,7 @@
   public final class ClipEntry {
     ctor public ClipEntry(android.content.ClipData clipData);
     method public android.content.ClipData getClipData();
+    method public androidx.compose.ui.platform.ClipMetadata getMetadata();
     property public final android.content.ClipData clipData;
   }
 
@@ -2857,12 +2863,10 @@
 
   public interface ClipboardManager {
     method public default androidx.compose.ui.platform.ClipEntry? getClip();
-    method public default androidx.compose.ui.platform.ClipMetadata? getClipMetadata();
     method public default android.content.ClipboardManager getNativeClipboard();
     method public androidx.compose.ui.text.AnnotatedString? getText();
-    method public default boolean hasClip();
     method public default boolean hasText();
-    method public default void setClip(androidx.compose.ui.platform.ClipEntry clipEntry);
+    method public default void setClip(androidx.compose.ui.platform.ClipEntry? clipEntry);
     method public void setText(androidx.compose.ui.text.AnnotatedString annotatedString);
     property public default android.content.ClipboardManager nativeClipboard;
   }
@@ -3196,6 +3200,16 @@
 
 }
 
+package androidx.compose.ui.scrollcapture {
+
+  public final class ScrollCapture_androidKt {
+    method @Deprecated public static boolean getComposeFeatureFlag_LongScreenshotsEnabled();
+    method @Deprecated public static void setComposeFeatureFlag_LongScreenshotsEnabled(boolean);
+    property @Deprecated public static final boolean ComposeFeatureFlag_LongScreenshotsEnabled;
+  }
+
+}
+
 package androidx.compose.ui.semantics {
 
   public final class AccessibilityAction<T extends kotlin.Function<? extends java.lang.Boolean>> {
@@ -3314,6 +3328,7 @@
     method @Deprecated public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getPerformImeAction();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getRequestFocus();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Float,java.lang.Boolean>>> getScrollBy();
+    method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.jvm.functions.Function2<androidx.compose.ui.geometry.Offset,kotlin.coroutines.Continuation<? super androidx.compose.ui.geometry.Offset>,java.lang.Object?>> getScrollByOffset();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Boolean>>> getScrollToIndex();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Boolean>>> getSetProgress();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function3<java.lang.Integer,java.lang.Integer,java.lang.Boolean,java.lang.Boolean>>> getSetSelection();
@@ -3341,6 +3356,7 @@
     property @Deprecated public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> PerformImeAction;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> RequestFocus;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Float,java.lang.Boolean>>> ScrollBy;
+    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.jvm.functions.Function2<androidx.compose.ui.geometry.Offset,kotlin.coroutines.Continuation<? super androidx.compose.ui.geometry.Offset>,java.lang.Object?>> ScrollByOffset;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Boolean>>> ScrollToIndex;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Boolean>>> SetProgress;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function3<java.lang.Integer,java.lang.Integer,java.lang.Boolean,java.lang.Boolean>>> SetSelection;
@@ -3363,7 +3379,6 @@
     method public <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T value);
     method public void setClearingSemantics(boolean);
     method public void setMergingSemanticsOfDescendants(boolean);
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public <T> void unset(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
     property public final boolean isClearingSemantics;
     property public final boolean isMergingSemanticsOfDescendants;
   }
@@ -3446,7 +3461,6 @@
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getInvisibleToUser();
     method @Deprecated public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getIsContainer();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsDialog();
-    method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsOpaque();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsPopup();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getIsShowingTextSubstitution();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getIsTraversalGroup();
@@ -3481,7 +3495,6 @@
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> InvisibleToUser;
     property @Deprecated public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> IsContainer;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsDialog;
-    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsOpaque;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsPopup;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> IsShowingTextSubstitution;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> IsTraversalGroup;
@@ -3550,7 +3563,6 @@
     method public static void insertTextAtCursor(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.AnnotatedString,java.lang.Boolean>? action);
     method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void invisibleToUser(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method @Deprecated public static boolean isContainer(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
-    method public static void isOpaque(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static boolean isShowingTextSubstitution(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static boolean isTraversalGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void onClick(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
@@ -3566,6 +3578,7 @@
     method public static void popup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void requestFocus(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
     method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
+    method public static void scrollByOffset(androidx.compose.ui.semantics.SemanticsPropertyReceiver, kotlin.jvm.functions.Function2<? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super androidx.compose.ui.geometry.Offset>,?> action);
     method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
     method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo);
@@ -3616,7 +3629,6 @@
 
   public interface SemanticsPropertyReceiver {
     method public operator <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T value);
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public <T> void unset(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
   }
 
 }
diff --git a/compose/ui/ui/api/res-current.txt b/compose/ui/ui/api/res-current.txt
index e69de29..ba71b41 100644
--- a/compose/ui/ui/api/res-current.txt
+++ b/compose/ui/ui/api/res-current.txt
@@ -0,0 +1 @@
+id hide_in_inspector_tag
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 54d037a..d2a9c77 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -920,7 +920,7 @@
   public abstract sealed class VectorProperty<T> {
   }
 
-  public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+  public static final class VectorProperty.Fill extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush?> {
     field public static final androidx.compose.ui.graphics.vector.VectorProperty.Fill INSTANCE;
   }
 
@@ -952,7 +952,7 @@
     field public static final androidx.compose.ui.graphics.vector.VectorProperty.ScaleY INSTANCE;
   }
 
-  public static final class VectorProperty.Stroke extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush> {
+  public static final class VectorProperty.Stroke extends androidx.compose.ui.graphics.vector.VectorProperty<androidx.compose.ui.graphics.Brush?> {
     field public static final androidx.compose.ui.graphics.vector.VectorProperty.Stroke INSTANCE;
   }
 
@@ -2394,6 +2394,7 @@
     method public int getMeasuredWidth();
     method protected final long getMeasurementConstraints();
     method public final int getWidth();
+    method protected void placeAt(long position, float zIndex, androidx.compose.ui.graphics.layer.GraphicsLayer layer);
     method protected abstract void placeAt(long position, float zIndex, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit>? layerBlock);
     method protected final void setMeasuredSize(long);
     method protected final void setMeasurementConstraints(long);
@@ -2416,9 +2417,13 @@
     method public final void place(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
     method public final void placeRelative(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex);
     method public final void placeRelative(androidx.compose.ui.layout.Placeable, long position, optional float zIndex);
+    method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
+    method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, long position, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeRelativeWithLayer(androidx.compose.ui.layout.Placeable, long position, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
+    method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, int x, int y, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
+    method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, long position, androidx.compose.ui.graphics.layer.GraphicsLayer layer, optional float zIndex);
     method public final void placeWithLayer(androidx.compose.ui.layout.Placeable, long position, optional float zIndex, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.GraphicsLayerScope,kotlin.Unit> layerBlock);
     property public androidx.compose.ui.layout.LayoutCoordinates? coordinates;
     property protected abstract androidx.compose.ui.unit.LayoutDirection parentLayoutDirection;
@@ -2504,7 +2509,7 @@
     method public void getSlotsToRetain(androidx.compose.ui.layout.SubcomposeSlotReusePolicy.SlotIdsSet slotIds);
   }
 
-  public static final class SubcomposeSlotReusePolicy.SlotIdsSet implements java.util.Collection<java.lang.Object> kotlin.jvm.internal.markers.KMappedMarker {
+  public static final class SubcomposeSlotReusePolicy.SlotIdsSet implements java.util.Collection<java.lang.Object?> kotlin.jvm.internal.markers.KMappedMarker {
     method public void clear();
     method public java.util.Iterator<java.lang.Object?> iterator();
     method public boolean remove(Object? slotId);
@@ -2895,6 +2900,7 @@
   public final class ClipEntry {
     ctor public ClipEntry(android.content.ClipData clipData);
     method public android.content.ClipData getClipData();
+    method public androidx.compose.ui.platform.ClipMetadata getMetadata();
     property public final android.content.ClipData clipData;
   }
 
@@ -2910,12 +2916,10 @@
 
   public interface ClipboardManager {
     method public default androidx.compose.ui.platform.ClipEntry? getClip();
-    method public default androidx.compose.ui.platform.ClipMetadata? getClipMetadata();
     method public default android.content.ClipboardManager getNativeClipboard();
     method public androidx.compose.ui.text.AnnotatedString? getText();
-    method public default boolean hasClip();
     method public default boolean hasText();
-    method public default void setClip(androidx.compose.ui.platform.ClipEntry clipEntry);
+    method public default void setClip(androidx.compose.ui.platform.ClipEntry? clipEntry);
     method public void setText(androidx.compose.ui.text.AnnotatedString annotatedString);
     property public default android.content.ClipboardManager nativeClipboard;
   }
@@ -3256,6 +3260,16 @@
 
 }
 
+package androidx.compose.ui.scrollcapture {
+
+  public final class ScrollCapture_androidKt {
+    method @Deprecated public static boolean getComposeFeatureFlag_LongScreenshotsEnabled();
+    method @Deprecated public static void setComposeFeatureFlag_LongScreenshotsEnabled(boolean);
+    property @Deprecated public static final boolean ComposeFeatureFlag_LongScreenshotsEnabled;
+  }
+
+}
+
 package androidx.compose.ui.semantics {
 
   public final class AccessibilityAction<T extends kotlin.Function<? extends java.lang.Boolean>> {
@@ -3374,6 +3388,7 @@
     method @Deprecated public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getPerformImeAction();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> getRequestFocus();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Float,java.lang.Boolean>>> getScrollBy();
+    method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.jvm.functions.Function2<androidx.compose.ui.geometry.Offset,kotlin.coroutines.Continuation<? super androidx.compose.ui.geometry.Offset>,java.lang.Object?>> getScrollByOffset();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Boolean>>> getScrollToIndex();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Boolean>>> getSetProgress();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function3<java.lang.Integer,java.lang.Integer,java.lang.Boolean,java.lang.Boolean>>> getSetSelection();
@@ -3401,6 +3416,7 @@
     property @Deprecated public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> PerformImeAction;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> RequestFocus;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function2<java.lang.Float,java.lang.Float,java.lang.Boolean>>> ScrollBy;
+    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.jvm.functions.Function2<androidx.compose.ui.geometry.Offset,kotlin.coroutines.Continuation<? super androidx.compose.ui.geometry.Offset>,java.lang.Object?>> ScrollByOffset;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Boolean>>> ScrollToIndex;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function1<java.lang.Float,java.lang.Boolean>>> SetProgress;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function3<java.lang.Integer,java.lang.Integer,java.lang.Boolean,java.lang.Boolean>>> SetSelection;
@@ -3423,7 +3439,6 @@
     method public <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T value);
     method public void setClearingSemantics(boolean);
     method public void setMergingSemanticsOfDescendants(boolean);
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public <T> void unset(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
     property public final boolean isClearingSemantics;
     property public final boolean isMergingSemanticsOfDescendants;
   }
@@ -3506,7 +3521,6 @@
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getInvisibleToUser();
     method @Deprecated public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getIsContainer();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsDialog();
-    method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsOpaque();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> getIsPopup();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getIsShowingTextSubstitution();
     method public androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> getIsTraversalGroup();
@@ -3541,7 +3555,6 @@
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> InvisibleToUser;
     property @Deprecated public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> IsContainer;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsDialog;
-    property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsOpaque;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<kotlin.Unit> IsPopup;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> IsShowingTextSubstitution;
     property public final androidx.compose.ui.semantics.SemanticsPropertyKey<java.lang.Boolean> IsTraversalGroup;
@@ -3610,7 +3623,6 @@
     method public static void insertTextAtCursor(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.AnnotatedString,java.lang.Boolean>? action);
     method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void invisibleToUser(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method @Deprecated public static boolean isContainer(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
-    method public static void isOpaque(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static boolean isShowingTextSubstitution(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static boolean isTraversalGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void onClick(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
@@ -3626,6 +3638,7 @@
     method public static void popup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void requestFocus(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function0<java.lang.Boolean>? action);
     method public static void scrollBy(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean>? action);
+    method public static void scrollByOffset(androidx.compose.ui.semantics.SemanticsPropertyReceiver, kotlin.jvm.functions.Function2<? super androidx.compose.ui.geometry.Offset,? super kotlin.coroutines.Continuation<? super androidx.compose.ui.geometry.Offset>,?> action);
     method public static void scrollToIndex(androidx.compose.ui.semantics.SemanticsPropertyReceiver, optional String? label, kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> action);
     method public static void selectableGroup(androidx.compose.ui.semantics.SemanticsPropertyReceiver);
     method public static void setCollectionInfo(androidx.compose.ui.semantics.SemanticsPropertyReceiver, androidx.compose.ui.semantics.CollectionInfo);
@@ -3676,7 +3689,6 @@
 
   public interface SemanticsPropertyReceiver {
     method public operator <T> void set(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key, T value);
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public <T> void unset(androidx.compose.ui.semantics.SemanticsPropertyKey<T> key);
   }
 
 }
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 265869f..8180f6d 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -98,7 +98,7 @@
                 implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
                 implementation("androidx.emoji2:emoji2:1.2.0")
 
-                implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+                implementation("androidx.profileinstaller:profileinstaller:1.3.1")
             }
         }
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/ClipboardDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/ClipboardDemo.kt
index 94804da..5c309ab 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/ClipboardDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/ClipboardDemo.kt
@@ -185,9 +185,9 @@
 }
 
 fun ClipboardManager.hasImage(): Boolean {
-    if (!hasClip()) return false
+    val clipMetadata = getClip()?.getMetadata() ?: return false
 
-    return getClipMetadata()?.clipDescription?.hasMimeType("image/*") ?: false
+    return clipMetadata.clipDescription.hasMimeType("image/*")
 }
 
 @Suppress("ClassVerificationFailure", "DEPRECATION")
diff --git a/compose/ui/ui/proguard-rules.pro b/compose/ui/ui/proguard-rules.pro
index 1dbcbf7..7aa50f4 100644
--- a/compose/ui/ui/proguard-rules.pro
+++ b/compose/ui/ui/proguard-rules.pro
@@ -17,6 +17,7 @@
 # R8 to complain about them not being there during optimization.
 -dontwarn android.view.RenderNode
 -dontwarn android.view.DisplayListCanvas
+-dontwarn android.view.HardwareCanvas
 
 -keepclassmembers class androidx.compose.ui.platform.ViewLayerContainer {
     protected void dispatchGetDisplayList();
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
index 5c7bddf..3b9a823 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
@@ -44,11 +44,11 @@
 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import kotlin.math.roundToInt
 
 @Suppress("SetTextI18n")
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 64797a5..c6f8ea58 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -139,8 +139,6 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.getOrNull
 import androidx.compose.ui.semantics.invisibleToUser
-import androidx.compose.ui.semantics.isContainer
-import androidx.compose.ui.semantics.isOpaque
 import androidx.compose.ui.semantics.isTraversalGroup
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.role
@@ -3960,144 +3958,6 @@
         }
     }
 
-    @OptIn(ExperimentalComposeUiApi::class)
-    @Test
-    fun testSemanticsHitTest_unimportantTraversalProperties() {
-        // Arrange.
-        setContent {
-            Box(
-                Modifier
-                    .size(100.dp)
-                    .testTag(tag)
-                    .semantics { isTraversalGroup = true; traversalIndex = 1f }) {
-            }
-        }
-        val bounds = with(rule.density) { rule.onNodeWithTag(tag).getBoundsInRoot().toRect() }
-
-        // Act.
-        val hitNodeId = rule.runOnIdle {
-            delegate.hitTestSemanticsAt(
-                bounds.left + bounds.width / 2,
-                bounds.top + bounds.height / 2
-            )
-        }
-
-        // Assert it doesn't hit the tagged node since it only has unimportant properties.
-        rule.runOnIdle { assertThat(hitNodeId).isEqualTo(InvalidId) }
-    }
-
-    @Test
-    @OptIn(ExperimentalComposeUiApi::class)
-    @Suppress("DEPRECATION")
-    fun testAccessibilityNodeInfoTreePruned_isContainerFalseDoesNotPrune() {
-        // Arrange.
-        val parentTag = "ParentForOverlappedChildren"
-        val childOneTag = "OverlappedChildOne"
-        val childTwoTag = "OverlappedChildTwo"
-        setContent {
-            Box(Modifier.testTag(parentTag)) {
-                with(LocalDensity.current) {
-                    Box(
-                        Modifier
-                            .zIndex(1f)
-                            .testTag(childOneTag)
-                            .semantics { isContainer = false }
-                            .semantics { isContainer = true }
-                            .requiredSize(50.toDp())
-                    )
-                    BasicText(
-                        "Child Two",
-                        Modifier
-                            .testTag(childTwoTag)
-                            .requiredSize(50.toDp())
-                    )
-                }
-            }
-        }
-        val parentNodeId = rule.onNodeWithTag(parentTag).semanticsId
-        val overlappedChildTwoNodeId = rule.onNodeWithTag(childTwoTag).semanticsId
-
-        rule.runOnIdle {
-            assertThat(createAccessibilityNodeInfo(parentNodeId).childCount).isEqualTo(2)
-            assertThat(createAccessibilityNodeInfo(overlappedChildTwoNodeId).text.toString())
-                .isEqualTo("Child Two")
-        }
-    }
-
-    @Test
-    @OptIn(ExperimentalComposeUiApi::class)
-    fun testAccessibilityNodeInfoTreePruned_invisibleDoesNotPrune() {
-        // Arrange.
-        val parentTag = "ParentForOverlappedChildren"
-        val childOneTag = "OverlappedChildOne"
-        val childTwoTag = "OverlappedChildTwo"
-        setContent {
-            Box(Modifier.testTag(parentTag)) {
-                with(LocalDensity.current) {
-                    BasicText(
-                        "Child One",
-                        Modifier
-                            .zIndex(1f)
-                            .testTag(childOneTag)
-                            .semantics { invisibleToUser() }
-                            .requiredSize(50.toDp())
-                    )
-                    BasicText(
-                        "Child Two",
-                        Modifier
-                            .testTag(childTwoTag)
-                            .requiredSize(50.toDp())
-                    )
-                }
-            }
-        }
-        val parentNodeId = rule.onNodeWithTag(parentTag).semanticsId
-        val overlappedChildTwoNodeId = rule.onNodeWithTag(childTwoTag).semanticsId
-
-        rule.runOnIdle {
-            assertThat(createAccessibilityNodeInfo(parentNodeId).childCount).isEqualTo(2)
-            assertThat(createAccessibilityNodeInfo(overlappedChildTwoNodeId).text.toString())
-                .isEqualTo("Child Two")
-        }
-    }
-
-    @Test
-    @OptIn(ExperimentalComposeUiApi::class)
-    fun testAccessibilityNodeInfoTreePruned_isOpaquePrunes() {
-        // Arrange.
-        val parentTag = "ParentForOverlappedChildren"
-        val childOneTag = "OverlappedChildOne"
-        val childTwoTag = "OverlappedChildTwo"
-        setContent {
-            Box(Modifier.testTag(parentTag)) {
-                with(LocalDensity.current) {
-                    Box(
-                        Modifier
-                            .zIndex(1f)
-                            .testTag(childOneTag)
-                            .semantics { isOpaque() }
-                            .requiredSize(50.toDp())
-                    )
-                    BasicText(
-                        "Child Two",
-                        Modifier
-                            .testTag(childTwoTag)
-                            .requiredSize(50.toDp())
-                    )
-                }
-            }
-        }
-        val parentNodeId = rule.onNodeWithTag(parentTag).semanticsId
-        val overlappedChildOneNodeId = rule.onNodeWithTag(childOneTag).semanticsId
-        val overlappedChildTwoNodeId = rule.onNodeWithTag(childTwoTag).semanticsId
-
-        rule.runOnIdle {
-            assertThat(createAccessibilityNodeInfo(parentNodeId).childCount).isEqualTo(1)
-            assertThat(provider.createAccessibilityNodeInfo(overlappedChildOneNodeId)).isNotNull()
-            assertThat(provider.createAccessibilityNodeInfo(overlappedChildTwoNodeId)).isNull()
-        }
-    }
-
     @Test
     fun testPaneAppear() {
         var isPaneVisible by mutableStateOf(false)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt
index 629ff4e..10940f9 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/contentcapture/ContentCaptureTest.kt
@@ -63,9 +63,11 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.isNull
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -140,7 +142,10 @@
                     Box(
                         Modifier
                             .size(10.dp)
-                            .semantics { text = AnnotatedString("foo") }
+                            .semantics {
+                                text = AnnotatedString("foo")
+                                testTag = "testTagFoo"
+                            }
                     )
                     Box(
                         Modifier
@@ -167,6 +172,10 @@
                 assertThat(firstValue).isEqualTo("foo")
                 assertThat(secondValue).isEqualTo("bar")
             }
+            with(argumentCaptor<String>()) {
+                verify(viewStructureCompat, times(1)).setId(anyInt(), isNull(), isNull(), capture())
+                assertThat(firstValue).isEqualTo("testTagFoo")
+            }
             verify(contentCaptureSessionCompat, times(0)).notifyViewsDisappeared(any())
             with(argumentCaptor<List<ViewStructure>>()) {
                 verify(contentCaptureSessionCompat, times(1)).notifyViewsAppeared(capture())
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt
index 92d11e5..c9f8888 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt
@@ -35,11 +35,13 @@
 import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.graphics.rememberGraphicsLayer
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalGraphicsContext
 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.Constraints
 import androidx.compose.ui.unit.IntSize
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -169,6 +171,39 @@
     }
 
     @Test
+    fun keepDrawingTheLayerWePreviouslyPlacedWith() {
+        // even that we don't place it anymore, it still holds the content we can continue drawing
+        rule.setContent {
+            Column {
+                if (!drawPrebuiltLayer) {
+                    val layer = obtainLayer()
+                    Canvas(
+                        Modifier
+                            .layout { measurable, _ ->
+                                val placeable = measurable.measure(Constraints.fixed(size, size))
+                                layout(placeable.width, placeable.height) {
+                                    placeable.placeWithLayer(0, 0, layer)
+                                }
+                            }
+                    ) {
+                        drawRect(Color.Red)
+                    }
+                } else {
+                    LayerDrawingBox()
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            drawPrebuiltLayer = true
+        }
+
+        rule.onNodeWithTag(LayerDrawingBoxTag)
+            .captureToImage()
+            .assertPixels(expectedSize) { Color.Red }
+    }
+
+    @Test
     fun drawNestedLayers_drawLayer() {
         rule.setContent {
             if (!drawPrebuiltLayer) {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
index 0d97c19..01b1a83 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
@@ -62,6 +62,8 @@
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.drawscope.inset
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.layer.GraphicsLayer
+import androidx.compose.ui.graphics.rememberGraphicsLayer
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.layout.Layout
@@ -86,6 +88,7 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -1797,4 +1800,82 @@
             assertEquals(0, modifierRelayoutCount)
         }
     }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun placingWithExplicitLayerDraws() {
+        rule.setContent {
+            val layer = rememberGraphicsLayer()
+            Canvas(
+                modifier = Modifier
+                    .testTag("tag")
+                    .layout { measurable, _ ->
+                        val placeable = measurable.measure(Constraints.fixed(10, 10))
+                        layout(placeable.width, placeable.height) {
+                            placeable.placeWithLayer(0, 0, layer)
+                        }
+                    }
+            ) {
+                drawRect(Color.Blue)
+            }
+        }
+
+        rule.onNodeWithTag("tag")
+            .captureToImage()
+            .assertPixels(IntSize(10, 10)) { Color.Blue }
+    }
+
+    @Test
+    fun placingWithExplicitLayerSetsCorrectSizeAndOffset() {
+        lateinit var layer: GraphicsLayer
+        rule.setContent {
+            layer = rememberGraphicsLayer()
+            Canvas(
+                modifier = Modifier.layout { measurable, _ ->
+                    val placeable = measurable.measure(Constraints.fixed(20, 20))
+                    layout(placeable.width, placeable.height) {
+                        placeable.placeWithLayer(10, 10, layer)
+                    }
+                }
+            ) {
+                drawRect(Color.Blue)
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(layer.size.width).isEqualTo(20)
+            assertThat(layer.size.height).isEqualTo(20)
+            assertThat(layer.topLeft.x).isEqualTo(10)
+            assertThat(layer.topLeft.y).isEqualTo(10)
+        }
+    }
+
+    @Test
+    fun layerIsNotReleasedWhenWeStopPlacingIt() {
+        lateinit var layer: GraphicsLayer
+        var needChild by mutableStateOf(true)
+        rule.setContent {
+            layer = rememberGraphicsLayer()
+            if (needChild) {
+                Canvas(
+                    modifier = Modifier.layout { measurable, constraints ->
+                        val placeable = measurable.measure(constraints)
+                        layout(placeable.width, placeable.height) {
+                            placeable.placeWithLayer(1, 0, layer)
+                        }
+                    }
+                ) {
+                    drawRect(Color.Blue)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            needChild = false
+        }
+
+        rule.runOnIdle {
+            assertThat(layer.isReleased).isFalse()
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
index 85b3f11..cea72d9 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/focus/FocusTargetAttachDetachTest.kt
@@ -20,8 +20,29 @@
 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.input.InputMode
+import androidx.compose.ui.input.InputModeManager
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyInputModifierNode
+import androidx.compose.ui.input.key.NativeKeyEvent
+import androidx.compose.ui.input.key.SoftKeyboardInterceptionModifierNode
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.pointer.elementFor
+import androidx.compose.ui.input.rotary.RotaryInputModifierNode
+import androidx.compose.ui.input.rotary.RotaryScrollEvent
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.platform.LocalInputModeManager
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.test.performRotaryScrollInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -980,6 +1001,407 @@
         }
     }
 
+    @OptIn(ExperimentalComposeUiApi::class, ExperimentalTestApi::class)
+    @Test
+    fun focusTarget_nodeThatIsKeyInputNodeKind_implementing_receivesKeyEventsWhenFocused() {
+        class FocusTargetAndKeyInputNode : DelegatingNode(), KeyInputModifierNode {
+            val keyEvents = mutableListOf<KeyEvent>()
+            val focusTargetNode = FocusTargetNode()
+
+            init {
+                delegate(focusTargetNode)
+            }
+
+            override fun onKeyEvent(event: KeyEvent): Boolean {
+                keyEvents.add(event)
+                return true
+            }
+
+            override fun onPreKeyEvent(event: KeyEvent) = false
+        }
+
+        val focusTargetAndKeyInputNode = FocusTargetAndKeyInputNode()
+        val focusTargetAndKeyInputModifier = elementFor(key1 = null, focusTargetAndKeyInputNode)
+
+        val focusRequester = FocusRequester()
+        val targetTestTag = "target"
+        lateinit var inputModeManager: InputModeManager
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            inputModeManager = LocalInputModeManager.current
+            Box(
+                modifier = Modifier
+                    .testTag(targetTestTag)
+                    .focusRequester(focusRequester)
+                    .then(focusTargetAndKeyInputModifier)
+            )
+        }
+
+        rule.runOnUiThread {
+            inputModeManager.requestInputMode(InputMode.Keyboard)
+            focusRequester.requestFocus()
+        }
+
+        assertThat(focusTargetAndKeyInputNode.focusTargetNode.focusState.isFocused).isTrue()
+
+        rule.onNodeWithTag(targetTestTag).performKeyInput { keyDown(Key.Enter) }
+
+        assertThat(focusTargetAndKeyInputNode.keyEvents).hasSize(1)
+        assertThat(focusTargetAndKeyInputNode.keyEvents[0].key).isEqualTo(Key.Enter)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class, ExperimentalTestApi::class)
+    @Test
+    fun focusTarget_nodeThatIsKeyInputNodeKind_delegating_receivesKeyEventsWhenFocused() {
+        class FocusTargetAndKeyInputNode : DelegatingNode() {
+            val keyEvents = mutableListOf<KeyEvent>()
+            val focusTargetNode = FocusTargetNode()
+            val keyInputNode = object : KeyInputModifierNode, Modifier.Node() {
+                override fun onKeyEvent(event: KeyEvent): Boolean {
+                    keyEvents.add(event)
+                    return true
+                }
+
+                override fun onPreKeyEvent(event: KeyEvent) = false
+            }
+
+            init {
+                delegate(focusTargetNode)
+                delegate(keyInputNode)
+            }
+        }
+
+        val focusTargetAndKeyInputNode = FocusTargetAndKeyInputNode()
+        val focusTargetAndKeyInputModifier = elementFor(key1 = null, focusTargetAndKeyInputNode)
+
+        val focusRequester = FocusRequester()
+        val targetTestTag = "target"
+        lateinit var inputModeManager: InputModeManager
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            inputModeManager = LocalInputModeManager.current
+            Box(
+                modifier = Modifier
+                    .testTag(targetTestTag)
+                    .focusRequester(focusRequester)
+                    .then(focusTargetAndKeyInputModifier)
+            )
+        }
+
+        rule.runOnUiThread {
+            inputModeManager.requestInputMode(InputMode.Keyboard)
+            focusRequester.requestFocus()
+        }
+
+        assertThat(focusTargetAndKeyInputNode.focusTargetNode.focusState.isFocused).isTrue()
+
+        rule.onNodeWithTag(targetTestTag).performKeyInput { keyDown(Key.Enter) }
+
+        assertThat(focusTargetAndKeyInputNode.keyEvents).hasSize(1)
+        assertThat(focusTargetAndKeyInputNode.keyEvents[0].key).isEqualTo(Key.Enter)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun focusTarget_nodeThatIsSoftKeyInputNodeKind_implementing_receivesSoftKeyEventsWhenFocused() {
+        class FocusTargetAndSoftKeyboardNode : DelegatingNode(),
+            SoftKeyboardInterceptionModifierNode {
+            val keyEvents = mutableListOf<KeyEvent>()
+            val focusTargetNode = FocusTargetNode()
+
+            init {
+                delegate(focusTargetNode)
+            }
+
+            override fun onInterceptKeyBeforeSoftKeyboard(event: KeyEvent) = keyEvents.add(event)
+
+            override fun onPreInterceptKeyBeforeSoftKeyboard(event: KeyEvent) = false
+        }
+
+        val focusTargetAndSoftKeyboardNode = FocusTargetAndSoftKeyboardNode()
+        val focusTargetAndSoftKeyboardModifier =
+            elementFor(key1 = null, focusTargetAndSoftKeyboardNode)
+
+        val focusRequester = FocusRequester()
+        val targetTestTag = "target"
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            Box(
+                modifier = Modifier
+                    .testTag(targetTestTag)
+                    .focusRequester(focusRequester)
+                    .then(focusTargetAndSoftKeyboardModifier)
+            )
+        }
+
+        rule.runOnUiThread { focusRequester.requestFocus() }
+        assertThat(focusTargetAndSoftKeyboardNode.focusTargetNode.focusState.isFocused).isTrue()
+
+        // This test specifically uses performKeyPress over performKeyInput as performKeyPress calls
+        // sendKeyEvent, which in turn notifies FocusOwner that there's a
+        // SoftKeyboardInterceptionModifierNode-interceptable key event first. performKeyInput goes
+        // through dispatchKeyEvent which does not notify SoftKeyboardInterceptionModifierNodes.
+        rule.onRoot().performKeyPress(
+            KeyEvent(
+                NativeKeyEvent(
+                    android.view.KeyEvent.ACTION_DOWN,
+                    android.view.KeyEvent.KEYCODE_ENTER
+                )
+            )
+        )
+
+        assertThat(focusTargetAndSoftKeyboardNode.keyEvents).hasSize(1)
+        assertThat(focusTargetAndSoftKeyboardNode.keyEvents[0].key).isEqualTo(Key.Enter)
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun focusTarget_nodeThatIsSoftKeyInputNodeKind_delegating_receivesSoftKeyEventsWhenFocused() {
+        class FocusTargetAndSoftKeyboardNode : DelegatingNode() {
+            val keyEvents = mutableListOf<KeyEvent>()
+            val focusTargetNode = FocusTargetNode()
+            val softKeyboardInterceptionNode = object : SoftKeyboardInterceptionModifierNode,
+                Modifier.Node() {
+                override fun onInterceptKeyBeforeSoftKeyboard(event: KeyEvent) =
+                    keyEvents.add(event)
+
+                override fun onPreInterceptKeyBeforeSoftKeyboard(event: KeyEvent) = false
+            }
+
+            init {
+                delegate(focusTargetNode)
+                delegate(softKeyboardInterceptionNode)
+            }
+        }
+
+        val focusTargetAndSoftKeyboardNode = FocusTargetAndSoftKeyboardNode()
+        val focusTargetAndSoftKeyboardModifier =
+            elementFor(key1 = null, focusTargetAndSoftKeyboardNode)
+
+        val focusRequester = FocusRequester()
+        val targetTestTag = "target"
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            Box(
+                modifier = Modifier
+                    .testTag(targetTestTag)
+                    .focusRequester(focusRequester)
+                    .then(focusTargetAndSoftKeyboardModifier)
+            )
+        }
+
+        rule.runOnUiThread { focusRequester.requestFocus() }
+        assertThat(focusTargetAndSoftKeyboardNode.focusTargetNode.focusState.isFocused).isTrue()
+
+        // This test specifically uses performKeyPress over performKeyInput as performKeyPress calls
+        // sendKeyEvent, which in turn notifies FocusOwner that there's a
+        // SoftKeyboardInterceptionModifierNode-interceptable key event first. performKeyInput goes
+        // through dispatchKeyEvent which does not notify SoftKeyboardInterceptionModifierNodes.
+        rule.onRoot().performKeyPress(
+            KeyEvent(
+                NativeKeyEvent(
+                    android.view.KeyEvent.ACTION_DOWN,
+                    android.view.KeyEvent.KEYCODE_ENTER
+                )
+            )
+        )
+
+        assertThat(focusTargetAndSoftKeyboardNode.keyEvents).hasSize(1)
+        assertThat(focusTargetAndSoftKeyboardNode.keyEvents[0].key).isEqualTo(Key.Enter)
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun focusTarget_nodeThatIsRotaryInputNodeKind_implementing_receivesRotaryEventsWhenFocused() {
+        class FocusTargetAndRotaryNode : DelegatingNode(), RotaryInputModifierNode {
+            val events = mutableListOf<RotaryScrollEvent>()
+            val focusTargetNode = FocusTargetNode()
+
+            init {
+                delegate(focusTargetNode)
+            }
+
+            override fun onRotaryScrollEvent(event: RotaryScrollEvent) = events.add(event)
+
+            override fun onPreRotaryScrollEvent(event: RotaryScrollEvent) = false
+        }
+
+        val focusTargetAndRotaryNode = FocusTargetAndRotaryNode()
+        val focusTargetAndRotaryModifier = elementFor(key1 = null, focusTargetAndRotaryNode)
+
+        val focusRequester = FocusRequester()
+        val targetTestTag = "target"
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            Box(
+                modifier = Modifier
+                    .testTag(targetTestTag)
+                    .focusRequester(focusRequester)
+                    .then(focusTargetAndRotaryModifier)
+            )
+        }
+
+        rule.runOnUiThread { focusRequester.requestFocus() }
+        assertThat(focusTargetAndRotaryNode.focusTargetNode.focusState.isFocused).isTrue()
+
+        rule.onNodeWithTag(targetTestTag).performRotaryScrollInput {
+            rotateToScrollVertically(100f)
+        }
+
+        assertThat(focusTargetAndRotaryNode.events).hasSize(1)
+        assertThat(focusTargetAndRotaryNode.events[0].verticalScrollPixels).isEqualTo(100f)
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun focusTarget_nodeThatIsRotaryInputNodeKind_delegating_receivesRotaryEventsWhenFocused() {
+        class FocusTargetAndRotaryNode : DelegatingNode() {
+            val events = mutableListOf<RotaryScrollEvent>()
+            val focusTargetNode = FocusTargetNode()
+            val rotaryInputNode = object : RotaryInputModifierNode, Modifier.Node() {
+                override fun onRotaryScrollEvent(event: RotaryScrollEvent) = events.add(event)
+                override fun onPreRotaryScrollEvent(event: RotaryScrollEvent) = false
+            }
+
+            init {
+                delegate(focusTargetNode)
+                delegate(rotaryInputNode)
+            }
+        }
+
+        val focusTargetAndRotaryNode = FocusTargetAndRotaryNode()
+        val focusTargetAndRotaryModifier = elementFor(key1 = null, focusTargetAndRotaryNode)
+
+        val focusRequester = FocusRequester()
+        val targetTestTag = "target"
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            Box(
+                modifier = Modifier
+                    .testTag(targetTestTag)
+                    .focusRequester(focusRequester)
+                    .then(focusTargetAndRotaryModifier)
+            )
+        }
+
+        rule.runOnUiThread { focusRequester.requestFocus() }
+        assertThat(focusTargetAndRotaryNode.focusTargetNode.focusState.isFocused).isTrue()
+
+        rule.onNodeWithTag(targetTestTag).performRotaryScrollInput {
+            rotateToScrollVertically(100f)
+        }
+
+        assertThat(focusTargetAndRotaryNode.events).hasSize(1)
+        assertThat(focusTargetAndRotaryNode.events[0].verticalScrollPixels).isEqualTo(100f)
+    }
+
+    /**
+     * Tests that when a node is both a NodeKind.FocusEvent and NodeKind.FocusTarget, the node
+     * receives events on detach.
+     */
+    @Test
+    fun focusEventNodeDelegatingToFocusTarget_invalidatedOnRemoval() {
+        var composeFocusableBox by mutableStateOf(true)
+        val focusTargetNode = FocusTargetNode()
+
+        class FocusEventAndFocusTargetNode : DelegatingNode(), FocusEventModifierNode {
+            val focusStates = mutableListOf<FocusState>()
+            override fun onFocusEvent(focusState: FocusState) {
+                focusStates.add(focusState)
+            }
+            init {
+                delegate(focusTargetNode)
+            }
+        }
+
+        val focusEventAndFocusTargetNode = FocusEventAndFocusTargetNode()
+        val focusEventAndFocusTargetModifier = elementFor(key1 = null, focusEventAndFocusTargetNode)
+
+        val focusRequester = FocusRequester()
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            if (composeFocusableBox) {
+                Box(
+                    modifier = Modifier
+                        .focusRequester(focusRequester)
+                        .then(focusEventAndFocusTargetModifier)
+                )
+            }
+        }
+
+        assertThat(focusEventAndFocusTargetNode.focusStates).hasSize(1)
+        assertThat(focusEventAndFocusTargetNode.focusStates[0].isFocused).isFalse()
+
+        rule.runOnUiThread { focusRequester.requestFocus() }
+        rule.waitForIdle()
+
+        assertThat(focusEventAndFocusTargetNode.focusStates).hasSize(2)
+        assertThat(focusEventAndFocusTargetNode.focusStates[0].isFocused).isFalse()
+        assertThat(focusEventAndFocusTargetNode.focusStates[1].isFocused).isTrue()
+
+        composeFocusableBox = false
+        rule.waitForIdle()
+
+        assertThat(focusEventAndFocusTargetNode.focusStates).hasSize(3)
+        assertThat(focusEventAndFocusTargetNode.focusStates[0].isFocused).isFalse()
+        assertThat(focusEventAndFocusTargetNode.focusStates[1].isFocused).isTrue()
+        assertThat(focusEventAndFocusTargetNode.focusStates[2].isFocused).isFalse()
+    }
+
+    @Test
+    fun modifierDelegatingToFocusEventAndFocusTarget_invalidatedOnRemoval() {
+        var composeFocusableBox by mutableStateOf(true)
+
+        class MyFocusEventNode : Modifier.Node(), FocusEventModifierNode {
+            val focusStates = mutableListOf<FocusState>()
+            override fun onFocusEvent(focusState: FocusState) {
+                focusStates.add(focusState)
+            }
+        }
+
+        val eventNode = MyFocusEventNode()
+        val focusTargetNode = FocusTargetNode()
+
+        class FocusEventAndFocusTargetNode : DelegatingNode() {
+            init {
+                delegate(focusTargetNode)
+                delegate(eventNode)
+            }
+        }
+
+        val focusEventAndFocusTargetNode = FocusEventAndFocusTargetNode()
+        val focusEventAndFocusTargetModifier = elementFor(key1 = null, focusEventAndFocusTargetNode)
+
+        val focusRequester = FocusRequester()
+
+        rule.setFocusableContent(extraItemForInitialFocus = false) {
+            if (composeFocusableBox) {
+                Box(modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .then(focusEventAndFocusTargetModifier)
+                )
+            }
+        }
+
+        assertThat(eventNode.focusStates).hasSize(1)
+        assertThat(eventNode.focusStates[0].isFocused).isFalse()
+
+        rule.runOnUiThread { focusRequester.requestFocus() }
+        rule.waitForIdle()
+
+        assertThat(eventNode.focusStates).hasSize(2)
+        assertThat(eventNode.focusStates[0].isFocused).isFalse()
+        assertThat(eventNode.focusStates[1].isFocused).isTrue()
+
+        composeFocusableBox = false
+        rule.waitForIdle()
+
+        assertThat(eventNode.focusStates).hasSize(3)
+        assertThat(eventNode.focusStates[0].isFocused).isFalse()
+        assertThat(eventNode.focusStates[1].isFocused).isTrue()
+        assertThat(eventNode.focusStates[2].isFocused).isFalse()
+    }
+
     private inline fun Modifier.thenIf(condition: Boolean, block: () -> Modifier): Modifier {
         return if (condition) then(block()) else this
     }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
index 94119f3..b20ed840 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/AndroidPointerInputTest.kt
@@ -58,6 +58,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.OpenComposeView
+import androidx.compose.ui.background
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.draw.scale
@@ -65,6 +66,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.gesture.PointerCoords
 import androidx.compose.ui.gesture.PointerProperties
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.LayoutCoordinates
@@ -2267,6 +2269,1050 @@
     }
 
     /*
+     * Tests TOUCH events are triggered correctly when dynamically adding a NON-pointer input
+     * modifier above an existing pointer input modifier.
+     *
+     * Note: The lambda for the existing pointer input modifier is not re-executed after the
+     * dynamic one is added.
+     *
+     * Specific events:
+     *  1. UI Element (modifier 1 only): PRESS (touch)
+     *  2. UI Element (modifier 1 only): MOVE (touch)
+     *  3. UI Element (modifier 1 only): RELEASE (touch)
+     *  4. Dynamically adds NON-pointer input modifier (between input event streams)
+     *  5. UI Element (modifier 1 and 2): PRESS (touch)
+     *  6. UI Element (modifier 1 and 2): MOVE (touch)
+     *  7. UI Element (modifier 1 and 2): RELEASE (touch)
+     */
+    @Test
+    fun dynamicNonInputModifier_addsAboveExistingModifier_shouldTriggerInNewModifier() {
+        // --> Arrange
+        val originalPointerInputModifierKey = "ORIGINAL_POINTER_INPUT_MODIFIER_KEY_123"
+        var box1LayoutCoordinates: LayoutCoordinates? = null
+
+        val setUpFinishedLatch = CountDownLatch(1)
+
+        var enableDynamicPointerInput by mutableStateOf(false)
+
+        // Events for the lower modifier Box 1
+        var originalPointerInputScopeExecutionCount by mutableStateOf(0)
+        var preexistingModifierPress by mutableStateOf(0)
+        var preexistingModifierMove by mutableStateOf(0)
+        var preexistingModifierRelease by mutableStateOf(0)
+
+        // All other events that should never be triggered in this test
+        var eventsThatShouldNotTrigger by mutableStateOf(false)
+
+        var pointerEvent: PointerEvent? by mutableStateOf(null)
+
+        // Events for the dynamic upper modifier Box 1
+        var dynamicModifierExecuted by mutableStateOf(false)
+
+        // Non-Pointer Input Modifier that is toggled on/off based on passed value.
+        fun Modifier.dynamicallyToggledModifier(enable: Boolean) = if (enable) {
+            dynamicModifierExecuted = true
+            background(Color.Green)
+        } else this
+
+        // Setup UI
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier
+                        .size(200.dp)
+                        .onGloballyPositioned {
+                            box1LayoutCoordinates = it
+                            setUpFinishedLatch.countDown()
+                        }
+                        .dynamicallyToggledModifier(enableDynamicPointerInput)
+                        .pointerInput(originalPointerInputModifierKey) {
+                            ++originalPointerInputScopeExecutionCount
+                            // Reset pointer events when lambda is ran the first time
+                            preexistingModifierPress = 0
+                            preexistingModifierMove = 0
+                            preexistingModifierRelease = 0
+
+                            awaitPointerEventScope {
+                                while (true) {
+                                    pointerEvent = awaitPointerEvent()
+                                    when (pointerEvent!!.type) {
+                                        PointerEventType.Press -> {
+                                            ++preexistingModifierPress
+                                        }
+                                        PointerEventType.Move -> {
+                                            ++preexistingModifierMove
+                                        }
+                                        PointerEventType.Release -> {
+                                            ++preexistingModifierRelease
+                                        }
+                                        else -> {
+                                            eventsThatShouldNotTrigger = true
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                ) { }
+            }
+        }
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // --> Act + Assert (interwoven)
+        // DOWN (original modifier only)
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicModifierExecuted).isEqualTo(false)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(0)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original modifier only)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicModifierExecuted).isEqualTo(false)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original modifier only)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicModifierExecuted).isEqualTo(false)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+        enableDynamicPointerInput = true
+        rule.waitForFutureFrame(2)
+
+        // DOWN (original + dynamically added modifiers)
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+
+        rule.runOnUiThread {
+            // There are no pointer input modifiers added above this pointer modifier, so the
+            // same one is used.
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            // The dynamic one has been added, so we execute its thing as well.
+            assertThat(dynamicModifierExecuted).isEqualTo(true)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(2)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicModifierExecuted).isEqualTo(true)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(2)
+            assertThat(preexistingModifierMove).isEqualTo(2)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicModifierExecuted).isEqualTo(true)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(2)
+            assertThat(preexistingModifierMove).isEqualTo(2)
+            assertThat(preexistingModifierRelease).isEqualTo(2)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+    }
+
+    /*
+     * Tests TOUCH events are triggered correctly when dynamically adding a pointer input modifier
+     * ABOVE an existing pointer input modifier.
+     *
+     * Note: The lambda for the existing pointer input modifier **IS** re-executed after the
+     * dynamic pointer input modifier is added above it.
+     *
+     * Specific events:
+     *  1. UI Element (modifier 1 only): PRESS (touch)
+     *  2. UI Element (modifier 1 only): MOVE (touch)
+     *  3. UI Element (modifier 1 only): RELEASE (touch)
+     *  4. Dynamically add pointer input modifier above existing one (between input event streams)
+     *  5. UI Element (modifier 1 and 2): PRESS (touch)
+     *  6. UI Element (modifier 1 and 2): MOVE (touch)
+     *  7. UI Element (modifier 1 and 2): RELEASE (touch)
+     */
+    @Test
+    fun dynamicInputModifierWithKey_addsAboveExistingModifier_shouldTriggerInNewModifier() {
+        // --> Arrange
+        val originalPointerInputModifierKey = "ORIGINAL_POINTER_INPUT_MODIFIER_KEY_123"
+        var box1LayoutCoordinates: LayoutCoordinates? = null
+
+        val setUpFinishedLatch = CountDownLatch(1)
+
+        var enableDynamicPointerInput by mutableStateOf(false)
+
+        // Events for the lower modifier Box 1
+        var originalPointerInputScopeExecutionCount by mutableStateOf(0)
+        var preexistingModifierPress by mutableStateOf(0)
+        var preexistingModifierMove by mutableStateOf(0)
+        var preexistingModifierRelease by mutableStateOf(0)
+
+        // Events for the dynamic upper modifier Box 1
+        var dynamicPointerInputScopeExecutionCount by mutableStateOf(0)
+        var dynamicModifierPress by mutableStateOf(0)
+        var dynamicModifierMove by mutableStateOf(0)
+        var dynamicModifierRelease by mutableStateOf(0)
+
+        // All other events that should never be triggered in this test
+        var eventsThatShouldNotTrigger by mutableStateOf(false)
+
+        var pointerEvent: PointerEvent? by mutableStateOf(null)
+
+        // Pointer Input Modifier that is toggled on/off based on passed value.
+        fun Modifier.dynamicallyToggledPointerInput(
+            enable: Boolean,
+            pointerEventLambda: (pointerEvent: PointerEvent) -> Unit
+        ) = if (enable) {
+            pointerInput(pointerEventLambda) {
+                ++dynamicPointerInputScopeExecutionCount
+
+                // Reset pointer events when lambda is ran the first time
+                dynamicModifierPress = 0
+                dynamicModifierMove = 0
+                dynamicModifierRelease = 0
+
+                awaitPointerEventScope {
+                    while (true) {
+                        pointerEventLambda(awaitPointerEvent())
+                    }
+                }
+            }
+        } else this
+
+        // Setup UI
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier
+                        .size(200.dp)
+                        .onGloballyPositioned {
+                            box1LayoutCoordinates = it
+                            setUpFinishedLatch.countDown()
+                        }
+                        .dynamicallyToggledPointerInput(enableDynamicPointerInput) {
+                            when (it.type) {
+                                PointerEventType.Press -> {
+                                    ++dynamicModifierPress
+                                }
+                                PointerEventType.Move -> {
+                                    ++dynamicModifierMove
+                                }
+                                PointerEventType.Release -> {
+                                    ++dynamicModifierRelease
+                                }
+                                else -> {
+                                    eventsThatShouldNotTrigger = true
+                                }
+                            }
+                        }
+                        .pointerInput(originalPointerInputModifierKey) {
+                            ++originalPointerInputScopeExecutionCount
+                            // Reset pointer events when lambda is ran the first time
+                            preexistingModifierPress = 0
+                            preexistingModifierMove = 0
+                            preexistingModifierRelease = 0
+
+                            awaitPointerEventScope {
+                                while (true) {
+                                    pointerEvent = awaitPointerEvent()
+                                    when (pointerEvent!!.type) {
+                                        PointerEventType.Press -> {
+                                            ++preexistingModifierPress
+                                        }
+                                        PointerEventType.Move -> {
+                                            ++preexistingModifierMove
+                                        }
+                                        PointerEventType.Release -> {
+                                            ++preexistingModifierRelease
+                                        }
+                                        else -> {
+                                            eventsThatShouldNotTrigger = true
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                ) { }
+            }
+        }
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // --> Act + Assert (interwoven)
+        // DOWN (original modifier only)
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(0)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original modifier only)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original modifier only)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        enableDynamicPointerInput = true
+        rule.waitForFutureFrame(2)
+
+        // Important Note: Even though we reset all the pointer input blocks, the initial lambda is
+        // lazily executed, meaning it won't reset the values until the first event comes in, so
+        // the previously set values are still the same until an event comes in.
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+        }
+
+        // DOWN (original + dynamically added modifiers)
+        // Now an event comes in, so the lambdas are both executed completely (dynamic one for the
+        // first time and the existing one for a second time [since it was moved]).
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+
+        rule.runOnUiThread {
+            // While the original pointer input block is being reused after a new one is added, it
+            // is reset (since things have changed with the Modifiers), so the entire block is
+            // executed again to allow devs to reset their gesture detectors for the new Modifier
+            // chain changes.
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(2)
+            // The dynamic one has been added, so we execute its thing as well.
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(1)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(0)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(1)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(2)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(1)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(1)
+            assertThat(dynamicModifierMove).isEqualTo(1)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(2)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(1)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(1)
+            assertThat(dynamicModifierMove).isEqualTo(1)
+            assertThat(dynamicModifierRelease).isEqualTo(1)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+    }
+
+    /*
+     * Tests TOUCH events are triggered incorrectly when dynamically adding a pointer input modifier
+     * (which uses Unit for its key [bad]) ABOVE an existing pointer input modifier. This is more
+     * of an "education test" for developers to see how things can go wrong if you use "Unit" for
+     * your key in pointer input and pointer input modifiers are later added dynamically.
+     *
+     * Note: Even though we are dynamically adding a new pointer input modifier above the existing
+     * pointer input modifier, Compose actually reuses the existing pointer input modifier to
+     * contain the new pointer input modifier. It then adds a new pointer input modifier below that
+     * one and copies in the original (non-dynamic) pointer input modifier into that. However, in
+     * this case, because we are using the "Unit" for both keys, Compose thinks they are the same
+     * pointer input modifier, so it never replaces the existing lambda with the dynamic pointer
+     * input modifier node's lambda. This is why you should not use Unit for your key.
+     *
+     * Why can't the lambdas passed into pointer input be compared? We can't memoize them because
+     * they are outside of a Compose scope (defined in a Modifier extension function), so
+     * developers need to pass a unique key(s) as a way to let us know when to update the lambda.
+     * You can do that with a unique key for each pointer input modifier and/or take it a step
+     * further and use captured values in the lambda as keys (ones that change lambda
+     * behavior).
+     *
+     * Specific events:
+     *  1. UI Element (modifier 1 only): PRESS (touch)
+     *  2. UI Element (modifier 1 only): MOVE (touch)
+     *  3. UI Element (modifier 1 only): RELEASE (touch)
+     *  4. Dynamically add pointer input modifier above existing one (between input event streams)
+     *  5. UI Element (modifier 1 and 2): PRESS (touch)
+     *  6. UI Element (modifier 1 and 2): MOVE (touch)
+     *  7. UI Element (modifier 1 and 2): RELEASE (touch)
+     */
+    @Test
+    fun dynamicInputModifierWithUnitKey_addsAboveExistingModifier_failsToTriggerNewModifier() {
+        // --> Arrange
+        var box1LayoutCoordinates: LayoutCoordinates? = null
+
+        val setUpFinishedLatch = CountDownLatch(1)
+
+        var enableDynamicPointerInput by mutableStateOf(false)
+
+        // Events for the lower modifier Box 1
+        var originalPointerInputScopeExecutionCount by mutableStateOf(0)
+        var preexistingModifierPress by mutableStateOf(0)
+        var preexistingModifierMove by mutableStateOf(0)
+        var preexistingModifierRelease by mutableStateOf(0)
+
+        // Events for the dynamic upper modifier Box 1
+        var dynamicPointerInputScopeExecutionCount by mutableStateOf(0)
+        var dynamicModifierPress by mutableStateOf(0)
+        var dynamicModifierMove by mutableStateOf(0)
+        var dynamicModifierRelease by mutableStateOf(0)
+
+        // All other events that should never be triggered in this test
+        var eventsThatShouldNotTrigger by mutableStateOf(false)
+
+        var pointerEvent: PointerEvent? by mutableStateOf(null)
+
+        // Pointer Input Modifier that is toggled on/off based on passed value.
+        fun Modifier.dynamicallyToggledPointerInput(
+            enable: Boolean,
+            pointerEventLambda: (pointerEvent: PointerEvent) -> Unit
+        ) = if (enable) {
+            pointerInput(Unit) {
+                ++dynamicPointerInputScopeExecutionCount
+
+                // Reset pointer events when lambda is ran the first time
+                dynamicModifierPress = 0
+                dynamicModifierMove = 0
+                dynamicModifierRelease = 0
+
+                awaitPointerEventScope {
+                    while (true) {
+                        pointerEventLambda(awaitPointerEvent())
+                    }
+                }
+            }
+        } else this
+
+        // Setup UI
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier
+                        .size(200.dp)
+                        .onGloballyPositioned {
+                            box1LayoutCoordinates = it
+                            setUpFinishedLatch.countDown()
+                        }
+                        .dynamicallyToggledPointerInput(enableDynamicPointerInput) {
+                            when (it.type) {
+                                PointerEventType.Press -> {
+                                    ++dynamicModifierPress
+                                }
+                                PointerEventType.Move -> {
+                                    ++dynamicModifierMove
+                                }
+                                PointerEventType.Release -> {
+                                    ++dynamicModifierRelease
+                                }
+                                else -> {
+                                    eventsThatShouldNotTrigger = true
+                                }
+                            }
+                        }
+                        .pointerInput(Unit) {
+                            ++originalPointerInputScopeExecutionCount
+                            awaitPointerEventScope {
+                                while (true) {
+                                    pointerEvent = awaitPointerEvent()
+                                    when (pointerEvent!!.type) {
+                                        PointerEventType.Press -> {
+                                            ++preexistingModifierPress
+                                        }
+                                        PointerEventType.Move -> {
+                                            ++preexistingModifierMove
+                                        }
+                                        PointerEventType.Release -> {
+                                            ++preexistingModifierRelease
+                                        }
+                                        else -> {
+                                            eventsThatShouldNotTrigger = true
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                ) { }
+            }
+        }
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // --> Act + Assert (interwoven)
+        // DOWN (original modifier only)
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(0)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original modifier only)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original modifier only)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        enableDynamicPointerInput = true
+        rule.waitForFutureFrame(2)
+
+        // Important Note: I'm not resetting the variable counters in this test.
+
+        // DOWN (original + dynamically added modifiers)
+        // Now an event comes in, so the lambdas are both executed completely for the first time.
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+
+        rule.runOnUiThread {
+            // While the original pointer input block is being reused after a new one is added, it
+            // is reset (since things have changed with the Modifiers), so the entire block is
+            // executed again to allow devs to reset their gesture detectors for the new Modifier
+            // chain changes.
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(2)
+            // The dynamic one has been added, so we execute its thing as well.
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            // This is 2 because the dynamic modifier added before the existing one, is using Unit
+            // for the key, so the comparison shows that it doesn't need to update the lambda...
+            // Thus, it uses the old lambda (why it is very important you don't use Unit for your
+            // key.
+            assertThat(preexistingModifierPress).isEqualTo(3)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(2)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(3)
+            assertThat(preexistingModifierMove).isEqualTo(3)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(2)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(3)
+            assertThat(preexistingModifierMove).isEqualTo(3)
+            assertThat(preexistingModifierRelease).isEqualTo(3)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+    }
+
+    /*
+     * Tests TOUCH events are triggered correctly when dynamically adding a pointer input
+     * modifier BELOW an existing pointer input modifier.
+     *
+     * Note: The lambda for the existing pointer input modifier is NOT re-executed after the
+     * dynamic one is added below it (since it doesn't impact it).
+     *
+     * Specific events:
+     *  1. UI Element (modifier 1 only): PRESS (touch)
+     *  2. UI Element (modifier 1 only): MOVE (touch)
+     *  3. UI Element (modifier 1 only): RELEASE (touch)
+     *  4. Dynamically add pointer input modifier below existing one (between input event streams)
+     *  5. UI Element (modifier 1 and 2): PRESS (touch)
+     *  6. UI Element (modifier 1 and 2): MOVE (touch)
+     *  7. UI Element (modifier 1 and 2): RELEASE (touch)
+     */
+    @Test
+    fun dynamicInputModifierWithKey_addsBelowExistingModifier_shouldTriggerInNewModifier() {
+        // --> Arrange
+        val originalPointerInputModifierKey = "ORIGINAL_POINTER_INPUT_MODIFIER_KEY_123"
+        var box1LayoutCoordinates: LayoutCoordinates? = null
+
+        val setUpFinishedLatch = CountDownLatch(1)
+
+        var enableDynamicPointerInput by mutableStateOf(false)
+
+        // Events for the lower modifier Box 1
+        var originalPointerInputScopeExecutionCount by mutableStateOf(0)
+        var preexistingModifierPress by mutableStateOf(0)
+        var preexistingModifierMove by mutableStateOf(0)
+        var preexistingModifierRelease by mutableStateOf(0)
+
+        // Events for the dynamic upper modifier Box 1
+        var dynamicPointerInputScopeExecutionCount by mutableStateOf(0)
+        var dynamicModifierPress by mutableStateOf(0)
+        var dynamicModifierMove by mutableStateOf(0)
+        var dynamicModifierRelease by mutableStateOf(0)
+
+        // All other events that should never be triggered in this test
+        var eventsThatShouldNotTrigger by mutableStateOf(false)
+
+        var pointerEvent: PointerEvent? by mutableStateOf(null)
+
+        // Pointer Input Modifier that is toggled on/off based on passed value.
+        fun Modifier.dynamicallyToggledPointerInput(
+            enable: Boolean,
+            pointerEventLambda: (pointerEvent: PointerEvent) -> Unit
+        ) = if (enable) {
+            pointerInput(pointerEventLambda) {
+                ++dynamicPointerInputScopeExecutionCount
+
+                // Reset pointer events when lambda is ran the first time
+                dynamicModifierPress = 0
+                dynamicModifierMove = 0
+                dynamicModifierRelease = 0
+
+                awaitPointerEventScope {
+                    while (true) {
+                        pointerEventLambda(awaitPointerEvent())
+                    }
+                }
+            }
+        } else this
+
+        // Setup UI
+        rule.runOnUiThread {
+            container.setContent {
+                Box(
+                    Modifier
+                        .size(200.dp)
+                        .onGloballyPositioned {
+                            box1LayoutCoordinates = it
+                            setUpFinishedLatch.countDown()
+                        }
+                        .pointerInput(originalPointerInputModifierKey) {
+                            ++originalPointerInputScopeExecutionCount
+                            // Reset pointer events when lambda is ran the first time
+                            preexistingModifierPress = 0
+                            preexistingModifierMove = 0
+                            preexistingModifierRelease = 0
+
+                            awaitPointerEventScope {
+                                while (true) {
+                                    pointerEvent = awaitPointerEvent()
+                                    when (pointerEvent!!.type) {
+                                        PointerEventType.Press -> {
+                                            ++preexistingModifierPress
+                                        }
+                                        PointerEventType.Move -> {
+                                            ++preexistingModifierMove
+                                        }
+                                        PointerEventType.Release -> {
+                                            ++preexistingModifierRelease
+                                        }
+                                        else -> {
+                                            eventsThatShouldNotTrigger = true
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        .dynamicallyToggledPointerInput(enableDynamicPointerInput) {
+                            when (it.type) {
+                                PointerEventType.Press -> {
+                                    ++dynamicModifierPress
+                                }
+                                PointerEventType.Move -> {
+                                    ++dynamicModifierMove
+                                }
+                                PointerEventType.Release -> {
+                                    ++dynamicModifierRelease
+                                }
+                                else -> {
+                                    eventsThatShouldNotTrigger = true
+                                }
+                            }
+                        }
+                ) { }
+            }
+        }
+        // Ensure Arrange (setup) step is finished
+        assertTrue(setUpFinishedLatch.await(2, TimeUnit.SECONDS))
+
+        // --> Act + Assert (interwoven)
+        // DOWN (original modifier only)
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(0)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original modifier only)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(0)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original modifier only)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(0)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(1)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(0)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        enableDynamicPointerInput = true
+        rule.waitForFutureFrame(2)
+
+        // DOWN (original + dynamically added modifiers)
+        dispatchTouchEvent(ACTION_DOWN, box1LayoutCoordinates!!)
+
+        rule.runOnUiThread {
+            // Because the new pointer input modifier is added below the existing one, the existing
+            // one doesn't change.
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(1)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(2)
+            assertThat(preexistingModifierMove).isEqualTo(1)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(1)
+            assertThat(dynamicModifierMove).isEqualTo(0)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // MOVE (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_MOVE,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(1)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(2)
+            assertThat(preexistingModifierMove).isEqualTo(2)
+            assertThat(preexistingModifierRelease).isEqualTo(1)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(1)
+            assertThat(dynamicModifierMove).isEqualTo(1)
+            assertThat(dynamicModifierRelease).isEqualTo(0)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+
+        // UP (original + dynamically added modifiers)
+        dispatchTouchEvent(
+            ACTION_UP,
+            box1LayoutCoordinates!!,
+            Offset(0f, box1LayoutCoordinates!!.size.height / 2 - 1f)
+        )
+        rule.runOnUiThread {
+            assertThat(originalPointerInputScopeExecutionCount).isEqualTo(1)
+            assertThat(dynamicPointerInputScopeExecutionCount).isEqualTo(1)
+
+            // Verify Box 1 existing modifier events
+            assertThat(preexistingModifierPress).isEqualTo(2)
+            assertThat(preexistingModifierMove).isEqualTo(2)
+            assertThat(preexistingModifierRelease).isEqualTo(2)
+
+            // Verify Box 1 dynamically added modifier events
+            assertThat(dynamicModifierPress).isEqualTo(1)
+            assertThat(dynamicModifierMove).isEqualTo(1)
+            assertThat(dynamicModifierRelease).isEqualTo(1)
+
+            assertThat(pointerEvent).isNotNull()
+            assertThat(eventsThatShouldNotTrigger).isFalse()
+        }
+    }
+
+    /*
      * Tests a full mouse event cycle from a press and release.
      *
      * Important Note: The pointer id should stay the same throughout all these events (part of the
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
index 8559282..67479b38 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/HitPathTrackerTest.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -3765,7 +3766,8 @@
 
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
-        invalidateParentLayer: () -> Unit
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
     ): OwnedLayer {
         return object : OwnedLayer {
             override fun updateLayerProperties(
@@ -3783,7 +3785,7 @@
             override fun resize(size: IntSize) {
             }
 
-            override fun drawLayer(canvas: Canvas) {
+            override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
                 drawBlock(canvas)
             }
 
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index 71afb4c..b08bb68 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -3420,7 +3421,8 @@
 
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
-        invalidateParentLayer: () -> Unit
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
     ): OwnedLayer {
         TODO("Not yet implemented")
     }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
index 5e0d001..316e860 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/Helpers.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -209,8 +210,11 @@
     override val autofill: Autofill
         get() = TODO("Not yet implemented")
 
-    override fun createLayer(drawBlock: (Canvas) -> Unit, invalidateParentLayer: () -> Unit) =
-        createLayer()
+    override fun createLayer(
+        drawBlock: (Canvas) -> Unit,
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
+    ) = createLayer()
 
     override fun onRequestRelayout(
         layoutNode: LayoutNode,
@@ -581,7 +585,7 @@
     override fun resize(size: IntSize) {
     }
 
-    override fun drawLayer(canvas: Canvas) {
+    override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
     }
 
     override fun updateDisplayList() {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt
index cadfde0..af67ee0 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/OnSizeChangedTest.kt
@@ -216,8 +216,8 @@
     @Test
     @SmallTest
     fun addedModifier() {
-        var latch1 = CountDownLatch(1)
-        var latch2 = CountDownLatch(1)
+        val latch1 = CountDownLatch(1)
+        val latch2 = CountDownLatch(1)
         var changedSize1 = IntSize.Zero
         var changedSize2 = IntSize.Zero
         var addModifier by mutableStateOf(false)
@@ -246,7 +246,6 @@
         assertEquals(10, changedSize1.height)
         assertEquals(10, changedSize1.width)
 
-        latch1 = CountDownLatch(1)
         addModifier = true
 
         // We've added an onSizeChanged modifier, so it must trigger another size change
@@ -257,6 +256,133 @@
 
     @Test
     @SmallTest
+    fun addedModifierNode() {
+        val sizeLatch1 = CountDownLatch(1)
+        val sizeLatch2 = CountDownLatch(1)
+        val placedLatch1 = CountDownLatch(1)
+        val placedLatch2 = CountDownLatch(1)
+        var changedSize1 = IntSize.Zero
+        var changedSize2 = IntSize.Zero
+        var addModifier by mutableStateOf(false)
+
+        val node = object : LayoutAwareModifierNode, Modifier.Node() {
+            override fun onRemeasured(size: IntSize) {
+                changedSize1 = size
+                sizeLatch1.countDown()
+            }
+            override fun onPlaced(coordinates: LayoutCoordinates) {
+                placedLatch1.countDown()
+            }
+        }
+
+        val node2 = object : LayoutAwareModifierNode, Modifier.Node() {
+            override fun onRemeasured(size: IntSize) {
+                changedSize2 = size
+                sizeLatch2.countDown()
+            }
+            override fun onPlaced(coordinates: LayoutCoordinates) {
+                placedLatch2.countDown()
+            }
+        }
+
+        rule.runOnUiThread {
+            activity.setContent {
+                with(LocalDensity.current) {
+                    val mod = if (addModifier) Modifier.elementFor(node2) else Modifier
+                    Box(
+                        Modifier.padding(10.toDp()).elementFor(node).then(mod)
+                    ) {
+                        Box(Modifier.requiredSize(10.toDp()))
+                    }
+                }
+            }
+        }
+
+        // Initial setting will call onRemeasured and onPlaced
+        assertTrue(sizeLatch1.await(1, TimeUnit.SECONDS))
+        assertTrue(placedLatch1.await(1, TimeUnit.SECONDS))
+        assertEquals(10, changedSize1.height)
+        assertEquals(10, changedSize1.width)
+
+        addModifier = true
+
+        // We've added a node, so it must trigger onRemeasured and onPlaced on the new node
+        assertTrue(sizeLatch2.await(1, TimeUnit.SECONDS))
+        assertTrue(placedLatch2.await(1, TimeUnit.SECONDS))
+        assertEquals(10, changedSize2.height)
+        assertEquals(10, changedSize2.width)
+    }
+
+    @Test
+    @SmallTest
+    fun lazilyDelegatedModifierNode() {
+        val sizeLatch1 = CountDownLatch(1)
+        val sizeLatch2 = CountDownLatch(1)
+        val placedLatch1 = CountDownLatch(1)
+        val placedLatch2 = CountDownLatch(1)
+        var changedSize1 = IntSize.Zero
+        var changedSize2 = IntSize.Zero
+
+        val node = object : LayoutAwareModifierNode, Modifier.Node() {
+            override fun onRemeasured(size: IntSize) {
+                changedSize1 = size
+                sizeLatch1.countDown()
+            }
+
+            override fun onPlaced(coordinates: LayoutCoordinates) {
+                placedLatch1.countDown()
+            }
+        }
+
+        val node2 = object : DelegatingNode() {
+            fun addDelegate() {
+                delegate(
+                    object : LayoutAwareModifierNode, Modifier.Node() {
+                        override fun onRemeasured(size: IntSize) {
+                            changedSize2 = size
+                            sizeLatch2.countDown()
+                        }
+
+                        override fun onPlaced(coordinates: LayoutCoordinates) {
+                            placedLatch2.countDown()
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.runOnUiThread {
+            activity.setContent {
+                with(LocalDensity.current) {
+                    val mod = Modifier.elementFor(node2)
+                    Box(
+                        Modifier.padding(10.toDp()).elementFor(node).then(mod)
+                    ) {
+                        Box(Modifier.requiredSize(10.toDp()))
+                    }
+                }
+            }
+        }
+
+        // Initial setting will call onRemeasured and onPlaced
+        assertTrue(sizeLatch1.await(1, TimeUnit.SECONDS))
+        assertTrue(placedLatch1.await(1, TimeUnit.SECONDS))
+        assertEquals(10, changedSize1.height)
+        assertEquals(10, changedSize1.width)
+
+        rule.runOnUiThread {
+            node2.addDelegate()
+        }
+
+        // We've delegated to a node, so it must trigger onRemeasured and onPlaced on the new node
+        assertTrue(sizeLatch2.await(1, TimeUnit.SECONDS))
+        assertTrue(placedLatch2.await(1, TimeUnit.SECONDS))
+        assertEquals(10, changedSize2.height)
+        assertEquals(10, changedSize2.width)
+    }
+
+    @Test
+    @SmallTest
     fun modifierIsReturningEqualObjectForTheSameLambda() {
         val lambda: (IntSize) -> Unit = { }
         assertEquals(Modifier.onSizeChanged(lambda), Modifier.onSizeChanged(lambda))
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index a85b878..95e0379 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -2859,7 +2859,7 @@
     private fun SemanticsNodeInteraction.assertIsDetached() {
         assertDoesNotExist()
         // we want to verify the node is not deactivated, but such API does not exist yet
-        expectAssertionError(true) {
+        expectAssertionError {
             assertIsDeactivated()
         }
     }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
index 51f9876..0b4ff31 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/node/NodeChainTester.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -484,7 +485,8 @@
     }
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
-        invalidateParentLayer: () -> Unit
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
     ): OwnedLayer {
         val transform = Matrix()
         val inverseTransform = Matrix()
@@ -492,7 +494,9 @@
             override fun isInLayer(position: Offset) = true
             override fun move(position: IntOffset) {}
             override fun resize(size: IntSize) {}
-            override fun drawLayer(canvas: Canvas) { drawBlock(canvas) }
+            override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
+                drawBlock(canvas)
+            }
             override fun updateDisplayList() {}
             override fun invalidate() { invalidatedLayers.add(this) }
             override fun destroy() {}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInAppCompatActivityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInAppCompatActivityTest.kt
index 6c8d88b..8ea1a4c 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInAppCompatActivityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInAppCompatActivityTest.kt
@@ -19,8 +19,8 @@
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import java.util.concurrent.CountDownLatch
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInComponentActivityTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInComponentActivityTest.kt
index 9d79d60..73ed014 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInComponentActivityTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInComponentActivityTest.kt
@@ -19,8 +19,8 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import java.util.concurrent.CountDownLatch
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInFragmentTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInFragmentTest.kt
index 497f8be..cae14f2 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInFragmentTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/owners/LifecycleOwnerInFragmentTest.kt
@@ -22,11 +22,11 @@
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
 import androidx.fragment.app.FragmentContainerView
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import java.util.concurrent.CountDownLatch
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/AndroidClipboardManagerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/AndroidClipboardManagerTest.kt
index 0ffc44f..28f3cfc 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/AndroidClipboardManagerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/AndroidClipboardManagerTest.kt
@@ -36,13 +36,14 @@
 import androidx.compose.ui.text.withStyle
 import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
@@ -259,33 +260,6 @@
     }
 
     @Test
-    fun getPrimaryClipDescription_returnsClipDescription() {
-        val clipboardManager = mock<ClipboardManager>()
-        val clipDescription = mock<ClipDescription>()
-        whenever(clipboardManager.primaryClipDescription).thenReturn(clipDescription)
-        val subject = AndroidClipboardManager(clipboardManager)
-
-        assertThat(subject.getClipMetadata()?.clipDescription).isSameInstanceAs(clipDescription)
-        verify(clipboardManager, never()).primaryClip
-    }
-
-    @Test
-    fun hasPrimaryClipEntry_returnsHasClipData() {
-        val clipboardManager = mock<ClipboardManager>()
-        whenever(clipboardManager.hasPrimaryClip()).thenReturn(true)
-        val subject = AndroidClipboardManager(clipboardManager)
-
-        assertThat(subject.hasClip()).isEqualTo(true)
-
-        whenever(clipboardManager.hasPrimaryClip()).thenReturn(false)
-
-        assertThat(subject.hasClip()).isEqualTo(false)
-
-        verify(clipboardManager, never()).primaryClip
-        verify(clipboardManager, never()).primaryClipDescription
-    }
-
-    @Test
     fun setPrimaryClip_callsSetPrimaryClip() {
         val clipboardManager = mock<ClipboardManager>()
         val clipData = mock<ClipData>()
@@ -296,6 +270,35 @@
         verify(clipboardManager, times(1)).setPrimaryClip(clipData)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @Test
+    fun setPrimaryClip_callsClearPrimaryClip_ifNull_above28() {
+        val clipboardManager = mock<ClipboardManager>()
+        val subject = AndroidClipboardManager(clipboardManager)
+
+        subject.setClip(null)
+
+        verify(clipboardManager, times(1)).clearPrimaryClip()
+    }
+
+    @SdkSuppress(maxSdkVersion = 27)
+    @Test
+    fun setPrimaryClip_callsClearPrimaryClip_ifNull_below27() {
+        val clipboardManager = mock<ClipboardManager>()
+        val subject = AndroidClipboardManager(clipboardManager)
+
+        subject.setClip(null)
+
+        val argumentCaptor = argumentCaptor<ClipData>()
+        verify(clipboardManager, times(1))
+            .setPrimaryClip(argumentCaptor.capture())
+
+        assertThat(argumentCaptor.lastValue.itemCount).isEqualTo(1)
+        assertThat(argumentCaptor.lastValue.getItemAt(0).uri).isEqualTo(null)
+        assertThat(argumentCaptor.lastValue.getItemAt(0).intent).isEqualTo(null)
+        assertThat(argumentCaptor.lastValue.getItemAt(0).text).isEqualTo("")
+    }
+
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun firstUriOrNull_returnsFirstItem_ifNotNull() {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/ComposeViewOverlayTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/ComposeViewOverlayTest.kt
index b1c2668..4f703cb 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/ComposeViewOverlayTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/platform/ComposeViewOverlayTest.kt
@@ -29,6 +29,7 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.InternalComposeUiApi
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.test.ext.junit.rules.activityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureDrawTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureDrawTest.kt
new file mode 100644
index 0000000..88b2c1c
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureDrawTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.scrollcapture
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.assertColor
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.graphics.applyCanvas
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class ScrollCaptureDrawTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val captureTester = ScrollCaptureTester(rule)
+
+    @Before
+    fun setUp() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = true
+    }
+
+    @After
+    fun tearDown() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = false
+    }
+
+    @Test
+    fun capture_drawsScrollContents_withCaptureHeight1px() {
+        val scrollState = ScrollState(0)
+        captureTester.setContent {
+            TestContent(scrollState)
+        }
+        val target = captureTester.findCaptureTargets().single()
+        val bitmaps = captureTester.captureBitmapsVertically(target, captureHeight = 1)
+        assertThat(bitmaps).hasSize(27)
+        bitmaps.joinVerticallyToBitmap().use { joined ->
+            joined.assertRect(Rect(0, 0, 10, 9), Color.Red)
+            joined.assertRect(Rect(0, 10, 10, 18), Color.Blue)
+            joined.assertRect(Rect(0, 19, 10, 27), Color.Green)
+        }
+    }
+
+    @Test
+    fun capture_drawsScrollContents_withCaptureHeightFullViewport() {
+        val scrollState = ScrollState(0)
+        captureTester.setContent {
+            TestContent(scrollState)
+        }
+        val target = captureTester.findCaptureTargets().single()
+        val bitmaps = captureTester.captureBitmapsVertically(target, captureHeight = 10)
+        assertThat(bitmaps).hasSize(3)
+        bitmaps.joinVerticallyToBitmap().use { joined ->
+            joined.assertRect(Rect(0, 0, 10, 9), Color.Red)
+            joined.assertRect(Rect(0, 10, 10, 18), Color.Blue)
+            joined.assertRect(Rect(0, 19, 10, 27), Color.Green)
+        }
+    }
+
+    @Test
+    fun capture_resetsScrollPosition_from0() {
+        val scrollState = ScrollState(0)
+        captureTester.setContent {
+            TestContent(scrollState)
+        }
+        val target = captureTester.findCaptureTargets().single()
+        val bitmaps = captureTester.captureBitmapsVertically(target, captureHeight = 10)
+        bitmaps.forEach { it.recycle() }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun capture_resetsScrollPosition_fromNonZero() {
+        val scrollState = ScrollState(5)
+        captureTester.setContent {
+            TestContent(scrollState)
+        }
+        val target = captureTester.findCaptureTargets().single()
+        val bitmaps = captureTester.captureBitmapsVertically(target, captureHeight = 10)
+        bitmaps.forEach { it.recycle() }
+        rule.runOnIdle {
+            assertThat(scrollState.value).isEqualTo(5)
+        }
+    }
+
+    @Composable
+    private fun TestContent(scrollState: ScrollState) {
+        with(LocalDensity.current) {
+            Column(
+                Modifier
+                    .size(10.toDp())
+                    .verticalScroll(scrollState)
+            ) {
+                Box(
+                    Modifier
+                        .background(Color.Red)
+                        .height(9.toDp())
+                        .fillMaxWidth()
+                )
+                Box(
+                    Modifier
+                        .background(Color.Blue)
+                        .height(9.toDp())
+                        .fillMaxWidth()
+                )
+                Box(
+                    Modifier
+                        .background(Color.Green)
+                        .height(9.toDp())
+                        .fillMaxWidth()
+                )
+            }
+        }
+    }
+
+    private inline fun Bitmap.use(block: (Bitmap) -> Unit) {
+        try {
+            block(this)
+        } finally {
+            recycle()
+        }
+    }
+
+    private fun Iterable<Bitmap>.joinVerticallyToBitmap(): Bitmap {
+        val width = maxOf { it.width }
+        val height = sumOf { it.height }
+        val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        var y = 0
+        try {
+            result.applyCanvas {
+                forEach {
+                    drawBitmap(it, 0f, y.toFloat(), null)
+                    y += it.height
+                }
+            }
+        } finally {
+            forEach { it.recycle() }
+        }
+        return result
+    }
+
+    private fun Bitmap.assertRect(rect: Rect, color: Color) {
+        for (x in rect.left until rect.right) {
+            for (y in rect.top until rect.bottom) {
+                assertColor(color, x, y)
+            }
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureIntegrationTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureIntegrationTest.kt
new file mode 100644
index 0000000..c29828b
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureIntegrationTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.scrollcapture
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests real Foundation scrollable components' integration with scroll capture.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class ScrollCaptureIntegrationTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val captureTester = ScrollCaptureTester(rule)
+
+    @Before
+    fun setUp() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = true
+    }
+
+    @After
+    fun tearDown() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = false
+    }
+
+    @Test
+    fun search_findsVerticalScrollModifier() {
+        captureTester.setContent {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .verticalScroll(rememberScrollState())
+                ) {
+                    Box(Modifier.size(100.toDp()))
+                }
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect.width()).isEqualTo(10)
+        assertThat(target.localVisibleRect.height()).isEqualTo(10)
+    }
+
+    @Test
+    fun search_findsLazyColumn() {
+        captureTester.setContent {
+            with(LocalDensity.current) {
+                LazyColumn(Modifier.size(10.toDp())) {
+                    item {
+                        Box(Modifier.size(100.toDp()))
+                    }
+                }
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect.width()).isEqualTo(10)
+        assertThat(target.localVisibleRect.height()).isEqualTo(10)
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureTest.kt
new file mode 100644
index 0000000..bb4d86f
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureTest.kt
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.scrollcapture
+
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.view.ScrollCaptureSession
+import android.view.Surface
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.ScrollAxisRange
+import androidx.compose.ui.semantics.horizontalScrollAxisRange
+import androidx.compose.ui.semantics.invisibleToUser
+import androidx.compose.ui.semantics.scrollByOffset
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.verticalScrollAxisRange
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.roundToInt
+import kotlin.test.fail
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.selects.onTimeout
+import kotlinx.coroutines.selects.select
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests the scroll capture implementation's integration with semantics. Tests in this class should
+ * not use any scrollable components from Foundation.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class ScrollCaptureTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val captureTester = ScrollCaptureTester(rule)
+
+    @Before
+    fun setUp() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = true
+    }
+
+    @After
+    fun tearDown() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = false
+    }
+
+    @Test
+    fun search_findsScrollableTarget() {
+        lateinit var coordinates: LayoutCoordinates
+        captureTester.setContent {
+            TestVerticalScrollable(
+                size = 10,
+                maxValue = 1f,
+                modifier = Modifier.onPlaced { coordinates = it }
+            )
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.hint).isEqualTo(0)
+        assertThat(target.localVisibleRect).isEqualTo(Rect(0, 0, 10, 10))
+        assertThat(target.positionInWindow)
+            .isEqualTo(coordinates.positionInWindow().roundToPoint())
+        assertThat(target.scrollBounds).isEqualTo(Rect(0, 0, 10, 10))
+    }
+
+    @Test
+    fun search_usesTargetsCoordinates() {
+        lateinit var coordinates: LayoutCoordinates
+        val padding = 15
+        captureTester.setContent {
+            Box(Modifier
+                .onPlaced { coordinates = it }
+                .padding(with(LocalDensity.current) { padding.toDp() })
+            ) {
+                TestVerticalScrollable(
+                    size = 10,
+                    maxValue = 1f,
+                )
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        // Relative to the View, i.e. the root of the composition.
+        assertThat(target.localVisibleRect)
+            .isEqualTo(Rect(padding, padding, padding + 10, padding + 10))
+        assertThat(target.positionInWindow)
+            .isEqualTo(
+                (coordinates.positionInWindow() +
+                    Offset(padding.toFloat(), padding.toFloat())
+                    ).roundToPoint()
+            )
+        assertThat(target.scrollBounds)
+            .isEqualTo(Rect(padding, padding, padding + 10, padding + 10))
+    }
+
+    @Test
+    fun search_findsLargestTarget_whenMultipleMatches() {
+        val smallerSize = 10
+        val largerSize = 11
+        captureTester.setContent {
+            Column {
+                TestVerticalScrollable(size = smallerSize)
+                TestVerticalScrollable(size = largerSize)
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect)
+            .isEqualTo(Rect(0, smallerSize, largerSize, smallerSize + largerSize))
+    }
+
+    @Test
+    fun search_findsDeepestTarget() {
+        captureTester.setContent {
+            TestVerticalScrollable(size = 11) {
+                TestVerticalScrollable(size = 10)
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect).isEqualTo(Rect(0, 0, 10, 10))
+    }
+
+    @Test
+    fun search_findsDeepestTarget_whenLargerParentSibling() {
+        captureTester.setContent {
+            Column {
+                TestVerticalScrollable(size = 10) {
+                    TestVerticalScrollable(size = 9)
+                }
+                TestVerticalScrollable(size = 11)
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect).isEqualTo(Rect(0, 0, 9, 9))
+    }
+
+    @Test
+    fun search_findsDeepestLargestTarget_whenMultipleMatches() {
+        captureTester.setContent {
+            Column {
+                TestVerticalScrollable(size = 10) {
+                    TestVerticalScrollable(size = 9)
+                }
+                TestVerticalScrollable(size = 10) {
+                    TestVerticalScrollable(size = 8)
+                }
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect).isEqualTo(Rect(0, 0, 9, 9))
+    }
+
+    @Test
+    fun search_usesClippedSize() {
+        captureTester.setContent {
+            TestVerticalScrollable(size = 10) {
+                TestVerticalScrollable(size = 100)
+            }
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).hasSize(1)
+        val target = targets.single()
+        assertThat(target.localVisibleRect).isEqualTo(Rect(0, 0, 10, 10))
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenFeatureFlagDisabled() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = false
+
+        captureTester.setContent {
+            TestVerticalScrollable()
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenInvisibleToUser() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = false
+
+        captureTester.setContent {
+            TestVerticalScrollable(Modifier.semantics {
+                invisibleToUser()
+            })
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenZeroSize() {
+        @Suppress("DEPRECATION")
+        ComposeFeatureFlag_LongScreenshotsEnabled = false
+
+        captureTester.setContent {
+            TestVerticalScrollable(Modifier.size(0.dp))
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenZeroMaxValue() {
+        captureTester.setContent {
+            TestVerticalScrollable(maxValue = 0f)
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenNoScrollAxisRange() {
+        captureTester.setContent {
+            Box(
+                Modifier
+                    .size(10.dp)
+                    .semantics {
+                        scrollByOffset { Offset.Zero }
+                    }
+            )
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenNoVerticalScrollAxisRange() {
+        captureTester.setContent {
+            Box(
+                Modifier
+                    .size(10.dp)
+                    .semantics {
+                        scrollByOffset { Offset.Zero }
+                        horizontalScrollAxisRange = ScrollAxisRange(
+                            value = { 0f },
+                            maxValue = { 1f },
+                        )
+                    }
+            )
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun search_doesNotFindTarget_whenNoScrollByImmediately() {
+        captureTester.setContent {
+            Box(
+                Modifier
+                    .size(10.dp)
+                    .semantics {
+                        verticalScrollAxisRange = ScrollAxisRange(
+                            value = { 0f },
+                            maxValue = { 1f },
+                        )
+                    }
+            )
+        }
+
+        val targets = captureTester.findCaptureTargets()
+        assertThat(targets).isEmpty()
+    }
+
+    @Test
+    fun callbackOnSearch_returnsViewportBounds() = runTest {
+        lateinit var coordinates: LayoutCoordinates
+        val padding = 15
+        captureTester.setContent {
+            Box(Modifier
+                .onPlaced { coordinates = it }
+                .padding(with(LocalDensity.current) { padding.toDp() })
+            ) {
+                TestVerticalScrollable(
+                    size = 10,
+                    maxValue = 1f,
+                )
+            }
+        }
+
+        val callback = captureTester.findCaptureTargets().single().callback
+
+        launch {
+            val result = callback.onScrollCaptureSearch()
+
+            // Search result is in window coordinates.
+            assertThat(result).isEqualTo(
+                Rect(
+                    coordinates.positionInWindow().x.roundToInt() + padding,
+                    coordinates.positionInWindow().y.roundToInt() + padding,
+                    coordinates.positionInWindow().x.roundToInt() + padding + 10,
+                    coordinates.positionInWindow().y.roundToInt() + padding + 10
+                )
+            )
+        }
+    }
+
+    // TODO this is flaky, figure out why
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun callbackOnImageCapture_scrollsBackwardsThenForwards() = runTest {
+        data class ScrollRequest(
+            val requestedOffset: Offset,
+            val consumeScroll: (Offset) -> Unit
+        )
+
+        val scrollRequests = Channel<ScrollRequest>(capacity = Channel.RENDEZVOUS)
+        suspend fun expectScrollRequest(expectedOffset: Offset, consume: Offset = expectedOffset) {
+            val request = select {
+                scrollRequests.onReceive { it }
+                onTimeout(1000) { fail("No scroll request received after 1000ms") }
+            }
+            assertThat(request.requestedOffset).isEqualTo(expectedOffset)
+            request.consumeScroll(consume)
+            // Allow the scroll request to be consumed.
+            rule.awaitIdle()
+        }
+
+        suspend fun expectNoScrollRequests() {
+            rule.awaitIdle()
+            if (!scrollRequests.isEmpty) {
+                val requests = buildList {
+                    do {
+                        val request = scrollRequests.tryReceive()
+                        request.getOrNull()?.let(::add)
+                    } while (request.isSuccess)
+                }
+                fail("Expected no scroll requests, but had ${requests.size}: " +
+                    requests.joinToString { it.requestedOffset.toString() })
+            }
+        }
+
+        val size = 10
+        captureTester.setContent {
+            TestVerticalScrollable(
+                size = size,
+                onScrollByImmediately = { offset ->
+                    val result = CompletableDeferred<Offset>(parent = currentCoroutineContext().job)
+                    scrollRequests.send(ScrollRequest(offset, consumeScroll = result::complete))
+                    result.await()
+                }
+            )
+        }
+
+        val callback = captureTester.findCaptureTargets().single().callback
+        val canvas = mock<Canvas>()
+        val surface = mock<Surface> {
+            on(it.lockHardwareCanvas()).thenReturn(canvas)
+        }
+        val session = mock<ScrollCaptureSession> {
+            on(it.surface).thenReturn(surface)
+        }
+
+        launch {
+            callback.onScrollCaptureStart(session)
+
+            // First request is at origin, no scrolling required.
+            async { callback.onScrollCaptureImageRequest(session, Rect(0, 0, 10, 10)) }
+                .let { captureResult ->
+                    expectNoScrollRequests()
+                    assertThat(captureResult.await()).isEqualTo(Rect(0, 0, 10, 10))
+                }
+
+            // Back one half-page, but only respond to part of it.
+            async { callback.onScrollCaptureImageRequest(session, Rect(0, -5, 10, 0)) }
+                .let { captureResult ->
+                    expectScrollRequest(Offset(0f, -5f), consume = Offset(0f, -4f))
+                    assertThat(captureResult.await()).isEqualTo(Rect(0, -4, 10, 0))
+                }
+
+            // Forward one half-page – already in viewport, no scrolling required.
+            async { callback.onScrollCaptureImageRequest(session, Rect(0, 0, 10, 5)) }
+                .let { captureResult ->
+                    expectNoScrollRequests()
+                    assertThat(captureResult.await()).isEqualTo(Rect(0, 0, 10, 5))
+                }
+
+            // Forward another half-page. This time we need to scroll.
+            async { callback.onScrollCaptureImageRequest(session, Rect(0, 5, 10, 10)) }
+                .let { captureResult ->
+                    expectScrollRequest(Offset(0f, 4f))
+                    assertThat(captureResult.await()).isEqualTo(Rect(0, 5, 10, 10))
+                }
+
+            // Forward another half-page, scroll again so now we're past the original viewport.
+            async { callback.onScrollCaptureImageRequest(session, Rect(0, 10, 10, 15)) }
+                .let { captureResult ->
+                    expectScrollRequest(Offset(0f, 5f))
+                    assertThat(captureResult.await()).isEqualTo(Rect(0, 10, 10, 15))
+                }
+
+            launch { callback.onScrollCaptureEnd() }
+            // One last scroll request to reset to original offset.
+            expectScrollRequest(Offset(0f, -5f))
+            expectNoScrollRequests()
+        }
+    }
+
+    /**
+     * A component that publishes all the right semantics to be considered a scrollable.
+     */
+    @Composable
+    private fun TestVerticalScrollable(
+        modifier: Modifier = Modifier,
+        size: Int = 10,
+        maxValue: Float = 1f,
+        onScrollByImmediately: suspend (Offset) -> Offset = { Offset.Zero },
+        content: (@Composable () -> Unit)? = null
+    ) {
+        with(LocalDensity.current) {
+            val updatedMaxValue by rememberUpdatedState(maxValue)
+            val scrollAxisRange = remember {
+                ScrollAxisRange(
+                    value = { 0f },
+                    maxValue = { updatedMaxValue },
+                )
+            }
+            Box(
+                modifier
+                    .size(size.toDp())
+                    .semantics {
+                        verticalScrollAxisRange = scrollAxisRange
+                        scrollByOffset(onScrollByImmediately)
+                    },
+                content = { content?.invoke() }
+            )
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureTester.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureTester.kt
new file mode 100644
index 0000000..8c75dc6
--- /dev/null
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/scrollcapture/ScrollCaptureTester.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.scrollcapture
+
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.graphics.ColorSpace
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.graphics.Rect
+import android.hardware.HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
+import android.hardware.HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+import android.media.Image
+import android.media.ImageReader
+import android.os.CancellationSignal
+import android.os.Handler
+import android.os.Looper
+import android.view.ScrollCaptureCallback
+import android.view.ScrollCaptureSession
+import android.view.ScrollCaptureTarget
+import android.view.Surface
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.internal.requirePrecondition
+import androidx.compose.ui.platform.AndroidComposeView
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import kotlin.coroutines.resume
+import kotlin.math.roundToInt
+import kotlin.test.fail
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.selects.onTimeout
+import kotlinx.coroutines.selects.select
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Helps tests pretend to be the Android platform performing scroll capture search and image
+ * capture. Tests must call [setContent] on this class instead of on [rule].
+ */
+@RequiresApi(31)
+class ScrollCaptureTester(private val rule: ComposeContentTestRule) {
+    private var view: View? = null
+    private var coroutineScope: CoroutineScope? = null
+
+    fun setContent(content: @Composable () -> Unit) {
+        rule.setContent {
+            this.view = LocalView.current
+            this.coroutineScope = rememberCoroutineScope()
+            content()
+        }
+    }
+
+    /**
+     * Calls [View.onScrollCaptureSearch] on the Compose host view, which searches the composition
+     * from [setContent] for scroll containers, and returns all the [ScrollCaptureTarget]s produced
+     * that would be given to the platform in production.
+     */
+    fun findCaptureTargets(): List<ScrollCaptureTarget> = rule.runOnIdle {
+        val view = checkNotNull(view as? AndroidComposeView) {
+            "Must call setContent on ScrollCaptureTester before capturing."
+        }
+        val localVisibleRect = Rect().also(view::getLocalVisibleRect)
+        val windowOffset = view.calculatePositionInWindow(Offset.Zero).roundToPoint()
+        val targets = mutableListOf<ScrollCaptureTarget>()
+        view.onScrollCaptureSearch(localVisibleRect, windowOffset, targets::add)
+        targets
+    }
+
+    /**
+     * Emulates (roughly) how the platform interacts with [ScrollCaptureCallback] to iteratively
+     * assemble a screenshot of the entire contents of the [target]. Unlike the platform, this
+     * method will not limit itself to a certain size, it always captures the entire scroll
+     * contents, so tests should make sure to use small enough scroll contents or the test might
+     * run out of memory.
+     *
+     * @param captureHeight The height of the capture window. Must not be greater than viewport
+     * height.
+     */
+    fun captureBitmapsVertically(target: ScrollCaptureTarget, captureHeight: Int): List<Bitmap> {
+        val scope = rule.runOnIdle {
+            checkNotNull(coroutineScope) {
+                "Must call setContent on ScrollCaptureTest before capturing."
+            }
+        }
+        val bitmapsFromTop = mutableListOf<Bitmap>()
+
+        // This coroutine will run on the main thread, no need to use runOnUiThread.
+        val captureJob = scope.launch {
+            runCaptureSession(target, captureHeight, onBitmap = bitmapsFromTop::add)
+        }
+
+        rule.waitUntil(3_000) { captureJob.isCompleted }
+        return bitmapsFromTop
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private suspend fun runCaptureSession(
+        target: ScrollCaptureTarget,
+        captureHeight: Int,
+        onBitmap: (Bitmap) -> Unit
+    ) {
+        val callback = target.callback
+        // Use the bounds returned from the callback, not the ones from the target, because that's
+        // what the system does.
+        val scrollBounds = callback.onScrollCaptureSearch()
+        val captureWidth = scrollBounds.width()
+        requirePrecondition(captureHeight <= scrollBounds.height()) {
+            "Expected windowSize ($captureHeight) ≤ viewport height (${scrollBounds.height()})"
+        }
+
+        withSurfaceBitmaps(captureWidth, captureHeight) { surface, bitmapsFromSurface ->
+            val session = ScrollCaptureSession(
+                surface,
+                scrollBounds,
+                target.positionInWindow
+            )
+            callback.onScrollCaptureStart(session)
+
+            var captureOffset = Point(0, 0)
+            var goingUp = true
+            // Starting with the original viewport, scrolls all the way to the top, then all the way
+            // back down, capturing images on the way down until it hits the bottom.
+            while (true) {
+                val requestedCaptureArea = Rect(
+                    captureOffset.x,
+                    captureOffset.y,
+                    captureOffset.x + captureWidth,
+                    captureOffset.y + captureHeight
+                )
+                val resultCaptureArea =
+                    callback.onScrollCaptureImageRequest(session, requestedCaptureArea)
+
+                // Empty results shouldn't produce an image.
+                if (!resultCaptureArea.isEmpty) {
+                    val bitmap = bitmapsFromSurface.receiveWithTimeout(1_000) {
+                        "No bitmap received after 1 second for capture area $resultCaptureArea"
+                    }
+
+                    // Only collect the returned images on the way down.
+                    if (!goingUp) {
+                        onBitmap(bitmap)
+                    } else {
+                        bitmap.recycle()
+                    }
+                }
+
+                if (resultCaptureArea != requestedCaptureArea) {
+                    // We found the top or bottom.
+                    if (goingUp) {
+                        // "Bounce" off the top: Change direction and start re-capturing down.
+                        goingUp = false
+                        captureOffset = Point(0, resultCaptureArea.top)
+                    } else {
+                        // If we hit the bottom then we're done.
+                        break
+                    }
+                } else {
+                    // We can keep going in the same direction, offset the capture window and loop.
+                    captureOffset = if (goingUp) {
+                        Point(0, resultCaptureArea.top - captureHeight)
+                    } else {
+                        Point(0, resultCaptureArea.bottom)
+                    }
+                }
+            }
+        }
+
+        callback.onScrollCaptureEnd()
+    }
+
+    /**
+     * Creates a [Surface] passes it to [block] along with a channel that will receive all images
+     * written to the [Surface].
+     */
+    private suspend inline fun withSurfaceBitmaps(
+        width: Int,
+        height: Int,
+        crossinline block: suspend (Surface, ReceiveChannel<Bitmap>) -> Unit
+    ) {
+        coroutineScope {
+            // ImageReader gives us the Surface that we'll provide to the session.
+            ImageReader.newInstance(
+                width,
+                height,
+                PixelFormat.RGBA_8888,
+                // Each image is read, processed, and closed before the next request to draw is made,
+                // so we don't need multiple images.
+                /* maxImages= */ 1,
+                USAGE_GPU_SAMPLED_IMAGE or USAGE_GPU_COLOR_OUTPUT
+            ).use { imageReader ->
+                val bitmapsChannel = Channel<Bitmap>(capacity = Channel.RENDEZVOUS)
+
+                // Must register the OnImageAvailableListener before any code in block runs to avoid
+                // race conditions.
+                val imageCollectorJob = launch(start = CoroutineStart.UNDISPATCHED) {
+                    imageReader.collectImages {
+                        val bitmap = it.toSoftwareBitmap()
+                        bitmapsChannel.send(bitmap)
+                    }
+                }
+
+                try {
+                    block(imageReader.surface, bitmapsChannel)
+                    // ImageReader has no signal that it's finished, so in the happy path we have to
+                    // stop the collector job explicitly.
+                    imageCollectorJob.cancel()
+                } finally {
+                    bitmapsChannel.close()
+                }
+            }
+        }
+    }
+
+    /**
+     * Reads all images from this [ImageReader] and passes them to [onImage]. The [Image] will
+     * automatically be closed when [onImage] returns.
+     *
+     * Propagates backpressure to the [ImageReader] – only one image will be acquired from the
+     * [ImageReader] at a time, and the next image won't be acquired until [onImage] returns.
+     */
+    private suspend inline fun ImageReader.collectImages(onImage: (Image) -> Unit): Nothing {
+        val imageAvailableChannel = Channel<Unit>(capacity = Channel.CONFLATED)
+        setOnImageAvailableListener(
+            { imageAvailableChannel.trySend(Unit) },
+            Handler(Looper.getMainLooper())
+        )
+        val context = currentCoroutineContext()
+
+        try {
+            // Read all images until cancelled.
+            while (true) {
+                context.ensureActive()
+                // Fast path – if an image is immediately available, don't suspend.
+                var image: Image? = acquireNextImage()
+                // If no image was available, suspend until the callback fires.
+                while (image == null) {
+                    imageAvailableChannel.receive()
+                    image = acquireNextImage()
+                }
+                image.use { onImage(image) }
+            }
+        } finally {
+            setOnImageAvailableListener(null, null)
+        }
+    }
+
+    /**
+     * Helper function for converting an [Image] to a [Bitmap] by copying the hardware buffer into
+     * a software bitmap.
+     */
+    private fun Image.toSoftwareBitmap(): Bitmap {
+        val hardwareBuffer = checkPreconditionNotNull(hardwareBuffer) { "No hardware buffer" }
+        hardwareBuffer.use {
+            val hardwareBitmap = Bitmap.wrapHardwareBuffer(
+                hardwareBuffer,
+                ColorSpace.get(ColorSpace.Named.SRGB)
+            ) ?: error("wrapHardwareBuffer returned null")
+            try {
+                return hardwareBitmap.copy(ARGB_8888, false)
+            } finally {
+                hardwareBitmap.recycle()
+            }
+        }
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private suspend inline fun <E> ReceiveChannel<E>.receiveWithTimeout(
+        timeoutMillis: Long,
+        crossinline timeoutMessage: () -> String
+    ): E = select {
+        onReceive { it }
+        onTimeout(timeoutMillis) { fail(timeoutMessage()) }
+    }
+}
+
+/**
+ * Helper for calling [ScrollCaptureCallback.onScrollCaptureSearch] from a suspend function.
+ * The [CancellationSignal] and continuation callback are generated from the coroutine.
+ */
+@RequiresApi(31)
+suspend fun ScrollCaptureCallback.onScrollCaptureSearch(): Rect =
+    suspendCancellableCoroutine { continuation ->
+        onScrollCaptureSearch(continuation.createCancellationSignal()) {
+            continuation.resume(it)
+        }
+    }
+
+/**
+ * Helper for calling [ScrollCaptureCallback.onScrollCaptureStart] from a suspend function.
+ * The [CancellationSignal] and continuation callback are generated from the coroutine.
+ */
+@RequiresApi(31)
+suspend fun ScrollCaptureCallback.onScrollCaptureStart(session: ScrollCaptureSession) {
+    suspendCancellableCoroutine { continuation ->
+        onScrollCaptureStart(session, continuation.createCancellationSignal()) {
+            continuation.resume(Unit)
+        }
+    }
+}
+
+/**
+ * Helper for calling [ScrollCaptureCallback.onScrollCaptureImageRequest] from a suspend function.
+ * The [CancellationSignal] and continuation callback are generated from the coroutine.
+ */
+@RequiresApi(31)
+suspend fun ScrollCaptureCallback.onScrollCaptureImageRequest(
+    session: ScrollCaptureSession,
+    captureArea: Rect
+): Rect = suspendCancellableCoroutine { continuation ->
+    onScrollCaptureImageRequest(
+        session,
+        continuation.createCancellationSignal(),
+        captureArea
+    ) {
+        continuation.resume(it)
+    }
+}
+
+/**
+ * Helper for calling [ScrollCaptureCallback.onScrollCaptureEnd] from a suspend function.
+ * The [CancellationSignal] and continuation callback are generated from the coroutine.
+ */
+@RequiresApi(31)
+suspend fun ScrollCaptureCallback.onScrollCaptureEnd() {
+    suspendCancellableCoroutine { continuation ->
+        onScrollCaptureEnd {
+            continuation.resume(Unit)
+        }
+    }
+}
+
+fun Offset.roundToPoint(): Point = Point(x.roundToInt(), y.roundToInt())
+
+/**
+ * Creates a [CancellationSignal] and wires up cancellation bidirectionally to the coroutine's
+ * job: cancelling either one will automatically cancel the other.
+ */
+private fun CancellableContinuation<*>.createCancellationSignal(): CancellationSignal {
+    val signal = CancellationSignal()
+    signal.setOnCancelListener(this::cancel)
+    invokeOnCancellation { signal.cancel() }
+    return signal
+}
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
index d12c772..5312b3d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
@@ -32,7 +32,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
@@ -205,68 +204,6 @@
     }
 
     @Test
-    @OptIn(ExperimentalComposeUiApi::class)
-    fun unsetSimpleProperty() {
-        rule.setContent {
-            Surface {
-                Box(Modifier
-                    .semantics { unset(SemanticsProperties.Heading) }.semantics { heading() }
-                    .semantics { traversalIndex = 1f; unset(SemanticsProperties.TraversalIndex) }
-                    .testTag(TestTag)
-                ) {
-                    Text("Hello World", modifier = Modifier.padding(8.dp))
-                }
-            }
-        }
-
-        rule.onNodeWithTag(TestTag)
-            .assertDoesNotHaveProperty(SemanticsProperties.Heading)
-        rule.onNodeWithTag(TestTag)
-            .assertDoesNotHaveProperty(SemanticsProperties.TraversalIndex)
-    }
-
-    @Test
-    @OptIn(ExperimentalComposeUiApi::class)
-    fun unsetDuplicateProperty() {
-        rule.setContent {
-            Surface {
-                Box(Modifier
-                    .semantics { unset(SemanticsProperties.TraversalIndex) }
-                    .semantics { traversalIndex = 2f }
-                    .semantics { traversalIndex = 1f }
-                    .testTag(TestTag)
-                ) {
-                    Text("Hello World", modifier = Modifier.padding(8.dp))
-                }
-            }
-        }
-
-        rule.onNodeWithTag(TestTag)
-            .assertDoesNotHaveProperty(SemanticsProperties.TraversalIndex)
-    }
-
-    @Test
-    @OptIn(ExperimentalComposeUiApi::class)
-    fun unsetDuplicatePropertySandwiched() {
-        rule.setContent {
-            Surface {
-                Box(Modifier
-                    .semantics { traversalIndex = 2f }
-                    .semantics { unset(SemanticsProperties.TraversalIndex) }
-                    .semantics { traversalIndex = 1f }
-                    .testTag(TestTag)
-                ) {
-                    Text("Hello World", modifier = Modifier.padding(8.dp))
-                }
-            }
-        }
-
-        rule.onNodeWithTag(TestTag)
-            .assert(SemanticsMatcher.expectValue(
-                SemanticsProperties.TraversalIndex, 2f))
-    }
-
-    @Test
     @Suppress("DEPRECATION")
     fun isContainerPropertyDeprecated() {
         rule.setContent {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index 9c7155d1..3e56cd1 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -74,7 +74,6 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.platform.ViewCompositionStrategy
 import androidx.compose.ui.platform.findViewTreeCompositionContext
@@ -108,6 +107,7 @@
 import androidx.lifecycle.Lifecycle.Event.ON_STOP
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.findViewTreeLifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.savedstate.SavedStateRegistry
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt
index 9ff7b8a..23e60dd 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/contentcapture/AndroidContentCaptureManager.android.kt
@@ -386,6 +386,10 @@
             "android.view.contentcapture.EventTimestamp",
             currentSemanticsNodesSnapshotTimestampMillis)
 
+        configuration.getOrNull(SemanticsProperties.TestTag)?.let {
+            // Treat test tag as resourceId
+            structure.setId(id, null, null, it)
+        }
         configuration.getOrNull(SemanticsProperties.Text)?.let {
             structure.setClassName("android.widget.TextView")
             structure.setText(it.fastJoinToString("\n"))
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidClipboardManager.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidClipboardManager.android.kt
index 2c94b70..1dcec61 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidClipboardManager.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidClipboardManager.android.kt
@@ -19,11 +19,14 @@
 import android.content.ClipData
 import android.content.ClipDescription
 import android.content.Context
+import android.os.Build
 import android.os.Parcel
 import android.text.Annotation
 import android.text.SpannableString
 import android.text.Spanned
 import android.util.Base64
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shadow
@@ -82,17 +85,18 @@
         return clipboardManager.primaryClip?.let(::ClipEntry)
     }
 
-    override fun getClipMetadata(): ClipMetadata? {
-        return clipboardManager.primaryClipDescription?.let(::ClipMetadata)
+    override fun setClip(clipEntry: ClipEntry?) {
+        if (clipEntry == null) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                Api28ClipboardManagerClipClear.clearPrimaryClip(clipboardManager)
+            } else {
+                clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
+            }
+        } else {
+            clipboardManager.setPrimaryClip(clipEntry.clipData)
+        }
     }
 
-    override fun setClip(clipEntry: ClipEntry) {
-        // We ignore the clipDescription parameter on Android because clipEntry comes with one.
-        clipboardManager.setPrimaryClip(clipEntry.clipData)
-    }
-
-    override fun hasClip(): Boolean = clipboardManager.hasPrimaryClip()
-
     override val nativeClipboard: NativeClipboard
         get() = clipboardManager
 }
@@ -102,7 +106,12 @@
  */
 // Defining this class not as a typealias but a wrapper gives us flexibility in the future to
 // add more functionality in it.
-actual class ClipEntry(val clipData: ClipData)
+actual class ClipEntry(val clipData: ClipData) {
+
+    actual fun getMetadata(): ClipMetadata {
+        return clipData.description.toClipMetadata()
+    }
+}
 
 fun ClipData.toClipEntry(): ClipEntry = ClipEntry(this)
 
@@ -118,6 +127,16 @@
 
 actual typealias NativeClipboard = android.content.ClipboardManager
 
+@RequiresApi(28)
+private object Api28ClipboardManagerClipClear {
+
+    @DoNotInline
+    @JvmStatic
+    fun clearPrimaryClip(clipboardManager: android.content.ClipboardManager) {
+        clipboardManager.clearPrimaryClip()
+    }
+}
+
 internal fun CharSequence?.convertToAnnotatedString(): AnnotatedString? {
     if (this == null) return null
     if (this !is Spanned) {
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 923af73..b618150 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
@@ -20,6 +20,7 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Point
 import android.graphics.Rect
 import android.os.Build.VERSION.SDK_INT
 import android.os.Build.VERSION_CODES.M
@@ -46,6 +47,7 @@
 import android.view.MotionEvent.ACTION_SCROLL
 import android.view.MotionEvent.ACTION_UP
 import android.view.MotionEvent.TOOL_TYPE_MOUSE
+import android.view.ScrollCaptureTarget
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewStructure
@@ -111,6 +113,7 @@
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.setFrom
 import androidx.compose.ui.graphics.toAndroidRect
 import androidx.compose.ui.graphics.toComposeRect
@@ -170,6 +173,7 @@
 import androidx.compose.ui.platform.MotionEventVerifierApi29.isValidMotionEvent
 import androidx.compose.ui.platform.coreshims.ContentCaptureSessionCompat
 import androidx.compose.ui.platform.coreshims.ViewCompatShims
+import androidx.compose.ui.scrollcapture.ScrollCapture
 import androidx.compose.ui.semantics.EmptySemanticsElement
 import androidx.compose.ui.semantics.SemanticsOwner
 import androidx.compose.ui.semantics.findClosestParentNode
@@ -792,6 +796,21 @@
         } ?: super.getFocusedRect(rect)
     }
 
+    override fun onScrollCaptureSearch(
+        localVisibleRect: Rect,
+        windowOffset: Point,
+        targets: Consumer<ScrollCaptureTarget>
+    ) {
+        if (SDK_INT >= 31) {
+            ScrollCapture.onScrollCaptureSearch(
+                view = this,
+                semanticsOwner = semanticsOwner,
+                coroutineContext = coroutineContext,
+                targets = targets
+            )
+        }
+    }
+
     override fun onResume(owner: LifecycleOwner) {
         // Refresh in onResume in case the value has changed.
         showLayoutBounds = getIsShowingLayoutBounds()
@@ -1376,8 +1395,12 @@
 
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
-        invalidateParentLayer: () -> Unit
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
     ): OwnedLayer {
+        if (explicitLayer != null) {
+            return GraphicsLayerOwnerLayer(explicitLayer, this, drawBlock, invalidateParentLayer)
+        }
         // First try the layer cache
         val layer = layerCache.pop()
         if (layer !== null) {
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 df549cf..078b92c 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
@@ -724,17 +724,14 @@
     private fun isScreenReaderFocusable(
         node: SemanticsNode
     ): Boolean {
-        // TODO(aelias): This may not behave correctly in combination with fake nodes, see b/283968786
         val nodeContentDescriptionOrNull =
             node.unmergedConfig.getOrNull(SemanticsProperties.ContentDescription)?.firstOrNull()
         val isSpeakingNode = nodeContentDescriptionOrNull != null ||
             getInfoText(node) != null || getInfoStateDescriptionOrNull(node) != null ||
             getInfoIsCheckable(node)
 
-        return node.isVisible &&
-            (node.unmergedConfig.isMergingSemanticsOfDescendants ||
-            node.isUnmergedLeafNode &&
-            isSpeakingNode)
+        return node.unmergedConfig.isMergingSemanticsOfDescendants ||
+            node.isUnmergedLeafNode && isSpeakingNode
     }
 
     @OptIn(ExperimentalComposeUiApi::class)
@@ -776,7 +773,6 @@
 
         // This property exists to distinguish semantically meaningful nodes from purely structural
         // or decorative UI elements.  Most nodes are considered important, except:
-        // * Invisible nodes.
         // * Non-merging nodes with only non-accessibility-speakable properties.
         //     * Of the built-in ones, the key example is testTag.
         //     * Custom SemanticsPropertyKeys defined outside the UI package
@@ -2102,40 +2098,29 @@
             hitSemanticsEntities = hitSemanticsEntities
         )
 
-        // Iterate front-to-back until we find a node with semantics that are important-for-a11y
-        for (i in hitSemanticsEntities.lastIndex downTo 0) {
-            val layoutNode = hitSemanticsEntities[i].requireLayoutNode()
+        val layoutNode = hitSemanticsEntities.lastOrNull()?.requireLayoutNode()
 
-            // If this node corresponds to an AndroidView, then we should return InvalidId
-            // to let the View System handle it.
-            val androidView = view
-                .androidViewsHandler
-                .layoutNodeToHolder[layoutNode]
-            if (androidView != null) {
-                return InvalidId
-            }
-
-            if (layoutNode.nodes.has(Nodes.Semantics) == false) {
-                continue
-            }
-
-            val virtualViewId = semanticsNodeIdToAccessibilityVirtualNodeId(
-                layoutNode.semanticsId
-            )
+        var virtualViewId = InvalidId
+        if (layoutNode?.nodes?.has(Nodes.Semantics) == true) {
 
             // The node below is not added to the tree; it's a wrapper around outer semantics to
             // use the methods available to the SemanticsNode
             val semanticsNode = SemanticsNode(layoutNode, false)
 
-            // Continue to the next items in the hit test if it's not considered important.
-            if (!semanticsNode.isImportantForAccessibility()) {
-                continue
+            // Do not 'find' invisible nodes when exploring by touch. This will prevent us from
+            // sending events for invisible nodes
+            if (semanticsNode.isVisible) {
+                val androidView = view
+                    .androidViewsHandler
+                    .layoutNodeToHolder[layoutNode]
+                if (androidView == null) {
+                    virtualViewId = semanticsNodeIdToAccessibilityVirtualNodeId(
+                        layoutNode.semanticsId
+                    )
+                }
             }
-
-            return virtualViewId
         }
-
-        return InvalidId
+        return virtualViewId
     }
 
     /**
@@ -3221,6 +3206,10 @@
 // shorter and more readable.
 private fun SemanticsNode.enabled() = (!config.contains(SemanticsProperties.Disabled))
 
+@OptIn(ExperimentalComposeUiApi::class)
+private val SemanticsNode.isVisible: Boolean
+    get() = !isTransparent && !unmergedConfig.contains(SemanticsProperties.InvisibleToUser)
+
 private fun SemanticsNode.propertiesDeleted(oldConfig: SemanticsConfiguration): Boolean {
     for (entry in oldConfig) {
         if (!config.contains(entry.key)) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ClipboardExtensions.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ClipboardExtensions.android.kt
index 32ec68d..2dd16b8 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ClipboardExtensions.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ClipboardExtensions.android.kt
@@ -23,9 +23,11 @@
 /**
  * Returns the first non-null [Uri] from the list of [ClipData.Item]s in this [ClipEntry].
  *
- * Do not forget that each [ClipEntry] can contain multiple [ClipData.Item]s in its [ClipData],
- * therefore it can have multiple [Uri]s. Always check whether you are processing all the items in
- * a given [ClipEntry].
+ * ClipEntry can contain single or multiple [ClipData.Item]s. This function is useful when you are
+ * only interested in processing just a single [Uri] item inside the [ClipEntry].
+ *
+ * It's advised that you consider checking all the items inside [ClipEntry.clipData] to thoroughly
+ * process a given [ClipEntry].
  */
 @ExperimentalComposeUiApi
 fun ClipEntry.firstUriOrNull(): Uri? {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
new file mode 100644
index 0000000..d99860d
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.platform
+
+import androidx.compose.ui.geometry.MutableRect
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isUnspecified
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.drawscope.draw
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.layer.GraphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
+import androidx.compose.ui.internal.throwIllegalStateException
+import androidx.compose.ui.layout.GraphicLayerInfo
+import androidx.compose.ui.node.OwnedLayer
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.center
+import androidx.compose.ui.unit.toOffset
+import androidx.compose.ui.unit.toSize
+
+internal class GraphicsLayerOwnerLayer(
+    private val graphicsLayer: GraphicsLayer,
+    private val ownerView: AndroidComposeView,
+    drawBlock: (Canvas) -> Unit,
+    invalidateParentLayer: () -> Unit
+) : OwnedLayer, GraphicLayerInfo {
+    private var drawBlock: ((Canvas) -> Unit)? = drawBlock
+    private var invalidateParentLayer: (() -> Unit)? = invalidateParentLayer
+
+    private var size: IntSize = IntSize.Zero
+    private var isDestroyed = false
+    private val matrixCache = Matrix()
+
+    private var isDirty = true
+        set(value) {
+            if (value != field) {
+                field = value
+                ownerView.notifyLayerIsDirty(this, value)
+            }
+        }
+
+    private var density = Density(1f)
+    private var layoutDirection = LayoutDirection.Ltr
+    private val scope = CanvasDrawScope()
+
+    private var tmpTouchPointPath: Path? = null
+    private var tmpOpPath: Path? = null
+
+    override fun updateLayerProperties(
+        scope: ReusableGraphicsLayerScope,
+        layoutDirection: LayoutDirection,
+        density: Density,
+    ) {
+        throwIllegalStateException(
+            "Current apis doesn't allow for both GraphicsLayer and GraphicsLayerScope to be " +
+                "used together"
+        )
+    }
+
+    override fun isInLayer(position: Offset): Boolean {
+        val x = position.x
+        val y = position.y
+
+        if (graphicsLayer.clip) {
+            val outline = graphicsLayer.outline
+            return isInOutline(outline, x, y, tmpTouchPointPath, tmpOpPath)
+        }
+
+        return true
+    }
+
+    override fun move(position: IntOffset) {
+        graphicsLayer.topLeft = position
+    }
+
+    override fun resize(size: IntSize) {
+        if (size != this.size) {
+            this.size = size
+            invalidate()
+        }
+    }
+
+    override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
+        updateDisplayList()
+        scope.draw(density, layoutDirection, canvas, size.toSize(), parentLayer) {
+            drawLayer(graphicsLayer)
+        }
+    }
+
+    override fun updateDisplayList() {
+        if (isDirty) {
+            graphicsLayer.buildLayer(density, layoutDirection, size) {
+                drawIntoCanvas { canvas ->
+                    drawBlock?.let { it(canvas) }
+                }
+            }
+            isDirty = false
+        }
+    }
+
+    override fun invalidate() {
+        if (!isDirty && !isDestroyed) {
+            ownerView.invalidate()
+            isDirty = true
+        }
+    }
+
+    override fun destroy() {
+        drawBlock = null
+        invalidateParentLayer = null
+        isDestroyed = true
+        isDirty = false
+    }
+
+    override fun mapOffset(point: Offset, inverse: Boolean): Offset {
+        return getMatrix(inverse).map(point)
+    }
+
+    override fun mapBounds(rect: MutableRect, inverse: Boolean) {
+        getMatrix(inverse).map(rect)
+    }
+
+    override fun reuseLayer(drawBlock: (Canvas) -> Unit, invalidateParentLayer: () -> Unit) {
+        throwIllegalStateException("reuseLayer is not supported yet")
+    }
+
+    override fun transform(matrix: Matrix) {
+        matrix.timesAssign(getMatrix(inverse = false))
+    }
+
+    override fun inverseTransform(matrix: Matrix) {
+        matrix.timesAssign(getMatrix(inverse = true))
+    }
+
+    override val layerId: Long
+        get() = graphicsLayer.layerId
+
+    override val ownerViewId: Long
+        get() = graphicsLayer.ownerViewId
+
+    private fun getMatrix(inverse: Boolean = false): Matrix {
+        updateMatrix()
+        if (inverse) {
+            matrixCache.invert()
+        }
+        return matrixCache
+    }
+
+    private fun updateMatrix() = with(graphicsLayer) {
+        val pivot = if (pivotOffset.isUnspecified) size.center.toOffset() else pivotOffset
+
+        matrixCache.reset()
+        matrixCache *= Matrix().apply {
+            translate(x = -pivot.x, y = -pivot.y)
+        }
+        matrixCache *= Matrix().apply {
+            translate(translationX, translationY)
+            rotateX(rotationX)
+            rotateY(rotationY)
+            rotateZ(rotationZ)
+            scale(scaleX, scaleY)
+        }
+        matrixCache *= Matrix().apply {
+            translate(x = pivot.x, y = pivot.y)
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
index 2d0e9030..7e258ec 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
 import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.GraphicLayerInfo
@@ -276,7 +277,7 @@
         }
     }
 
-    override fun drawLayer(canvas: Canvas) {
+    override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
         val androidCanvas = canvas.nativeCanvas
         if (androidCanvas.isHardwareAccelerated) {
             updateDisplayList()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
index 314fa5c..9e94664 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/SemanticsUtils.android.kt
@@ -23,7 +23,6 @@
 import androidx.collection.MutableIntSet
 import androidx.collection.mutableIntObjectMapOf
 import androidx.collection.mutableIntSetOf
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.OwnerScope
@@ -33,7 +32,6 @@
 import androidx.compose.ui.semantics.SemanticsConfiguration
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.semantics.SemanticsOwner
-import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.semantics.getOrNull
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.util.fastForEach
@@ -119,13 +117,8 @@
     }
 
 internal fun SemanticsNode.isImportantForAccessibility() =
-    isVisible &&
-        (unmergedConfig.isMergingSemanticsOfDescendants ||
-            unmergedConfig.containsImportantForAccessibility())
-
-@OptIn(ExperimentalComposeUiApi::class)
-internal val SemanticsNode.isVisible: Boolean
-    get() = !isTransparent && !unmergedConfig.contains(SemanticsProperties.InvisibleToUser)
+    unmergedConfig.isMergingSemanticsOfDescendants ||
+        unmergedConfig.containsImportantForAccessibility()
 
 internal val DefaultFakeNodeBounds = Rect(0f, 0f, 10f, 10f)
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
index ca943b3..4ef277e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.graphics.RenderEffect
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
 import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.GraphicLayerInfo
 import androidx.compose.ui.node.OwnedLayer
@@ -308,7 +309,7 @@
         }
     }
 
-    override fun drawLayer(canvas: Canvas) {
+    override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
         drawnWithZ = elevation > 0f
         if (drawnWithZ) {
             canvas.enableZ()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
index 118695c..6716cc4 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayerContainer.android.kt
@@ -19,8 +19,8 @@
 import android.content.Context
 import android.view.View
 import android.view.ViewGroup
+import androidx.compose.ui.R
 import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.R
 import androidx.compose.ui.graphics.nativeCanvas
 
 /**
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ComposeScrollCaptureCallback.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ComposeScrollCaptureCallback.android.kt
new file mode 100644
index 0000000..3f05d03
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ComposeScrollCaptureCallback.android.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.scrollcapture
+
+import android.graphics.BlendMode
+import android.graphics.Canvas as AndroidCanvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect as AndroidRect
+import android.os.CancellationSignal
+import android.view.ScrollCaptureCallback
+import android.view.ScrollCaptureSession
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.ui.MotionDurationScale
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.toAndroidRect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.graphics.toComposeIntRect
+import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.semantics.SemanticsActions.ScrollByOffset
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.unit.IntRect
+import java.util.function.Consumer
+import kotlin.math.roundToInt
+import kotlin.random.Random
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
+
+private const val DEBUG = false
+
+/**
+ * Implementation of [ScrollCaptureCallback] that captures Compose scroll containers.
+ *
+ * This callback interacts with the scroll container via semantics, namely [ScrollByOffset],
+ * and supports any container that publishes that action – whether the size of the scroll contents
+ * are known or not (e.g. `LazyColumn`). Pixels are captured by drawing the node directly after each
+ * scroll operation.
+ */
+@RequiresApi(31)
+internal class ComposeScrollCaptureCallback(
+    private val node: SemanticsNode,
+    private val viewportBoundsInWindow: IntRect,
+    coroutineScope: CoroutineScope,
+) : ScrollCaptureCallback {
+    // Don't animate scrollByOffset calls.
+    private val coroutineScope = coroutineScope + DisableAnimationMotionDurationScale
+
+    private val scrollTracker = RelativeScroller(
+        viewportSize = viewportBoundsInWindow.height,
+        scrollBy = { amount ->
+            val scrollByOffset = checkPreconditionNotNull(node.scrollCaptureScrollByAction)
+            // This action may animate, ensure any calls to this RelativeScroll are done with a
+            // coroutine context that disables animations.
+            val consumed = scrollByOffset(Offset(0f, amount))
+            consumed.y
+        }
+    )
+
+    override fun onScrollCaptureSearch(signal: CancellationSignal, onReady: Consumer<AndroidRect>) {
+        val bounds = viewportBoundsInWindow
+        onReady.accept(bounds.toAndroidRect())
+    }
+
+    override fun onScrollCaptureStart(
+        session: ScrollCaptureSession,
+        signal: CancellationSignal,
+        onReady: Runnable
+    ) {
+        scrollTracker.reset()
+        // TODO(b/329296635) Notify target when capture session starts.
+        onReady.run()
+    }
+
+    override fun onScrollCaptureImageRequest(
+        session: ScrollCaptureSession,
+        signal: CancellationSignal,
+        captureArea: AndroidRect,
+        onComplete: Consumer<AndroidRect>
+    ) {
+        coroutineScope.launchWithCancellationSignal(signal) {
+            val result = onScrollCaptureImageRequest(session, captureArea.toComposeIntRect())
+            onComplete.accept(result.toAndroidRect())
+        }
+    }
+
+    private suspend fun onScrollCaptureImageRequest(
+        session: ScrollCaptureSession,
+        captureArea: IntRect,
+    ): IntRect {
+        // Scroll the requested capture area into the viewport so we can draw it.
+        val targetMin = captureArea.top
+        val targetMax = captureArea.bottom
+        scrollTracker.scrollRangeIntoView(targetMin, targetMax)
+
+        // Wait a frame to allow layout to respond to the scroll.
+        withFrameNanos {}
+
+        // Calculate the viewport-relative coordinates of the capture area, clipped to
+        // the viewport.
+        val viewportClippedMin = scrollTracker.mapOffsetToViewport(targetMin)
+        val viewportClippedMax = scrollTracker.mapOffsetToViewport(targetMax)
+        val viewportClippedRect = captureArea.copy(
+            top = viewportClippedMin,
+            bottom = viewportClippedMax
+        )
+
+        if (viewportClippedMin == viewportClippedMax) {
+            // Requested capture area is outside the bounds of scrollable content,
+            // nothing to capture.
+            return IntRect.Zero
+        }
+
+        // Draw a single frame of the content to a buffer that we can stamp out.
+        val coordinator = checkNotNull(node.findCoordinatorToGetBounds()) {
+            "Could not find coordinator for semantics node."
+        }
+
+        val androidCanvas = session.surface.lockHardwareCanvas()
+        try {
+            // Clear any pixels left over from a previous request.
+            androidCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR)
+
+            if (DEBUG) {
+                androidCanvas.drawDebugBackground()
+            }
+
+            val canvas = Canvas(androidCanvas)
+            canvas.translate(
+                dx = -viewportClippedRect.left.toFloat(),
+                dy = -viewportClippedRect.top.toFloat()
+            )
+            coordinator.draw(canvas, graphicsLayer = null)
+
+            if (DEBUG) {
+                canvas.translate(
+                    dx = viewportClippedRect.left.toFloat(),
+                    dy = viewportClippedRect.top.toFloat(),
+                )
+                androidCanvas.drawDebugOverlay()
+            }
+        } finally {
+            session.surface.unlockCanvasAndPost(androidCanvas)
+        }
+
+        // Translate back to "original" coordinates to report.
+        val resultRect = viewportClippedRect.translate(0, scrollTracker.scrollAmount.roundToInt())
+        return resultRect
+    }
+
+    override fun onScrollCaptureEnd(onReady: Runnable) {
+        coroutineScope.launch(NonCancellable) {
+            scrollTracker.scrollTo(0f)
+            // TODO(b/329296635) Notify target when capture session ends.
+            onReady.run()
+        }
+    }
+
+    private fun AndroidCanvas.drawDebugBackground() {
+        drawColor(
+            androidx.compose.ui.graphics.Color.hsl(
+                hue = Random.nextFloat() * 360f,
+                saturation = 0.75f,
+                lightness = 0.5f,
+                alpha = 1f
+            ).toArgb()
+        )
+    }
+
+    private fun AndroidCanvas.drawDebugOverlay() {
+        val circleRadius = 20f
+        val circlePaint = Paint().apply {
+            color = Color.RED
+        }
+        drawCircle(0f, 0f, circleRadius, circlePaint)
+        drawCircle(width.toFloat(), 0f, circleRadius, circlePaint)
+        drawCircle(
+            width.toFloat(),
+            height.toFloat(),
+            circleRadius,
+            circlePaint
+        )
+        drawCircle(0f, height.toFloat(), circleRadius, circlePaint)
+    }
+}
+
+private fun CoroutineScope.launchWithCancellationSignal(
+    signal: CancellationSignal,
+    block: suspend CoroutineScope.() -> Unit
+): Job {
+    val job = launch(block = block)
+    job.invokeOnCompletion { cause ->
+        if (cause != null) {
+            signal.cancel()
+        }
+    }
+    signal.setOnCancelListener {
+        job.cancel()
+    }
+    return job
+}
+
+/**
+ * Helper class for scrolling to specific offsets relative to an original scroll position and
+ * mapping those offsets to the current viewport coordinates.
+ */
+private class RelativeScroller(
+    private val viewportSize: Int,
+    private val scrollBy: suspend (Float) -> Float
+) {
+    var scrollAmount = 0f
+        private set
+
+    fun reset() {
+        scrollAmount = 0f
+    }
+
+    /**
+     * Scrolls so that the range ([min], [max]) is in the viewport. The range must fit inside the
+     * viewport.
+     */
+    suspend fun scrollRangeIntoView(min: Int, max: Int) {
+        require(min <= max) { "Expected min=$min ≤ max=$max" }
+        require(max - min <= viewportSize) {
+            "Expected range (${max - min}) to be ≤ viewportSize=$viewportSize"
+        }
+
+        if (min >= scrollAmount && max <= scrollAmount + viewportSize) {
+            // Already visible, no need to scroll.
+            return
+        }
+
+        // Scroll to the nearest edge.
+        val target = if (min < scrollAmount) min else max - viewportSize
+        scrollTo(target.toFloat())
+    }
+
+    /**
+     * Given [offset] relative to the original scroll position, maps it to the current offset in the
+     * viewport. Values are clamped to the viewport.
+     *
+     * This is an identity map for values inside the viewport before any scrolling has been done
+     * after calling `scrollTo(0f)`.
+     */
+    fun mapOffsetToViewport(offset: Int): Int {
+        return (offset - scrollAmount.roundToInt()).coerceIn(0, viewportSize)
+    }
+
+    /**
+     * Try to scroll to [offset] pixels past the original scroll position.
+     */
+    suspend fun scrollTo(offset: Float): Float = scrollBy(offset - scrollAmount)
+
+    private suspend fun scrollBy(delta: Float): Float {
+        val consumed = scrollBy.invoke(delta)
+        scrollAmount += consumed
+        return consumed
+    }
+}
+
+private object DisableAnimationMotionDurationScale : MotionDurationScale {
+    override val scaleFactor: Float
+        get() = 0f
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ScrollCapture.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ScrollCapture.android.kt
new file mode 100644
index 0000000..81e18e2
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ScrollCapture.android.kt
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.scrollcapture
+
+import android.graphics.Point
+import android.view.ScrollCaptureCallback
+import android.view.ScrollCaptureTarget
+import android.view.View
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.graphics.toAndroidRect
+import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInRoot
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.semantics.SemanticsActions.ScrollByOffset
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.semantics.SemanticsOwner
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.SemanticsProperties.Disabled
+import androidx.compose.ui.semantics.SemanticsProperties.VerticalScrollAxisRange
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.roundToIntRect
+import java.util.function.Consumer
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Temporary feature flag for long screenshots support in Compose scrollables. This property will
+ * eventually be removed.
+ *
+ * Long screenshot support is currently off by default. To enable it, set this flag to true.
+ * A future release will set it to true by default.
+ */
+@Deprecated("Temporary feature flag. See b/329128246")
+@get:Deprecated("Temporary feature flag. See b/329128246")
+@set:Deprecated("Temporary feature flag. See b/329128246")
+// TODO(b/329128246) Remove before 1.7
+var ComposeFeatureFlag_LongScreenshotsEnabled by mutableStateOf(false)
+
+/**
+ * Separate class to host the implementation of scroll capture for dex verification.
+ */
+@RequiresApi(31)
+internal object ScrollCapture {
+    /**
+     * Implements scroll capture (long screenshots) support for a composition. Finds a single
+     * [ScrollCaptureTarget] to propose to the platform. Searches over the semantics tree to find
+     * nodes that publish vertical scroll semantics (namely [ScrollByOffset] and
+     * [VerticalScrollAxisRange]) and then uses logic similar to how the platform searches [View]
+     * targets to select the deepest, largest scroll container. If a target is found, an
+     * implementation of [ScrollCaptureCallback] is created for it (see
+     * [ComposeScrollCaptureCallback]) and given to the platform.
+     *
+     * The platform currently only supports scroll capture for containers that scroll vertically.
+     * The API supports horizontal as well, but it's not used. To keep this code simpler and avoid
+     * having dead code, we only implement vertical scroll capture as well.
+     *
+     * See go/compose-long-screenshots for more background.
+     */
+    // Required not to be inlined for class verification.
+    @DoNotInline
+    fun onScrollCaptureSearch(
+        view: View,
+        semanticsOwner: SemanticsOwner,
+        coroutineContext: CoroutineContext,
+        targets: Consumer<ScrollCaptureTarget>
+    ) {
+        @Suppress("DEPRECATION")
+        if (!ComposeFeatureFlag_LongScreenshotsEnabled) return
+
+        // Search the semantics tree for scroll containers.
+        val candidates = mutableVectorOf<ScrollCaptureCandidate>()
+        visitScrollCaptureCandidates(
+            fromNode = semanticsOwner.unmergedRootSemanticsNode,
+            onCandidate = candidates::add
+        )
+
+        // Sort to find the deepest node with the biggest bounds in the dimension(s) that the node
+        // supports scrolling in.
+        candidates.sortWith(compareBy(
+            { it.depth },
+            { it.viewportBoundsInWindow.height },
+        ))
+        val candidate = candidates.lastOrNull() ?: return
+
+        // If we found a candidate, create a capture callback for it and give it to the system.
+        val coroutineScope = CoroutineScope(coroutineContext)
+        val callback = ComposeScrollCaptureCallback(
+            node = candidate.node,
+            viewportBoundsInWindow = candidate.viewportBoundsInWindow,
+            coroutineScope = coroutineScope,
+        )
+        val localVisibleRectOfCandidate = candidate.coordinates.boundsInRoot()
+        val windowOffsetOfCandidate = candidate.viewportBoundsInWindow.topLeft
+        targets.accept(
+            ScrollCaptureTarget(
+                view,
+                localVisibleRectOfCandidate.roundToIntRect().toAndroidRect(),
+                windowOffsetOfCandidate.let { Point(it.x, it.y) },
+                callback
+            ).apply {
+                scrollBounds = candidate.viewportBoundsInWindow.toAndroidRect()
+            }
+        )
+    }
+}
+
+/**
+ * Walks the tree of [SemanticsNode]s rooted at [fromNode] to find nodes that look scrollable and
+ * calculate their nesting depth.
+ */
+private fun visitScrollCaptureCandidates(
+    fromNode: SemanticsNode,
+    depth: Int = 0,
+    onCandidate: (ScrollCaptureCandidate) -> Unit
+) {
+    fromNode.visitDescendants { node ->
+        // Invisible/disabled nodes can't be candidates, nor can any of their descendants.
+        if (!node.isVisible || Disabled in node.config) {
+            return@visitDescendants false
+        }
+
+        val nodeCoordinates = checkPreconditionNotNull(node.findCoordinatorToGetBounds()) {
+            "Expected semantics node to have a coordinator."
+        }.coordinates
+
+        // Zero-sized nodes can't be candidates, and by definition would clip all their children so
+        // they and their descendants can't be candidates either.
+        val viewportBoundsInWindow = nodeCoordinates.boundsInWindow().roundToIntRect()
+        if (viewportBoundsInWindow.isEmpty) {
+            return@visitDescendants false
+        }
+
+        // If the node is visible, we need to check if it's scrollable.
+        // TODO(b/329295945) Support explicit opt-in/-out.
+        // Don't care about horizontal scroll containers.
+        if (!node.canScrollVertically) {
+            // Not a scrollable, so can't be a candidate, but its descendants might be.
+            return@visitDescendants true
+        }
+
+        // We found a node that looks scrollable! Report it, then visit its children with an
+        // incremented depth counter.
+        val candidateDepth = depth + 1
+        onCandidate(
+            ScrollCaptureCandidate(
+                node = node,
+                depth = candidateDepth,
+                viewportBoundsInWindow = viewportBoundsInWindow,
+                coordinates = nodeCoordinates,
+            )
+        )
+        visitScrollCaptureCandidates(
+            fromNode = node,
+            depth = candidateDepth,
+            onCandidate = onCandidate
+        )
+        // We've just visited descendants ourselves, don't need this visit call to do it.
+        return@visitDescendants false
+    }
+}
+
+internal val SemanticsNode.scrollCaptureScrollByAction get() = config.getOrNull(ScrollByOffset)
+
+// TODO(mnuzen): Port this back to the SemanticsUtil file
+@OptIn(ExperimentalComposeUiApi::class)
+private val SemanticsNode.isVisible: Boolean
+    get() = !isTransparent && !unmergedConfig.contains(SemanticsProperties.InvisibleToUser)
+
+private val SemanticsNode.canScrollVertically: Boolean
+    get() {
+        val scrollByOffset = scrollCaptureScrollByAction
+        val verticalScrollAxisRange = config.getOrNull(VerticalScrollAxisRange)
+        return scrollByOffset != null &&
+            verticalScrollAxisRange != null &&
+            verticalScrollAxisRange.maxValue() > 0f
+    }
+
+/**
+ * Visits all the descendants of this [SemanticsNode].
+ *
+ * @param onNode Function called for each [SemanticsNode]. Iff this function returns true, the
+ * children of the current node will be visited.
+ */
+private inline fun SemanticsNode.visitDescendants(onNode: (SemanticsNode) -> Boolean) {
+    val nodes = mutableVectorOf<SemanticsNode>()
+    nodes.addAll(getChildrenForSearch())
+    while (nodes.isNotEmpty()) {
+        val node = nodes.removeAt(nodes.lastIndex)
+        val visitChildren = onNode(node)
+        if (visitChildren) {
+            nodes.addAll(node.getChildrenForSearch())
+        }
+    }
+}
+
+private fun SemanticsNode.getChildrenForSearch() = getChildren(
+    includeDeactivatedNodes = false,
+    includeReplacedSemantics = false,
+    includeFakeNodes = false
+)
+
+/**
+ * Information about a potential [ScrollCaptureTarget] needed to both select the final candidate and
+ * create its [ComposeScrollCaptureCallback].
+ */
+private class ScrollCaptureCandidate(
+    val node: SemanticsNode,
+    val depth: Int,
+    val viewportBoundsInWindow: IntRect,
+    val coordinates: LayoutCoordinates,
+) {
+    override fun toString(): String =
+        "ScrollCaptureCandidate(node=$node, " +
+            "depth=$depth, " +
+            "viewportBoundsInWindow=$viewportBoundsInWindow, " +
+            "coordinates=$coordinates)"
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
index 47250c8..744a9a9 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
@@ -47,13 +47,13 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.ViewRootForInspector
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.findViewTreeLifecycleOwner
 import androidx.savedstate.SavedStateRegistryOwner
 
diff --git a/compose/ui/ui/src/androidMain/res/values/ids.xml b/compose/ui/ui/src/androidMain/res/values/ids.xml
index 140ecaf..721b41d 100644
--- a/compose/ui/ui/src/androidMain/res/values/ids.xml
+++ b/compose/ui/ui/src/androidMain/res/values/ids.xml
@@ -52,5 +52,6 @@
     <item name="inspection_slot_table_set" type="id" />
     <item name="androidx_compose_ui_view_composition_context" type="id" />
     <item name="compose_view_saveable_id_tag" type="id" />
+    <item name="hide_in_inspector_tag" type="id" />
     <item name="consume_window_insets_tag" type="id" />
 </resources>
diff --git a/compose/ui/ui/src/androidMain/res/values/public.xml b/compose/ui/ui/src/androidMain/res/values/public.xml
index 4d54254..969bb58 100644
--- a/compose/ui/ui/src/androidMain/res/values/public.xml
+++ b/compose/ui/ui/src/androidMain/res/values/public.xml
@@ -15,4 +15,5 @@
   -->
 
 <resources>
+    <public name="hide_in_inspector_tag" type="id" />
 </resources>
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index abde106..14e60cb 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -36,6 +36,7 @@
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -2644,7 +2645,8 @@
 
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
-        invalidateParentLayer: () -> Unit
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
     ): OwnedLayer {
         val transform = Matrix()
         val inverseTransform = Matrix()
@@ -2670,7 +2672,7 @@
             override fun resize(size: IntSize) {
             }
 
-            override fun drawLayer(canvas: Canvas) {
+            override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
                 drawBlock(canvas)
             }
 
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
index 1d27c7b..1c7c4c2 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/node/ModifierLocalConsumerEntityTest.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -386,8 +387,11 @@
         override val autofill: Autofill
             get() = TODO("Not yet implemented")
 
-        override fun createLayer(drawBlock: (Canvas) -> Unit, invalidateParentLayer: () -> Unit) =
-            TODO("Not yet implemented")
+        override fun createLayer(
+            drawBlock: (Canvas) -> Unit,
+            invalidateParentLayer: () -> Unit,
+            explicitLayer: GraphicsLayer?
+        ) = TODO("Not yet implemented")
 
         override fun onRequestRelayout(
             layoutNode: LayoutNode,
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/platform/ClipboardManagerTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/platform/ClipboardManagerTest.kt
index 6c31ebf..35fd899 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/platform/ClipboardManagerTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/platform/ClipboardManagerTest.kt
@@ -53,9 +53,5 @@
 
     override fun getClip(): ClipEntry? = null
 
-    override fun getClipMetadata(): ClipMetadata? = null
-
-    override fun hasClip(): Boolean = false
-
-    override fun setClip(clipEntry: ClipEntry) = Unit
+    override fun setClip(clipEntry: ClipEntry?) = Unit
 }
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 c78b8ef..257922e 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
@@ -42,6 +42,7 @@
 import androidx.compose.ui.node.ancestors
 import androidx.compose.ui.node.dispatchForKind
 import androidx.compose.ui.node.nearestAncestor
+import androidx.compose.ui.node.visitAncestors
 import androidx.compose.ui.node.visitLocalDescendants
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.LayoutDirection
@@ -266,10 +267,10 @@
 
         val activeFocusTarget = rootFocusNode.findActiveFocusNode()
         val focusedKeyInputNode = activeFocusTarget?.lastLocalKeyInputNode()
-            ?: activeFocusTarget?.nearestAncestor(Nodes.KeyInput)?.node
+            ?: activeFocusTarget?.nearestAncestorIncludingSelf(Nodes.KeyInput)?.node
             ?: rootFocusNode.nearestAncestor(Nodes.KeyInput)?.node
 
-        focusedKeyInputNode?.traverseAncestors(
+        focusedKeyInputNode?.traverseAncestorsIncludingSelf(
             type = Nodes.KeyInput,
             onPreVisit = { if (it.onPreKeyEvent(keyEvent)) return true },
             onVisit = { if (onFocusedItem.invoke()) return true },
@@ -285,9 +286,9 @@
         }
 
         val focusedSoftKeyboardInterceptionNode = rootFocusNode.findActiveFocusNode()
-            ?.nearestAncestor(Nodes.SoftKeyboardKeyInput)
+            ?.nearestAncestorIncludingSelf(Nodes.SoftKeyboardKeyInput)
 
-        focusedSoftKeyboardInterceptionNode?.traverseAncestors(
+        focusedSoftKeyboardInterceptionNode?.traverseAncestorsIncludingSelf(
             type = Nodes.SoftKeyboardKeyInput,
             onPreVisit = { if (it.onPreInterceptKeyBeforeSoftKeyboard(keyEvent)) return true },
             onVisit = { /* TODO(b/320510084): dispatch soft keyboard events to embedded views. */ },
@@ -305,9 +306,9 @@
         }
 
         val focusedRotaryInputNode = rootFocusNode.findActiveFocusNode()
-            ?.nearestAncestor(Nodes.RotaryInput)
+            ?.nearestAncestorIncludingSelf(Nodes.RotaryInput)
 
-        focusedRotaryInputNode?.traverseAncestors(
+        focusedRotaryInputNode?.traverseAncestorsIncludingSelf(
             type = Nodes.RotaryInput,
             onPreVisit = { if (it.onPreRotaryScrollEvent(event)) return true },
             onVisit = { /* TODO(b/320510084): dispatch rotary events to embedded views. */ },
@@ -341,7 +342,7 @@
         }
     }
 
-    private inline fun <reified T : DelegatableNode> DelegatableNode.traverseAncestors(
+    private inline fun <reified T : DelegatableNode> DelegatableNode.traverseAncestorsIncludingSelf(
         type: NodeKind<T>,
         onPreVisit: (T) -> Unit,
         onVisit: () -> Unit,
@@ -355,6 +356,15 @@
         ancestors?.fastForEach(onPostVisit)
     }
 
+    private inline fun <reified T : Any> DelegatableNode.nearestAncestorIncludingSelf(
+        type: NodeKind<T>
+    ): T? {
+        visitAncestors(type, includeSelf = true) {
+            return it
+        }
+        return null
+    }
+
     /**
      * Searches for the currently focused item, and returns its coordinates as a rect.
      */
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 d58b427..d4f1682 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
@@ -212,7 +212,13 @@
             mask = Nodes.FocusEvent or Nodes.FocusTarget,
             includeSelf = true
         ) {
-            if (it.isKind(Nodes.FocusTarget)) return@visitAncestors
+            // We want invalidation to propagate until the next focus target in the hierarchy, but
+            // if the current node is both a FocusEvent and FocusTarget node, we still want to
+            // visit this node and invalidate the focus event nodes. This case is not recommended,
+            // using the state from the FocusTarget node directly is preferred to the indirection of
+            // listening to events from the state you already own, but we should support this case
+            // anyway to be safe.
+            if (it !== this.node && it.isKind(Nodes.FocusTarget)) return@visitAncestors
 
             if (it.isAttached) {
                 it.dispatchForKind(Nodes.FocusEvent) { eventNode ->
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
index 6ebe14f..91fe684 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnRemeasuredModifier.kt
@@ -20,9 +20,9 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.IntSize
 
 /**
@@ -44,27 +44,15 @@
 @Stable
 fun Modifier.onSizeChanged(
     onSizeChanged: (IntSize) -> Unit
-) = this.then(
-    OnSizeChangedModifier(
-        onSizeChanged = onSizeChanged,
-        inspectorInfo = debugInspectorInfo {
-            name = "onSizeChanged"
-            properties["onSizeChanged"] = onSizeChanged
-        }
-    )
-)
+) = this.then(OnSizeChangedModifier(onSizeChanged = onSizeChanged))
 
 private class OnSizeChangedModifier(
-    val onSizeChanged: (IntSize) -> Unit,
-    inspectorInfo: InspectorInfo.() -> Unit
-) : OnRemeasuredModifier, InspectorValueInfo(inspectorInfo) {
-    private var previousSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
+    private val onSizeChanged: (IntSize) -> Unit
+) : ModifierNodeElement<OnSizeChangedNode>() {
+    override fun create(): OnSizeChangedNode = OnSizeChangedNode(onSizeChanged)
 
-    override fun onRemeasured(size: IntSize) {
-        if (previousSize != size) {
-            onSizeChanged(size)
-            previousSize = size
-        }
+    override fun update(node: OnSizeChangedNode) {
+        node.update(onSizeChanged)
     }
 
     override fun equals(other: Any?): Boolean {
@@ -77,6 +65,33 @@
     override fun hashCode(): Int {
         return onSizeChanged.hashCode()
     }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "onSizeChanged"
+        properties["onSizeChanged"] = onSizeChanged
+    }
+}
+
+private class OnSizeChangedNode(
+    private var onSizeChanged: (IntSize) -> Unit
+) : Modifier.Node(), LayoutAwareModifierNode {
+    // When onSizeChanged changes, we want to invalidate so onRemeasured is called again
+    override val shouldAutoInvalidate: Boolean = true
+    private var previousSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
+
+    fun update(onSizeChanged: (IntSize) -> Unit) {
+        this.onSizeChanged = onSizeChanged
+        // Reset the previous size, so when onSizeChanged changes the new lambda gets invoked,
+        // matching previous behavior
+        previousSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
+    }
+
+    override fun onRemeasured(size: IntSize) {
+        if (previousSize != size) {
+            onSizeChanged(size)
+            previousSize = size
+        }
+    }
 }
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
index c432407..d96c185 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.layout
 
 import androidx.compose.ui.graphics.GraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.node.LookaheadCapablePlaceable
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.unit.Constraints
@@ -105,6 +106,24 @@
     )
 
     /**
+     * Positions the [Placeable] at [position] in its parent's coordinate system.
+     *
+     * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+     * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+     * have the same [zIndex] the order in which the items were placed is used.
+     * @param layer [GraphicsLayer] to place this placeable with. If the [Placeable] will be
+     * placed with a new [position] next time only the graphic layer will be moved without
+     * requiring to redrawn the [Placeable] content.
+     */
+    protected open fun placeAt(
+        position: IntOffset,
+        zIndex: Float,
+        layer: GraphicsLayer
+    ) {
+        placeAt(position, zIndex, null)
+    }
+
+    /**
      * The constraints used for the measurement made to obtain this [Placeable].
      */
     protected var measurementConstraints: Constraints = DefaultConstraints
@@ -331,6 +350,98 @@
             layerBlock: GraphicsLayerScope.() -> Unit = DefaultLayerBlock
         ) = placeApparentToRealOffset(position, zIndex, layerBlock)
 
+        /**
+         * Place a [Placeable] at [x], [y] in its parent's coordinate system with an introduced
+         * graphic layer.
+         * Unlike [placeRelative], the given position will not implicitly react in RTL layout direction
+         * contexts.
+         *
+         * @param x x coordinate in the parent's coordinate system.
+         * @param y y coordinate in the parent's coordinate system.
+         * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+         * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+         * have the same [zIndex] the order in which the items were placed is used.
+         * @param layer [GraphicsLayer] to place this placeable with. If the [Placeable] will be
+         * placed with a new [x] or [y] next time only the graphic layer will be moved without
+         * requiring to redrawn the [Placeable] content.
+         */
+        fun Placeable.placeWithLayer(
+            x: Int,
+            y: Int,
+            layer: GraphicsLayer,
+            zIndex: Float = 0f,
+        ) = placeApparentToRealOffset(IntOffset(x, y), zIndex, layer)
+
+        /**
+         * Place a [Placeable] at [position] in its parent's coordinate system with an introduced
+         * graphic layer.
+         * Unlike [placeRelative], the given [position] will not implicitly react in RTL layout direction
+         * contexts.
+         *
+         * @param position position it parent's coordinate system.
+         * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+         * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+         * have the same [zIndex] the order in which the items were placed is used.
+         * @param layer [GraphicsLayer] to place this placeable with. If the [Placeable] will be
+         * placed with a new [position] next time only the graphic layer will be moved without
+         * requiring to redrawn the [Placeable] content.
+         */
+        fun Placeable.placeWithLayer(
+            position: IntOffset,
+            layer: GraphicsLayer,
+            zIndex: Float = 0f,
+        ) = placeApparentToRealOffset(position, zIndex, layer)
+
+        /**
+         * Place a [Placeable] at [x], [y] in its parent's coordinate system with an introduced
+         * graphic layer.
+         * If the layout direction is right-to-left, the given position will be horizontally
+         * mirrored so that the position of the [Placeable] implicitly reacts to RTL layout
+         * direction contexts.
+         * If this method is used outside the [MeasureScope.layout] positioning block, the
+         * automatic position mirroring will not happen and the [Placeable] will be placed at the
+         * given position, similar to the [place] method.
+         *
+         * @param x x coordinate in the parent's coordinate system.
+         * @param y y coordinate in the parent's coordinate system.
+         * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+         * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+         * have the same [zIndex] the order in which the items were placed is used.
+         * @param layer [GraphicsLayer] to place this placeable with. If the [Placeable] will be
+         * placed with a new [x] or [y] next time only the graphic layer will be moved without
+         * requiring to redrawn the [Placeable] content.
+         */
+        fun Placeable.placeRelativeWithLayer(
+            x: Int,
+            y: Int,
+            layer: GraphicsLayer,
+            zIndex: Float = 0f
+        ) = placeAutoMirrored(IntOffset(x, y), zIndex, layer)
+
+        /**
+         * Place a [Placeable] at [position] in its parent's coordinate system with an introduced
+         * graphic layer.
+         * If the layout direction is right-to-left, the given [position] will be horizontally
+         * mirrored so that the position of the [Placeable] implicitly reacts to RTL layout
+         * direction contexts.
+         * If this method is used outside the [MeasureScope.layout] positioning block, the
+         * automatic position mirroring will not happen and the [Placeable] will be placed at the
+         * given [position], similar to the [place] method.
+         *
+         * @param position position it parent's coordinate system.
+         * @param zIndex controls the drawing order for the [Placeable]. A [Placeable] with larger
+         * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
+         * have the same [zIndex] the order in which the items were placed is used.
+         * @param layer [GraphicsLayer] to place this placeable with. If the [Placeable] will be
+         * placed with a new [position] next time only the graphic layer will be moved without
+         * requiring to redrawn the [Placeable] content.
+         */
+        fun Placeable.placeRelativeWithLayer(
+            position: IntOffset,
+            layer: GraphicsLayer,
+            zIndex: Float = 0f
+        ) = placeAutoMirrored(position, zIndex, layer)
+
         @Suppress("NOTHING_TO_INLINE")
         internal inline fun Placeable.placeAutoMirrored(
             position: IntOffset,
@@ -349,13 +460,39 @@
         }
 
         @Suppress("NOTHING_TO_INLINE")
+        internal inline fun Placeable.placeAutoMirrored(
+            position: IntOffset,
+            zIndex: Float,
+            layer: GraphicsLayer
+        ) {
+            if (parentLayoutDirection == LayoutDirection.Ltr || parentWidth == 0) {
+                placeApparentToRealOffset(position, zIndex, layer)
+            } else {
+                placeApparentToRealOffset(
+                    IntOffset((parentWidth - width - position.x), position.y),
+                    zIndex,
+                    layer
+                )
+            }
+        }
+
+        @Suppress("NOTHING_TO_INLINE")
         internal inline fun Placeable.placeApparentToRealOffset(
             position: IntOffset,
             zIndex: Float,
-            noinline layerBlock: (GraphicsLayerScope.() -> Unit)?
+            noinline layerBlock: (GraphicsLayerScope.() -> Unit)?,
         ) {
             placeAt(position + apparentToRealOffset, zIndex, layerBlock)
         }
+
+        @Suppress("NOTHING_TO_INLINE")
+        internal inline fun Placeable.placeApparentToRealOffset(
+            position: IntOffset,
+            zIndex: Float,
+            layer: GraphicsLayer
+        ) {
+            placeAt(position + apparentToRealOffset, zIndex, layer)
+        }
     }
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
index 25ba62db..26d852c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerNodeCoordinator.kt
@@ -154,10 +154,22 @@
     override fun placeAt(
         position: IntOffset,
         zIndex: Float,
-        layerBlock: (GraphicsLayerScope.() -> Unit)?
+        layer: GraphicsLayer
+    ) {
+        super.placeAt(position, zIndex, layer)
+        onAfterPlaceAt()
+    }
+
+    override fun placeAt(
+        position: IntOffset,
+        zIndex: Float,
+        layerBlock: (GraphicsLayerScope.() -> Unit)?,
     ) {
         super.placeAt(position, zIndex, layerBlock)
+        onAfterPlaceAt()
+    }
 
+    private fun onAfterPlaceAt() {
         // The coordinator only runs their placement block to obtain our position, which allows them
         // to calculate the offset of an alignment line we have already provided a position for.
         // No need to place our wrapped as well (we might have actually done this already in
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
index b1ce10a..e491fd1e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutModifierNodeCoordinator.kt
@@ -231,9 +231,22 @@
     override fun placeAt(
         position: IntOffset,
         zIndex: Float,
+        layer: GraphicsLayer
+    ) {
+        super.placeAt(position, zIndex, layer)
+        onAfterPlaceAt()
+    }
+
+    override fun placeAt(
+        position: IntOffset,
+        zIndex: Float,
         layerBlock: (GraphicsLayerScope.() -> Unit)?
     ) {
         super.placeAt(position, zIndex, layerBlock)
+        onAfterPlaceAt()
+    }
+
+    private fun onAfterPlaceAt() {
         // The coordinator only runs their placement block to obtain our position, which allows them
         // to calculate the offset of an alignment line we have already provided a position for.
         // No need to place our wrapped as well (we might have actually done this already in
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 353991f..25cc805 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -1387,6 +1387,7 @@
             // we don't need to reset state as it was done when deactivated
         } else {
             resetModifierState()
+            resetExplicitLayers()
         }
         // resetModifierState detaches all nodes, so we need to re-attach them upon reuse.
         semanticsId = generateSemanticsId()
@@ -1404,6 +1405,13 @@
         if (isAttached) {
             invalidateSemantics()
         }
+        resetExplicitLayers()
+    }
+
+    private fun resetExplicitLayers() {
+        forEachCoordinatorIncludingInner {
+            it.releaseExplicitLayer()
+        }
     }
 
     override fun onRelease() {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
index ba34e9c5..32679aa 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeDrawScope.kt
@@ -34,7 +34,7 @@
  */
 @OptIn(ExperimentalComposeUiApi::class)
 internal class LayoutNodeDrawScope(
-    private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
+    val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
 ) : DrawScope by canvasDrawScope, ContentDrawScope {
 
     // NOTE, currently a single ComponentDrawScope is shared across composables
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
index 282375b..c0b271a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.ui.graphics.GraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.internal.checkPrecondition
 import androidx.compose.ui.internal.checkPreconditionNotNull
 import androidx.compose.ui.internal.requirePrecondition
@@ -319,6 +320,7 @@
 
         private var lastPosition: IntOffset = IntOffset.Zero
         private var lastLayerBlock: (GraphicsLayerScope.() -> Unit)? = null
+        private var lastExplicitLayer: GraphicsLayer? = null
         private var lastZIndex: Float = 0f
 
         private var parentDataDirty: Boolean = true
@@ -480,6 +482,7 @@
 
         // Used by placeOuterBlock to avoid allocating the lambda on every call
         private var placeOuterCoordinatorLayerBlock: (GraphicsLayerScope.() -> Unit)? = null
+        private var placeOuterCoordinatorLayer: GraphicsLayer? = null
         private var placeOuterCoordinatorPosition = IntOffset.Zero
         private var placeOuterCoordinatorZIndex = 0f
 
@@ -488,7 +491,14 @@
                 ?: layoutNode.requireOwner().placementScope
             with(scope) {
                 val layerBlock = placeOuterCoordinatorLayerBlock
-                if (layerBlock == null) {
+                val layer = placeOuterCoordinatorLayer
+                if (layer != null) {
+                    outerCoordinator.placeWithLayer(
+                        placeOuterCoordinatorPosition,
+                        layer,
+                        placeOuterCoordinatorZIndex
+                    )
+                } else if (layerBlock == null) {
                     outerCoordinator.place(
                         placeOuterCoordinatorPosition,
                         placeOuterCoordinatorZIndex
@@ -689,6 +699,23 @@
             zIndex: Float,
             layerBlock: (GraphicsLayerScope.() -> Unit)?
         ) {
+            placeSelf(position, zIndex, layerBlock, null)
+        }
+
+        override fun placeAt(
+            position: IntOffset,
+            zIndex: Float,
+            layer: GraphicsLayer
+        ) {
+            placeSelf(position, zIndex, null, layer)
+        }
+
+        private fun placeSelf(
+            position: IntOffset,
+            zIndex: Float,
+            layerBlock: (GraphicsLayerScope.() -> Unit)?,
+            layer: GraphicsLayer?
+        ) {
             isPlacedByParent = true
             if (position != lastPosition) {
                 if (coordinatesAccessedDuringModifierPlacement ||
@@ -724,13 +751,14 @@
             }
 
             // Post-lookahead (if any) placement
-            placeOuterCoordinator(position, zIndex, layerBlock)
+            placeOuterCoordinator(position, zIndex, layerBlock, layer)
         }
 
         private fun placeOuterCoordinator(
             position: IntOffset,
             zIndex: Float,
-            layerBlock: (GraphicsLayerScope.() -> Unit)?
+            layerBlock: (GraphicsLayerScope.() -> Unit)?,
+            layer: GraphicsLayer?
         ) {
             requirePrecondition(!layoutNode.isDeactivated) {
                 "place is called on a deactivated node"
@@ -740,12 +768,13 @@
             lastPosition = position
             lastZIndex = zIndex
             lastLayerBlock = layerBlock
+            lastExplicitLayer = layer
             placedOnce = true
             onNodePlacedCalled = false
 
             val owner = layoutNode.requireOwner()
             if (!layoutPending && isPlaced) {
-                outerCoordinator.placeSelfApparentToRealOffset(position, zIndex, layerBlock)
+                outerCoordinator.placeSelfApparentToRealOffset(position, zIndex, layerBlock, layer)
                 onNodePlaced()
             } else {
                 alignmentLines.usedByModifierLayout = false
@@ -753,10 +782,10 @@
                 placeOuterCoordinatorLayerBlock = layerBlock
                 placeOuterCoordinatorPosition = position
                 placeOuterCoordinatorZIndex = zIndex
+                placeOuterCoordinatorLayer = layer
                 owner.snapshotObserver.observeLayoutModifierSnapshotReads(
                     layoutNode, affectsLookahead = false, block = placeOuterCoordinatorBlock
                 )
-                placeOuterCoordinatorLayerBlock = null
             }
 
             layoutState = LayoutState.Idle
@@ -772,7 +801,7 @@
                 relayoutWithoutParentInProgress = true
                 checkPrecondition(placedOnce) { "replace called on unplaced item" }
                 val wasPlacedBefore = isPlaced
-                placeOuterCoordinator(lastPosition, lastZIndex, lastLayerBlock)
+                placeOuterCoordinator(lastPosition, lastZIndex, lastLayerBlock, lastExplicitLayer)
                 if (wasPlacedBefore && !onNodePlacedCalled) {
                     // parent should be notified that this node is not placed anymore so the
                     // children `placeOrder`s are updated.
@@ -1000,10 +1029,11 @@
             val lookaheadDelegate = checkPreconditionNotNull(lookaheadPassDelegate) {
                 "invalid lookaheadDelegate"
             }
-            placeAt(
+            placeSelf(
                 lookaheadDelegate.lastPosition,
                 lookaheadDelegate.lastZIndex,
-                lookaheadDelegate.lastLayerBlock
+                lookaheadDelegate.lastLayerBlock,
+                lookaheadDelegate.lastExplicitLayer
             )
         }
     }
@@ -1055,6 +1085,9 @@
         internal var lastLayerBlock: (GraphicsLayerScope.() -> Unit)? = null
             private set
 
+        internal var lastExplicitLayer: GraphicsLayer? = null
+            private set
+
         override var isPlaced: Boolean = false
         override val innerCoordinator: NodeCoordinator
             get() = layoutNode.innerCoordinator
@@ -1326,6 +1359,23 @@
             zIndex: Float,
             layerBlock: (GraphicsLayerScope.() -> Unit)?
         ) {
+            placeSelf(position, zIndex, layerBlock, null)
+        }
+
+        override fun placeAt(
+            position: IntOffset,
+            zIndex: Float,
+            layer: GraphicsLayer
+        ) {
+            placeSelf(position, zIndex, null, layer)
+        }
+
+        private fun placeSelf(
+            position: IntOffset,
+            zIndex: Float,
+            layerBlock: (GraphicsLayerScope.() -> Unit)?,
+            layer: GraphicsLayer?
+        ) {
             requirePrecondition(!layoutNode.isDeactivated) {
                 "place is called on a deactivated node"
             }
@@ -1362,6 +1412,7 @@
             lastPosition = position
             lastZIndex = zIndex
             lastLayerBlock = layerBlock
+            lastExplicitLayer = layer
             layoutState = LayoutState.Idle
         }
 
@@ -1590,7 +1641,7 @@
 
                 onNodePlacedCalled = false
                 val wasPlacedBefore = isPlaced
-                placeAt(lastPosition, 0f, null)
+                placeSelf(lastPosition, 0f, lastLayerBlock, lastExplicitLayer)
                 if (wasPlacedBefore && !onNodePlacedCalled) {
                     // parent should be notified that this node is not placed anymore so the
                     // children `placeOrder`s are updated.
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 2eaa226..df50f30 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
@@ -456,6 +456,8 @@
                 layoutNode.replace()
                 onPositionedDispatcher.onNodePositioned(layoutNode)
             }
+
+            drainPostponedMeasureRequests()
         }
         callOnLayoutCompletedListeners()
     }
@@ -568,23 +570,26 @@
                     }
                 }
             }
-            // execute postponed `onRequestMeasure`
-            if (postponedMeasureRequests.isNotEmpty()) {
-                postponedMeasureRequests.forEach { request ->
-                    if (request.node.isAttached) {
-                        if (!request.isLookahead) {
-                            requestRemeasure(request.node, request.isForced)
-                        } else {
-                            requestLookaheadRemeasure(request.node, request.isForced)
-                        }
-                    }
-                }
-                postponedMeasureRequests.clear()
-            }
+            drainPostponedMeasureRequests()
         }
         return sizeChanged
     }
 
+    private fun drainPostponedMeasureRequests() {
+        if (postponedMeasureRequests.isNotEmpty()) {
+            postponedMeasureRequests.forEach { request ->
+                if (request.node.isAttached) {
+                    if (!request.isLookahead) {
+                        requestRemeasure(request.node, request.isForced)
+                    } else {
+                        requestLookaheadRemeasure(request.node, request.isForced)
+                    }
+                }
+            }
+            postponedMeasureRequests.clear()
+        }
+    }
+
     /**
      * Remeasures [layoutNode] if it has [LayoutNode.measurePending] or
      * [LayoutNode.lookaheadMeasurePending].
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 dac2206..380b4b4 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
@@ -34,6 +34,7 @@
 import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.internal.checkPrecondition
 import androidx.compose.ui.internal.checkPreconditionNotNull
+import androidx.compose.ui.internal.requirePrecondition
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.LookaheadLayoutCoordinates
@@ -145,7 +146,12 @@
         get() = wrapped
 
     override fun replace() {
-        placeAt(position, zIndex, layerBlock)
+        val explicitLayer = explicitLayer
+        if (explicitLayer != null) {
+            placeAt(position, zIndex, explicitLayer)
+        } else {
+            placeAt(position, zIndex, layerBlock)
+        }
     }
 
     override val hasMeasureResult: Boolean
@@ -216,7 +222,9 @@
             wrappedBy?.invalidateLayer()
         }
         measuredSize = IntSize(width, height)
-        updateLayerParameters(invokeOnLayoutChange = false)
+        if (layerBlock != null) {
+            updateLayerParameters(invokeOnLayoutChange = false)
+        }
         visitNodes(Nodes.Draw) {
             it.onMeasureResultChanged()
         }
@@ -313,18 +321,56 @@
         layerBlock: (GraphicsLayerScope.() -> Unit)?
     ) {
         if (forcePlaceWithLookaheadOffset) {
-            placeSelf(lookaheadDelegate!!.position, zIndex, layerBlock)
+            placeSelf(lookaheadDelegate!!.position, zIndex, layerBlock, null)
         } else {
-            placeSelf(position, zIndex, layerBlock)
+            placeSelf(position, zIndex, layerBlock, null)
+        }
+    }
+
+    override fun placeAt(
+        position: IntOffset,
+        zIndex: Float,
+        layer: GraphicsLayer
+    ) {
+        if (forcePlaceWithLookaheadOffset) {
+            placeSelf(lookaheadDelegate!!.position, zIndex, null, layer)
+        } else {
+            placeSelf(position, zIndex, null, layer)
         }
     }
 
     private fun placeSelf(
         position: IntOffset,
         zIndex: Float,
-        layerBlock: (GraphicsLayerScope.() -> Unit)?
+        layerBlock: (GraphicsLayerScope.() -> Unit)?,
+        explicitLayer: GraphicsLayer?
     ) {
-        updateLayerBlock(layerBlock)
+        if (explicitLayer != null) {
+            requirePrecondition(layerBlock == null) {
+                "both ways to create layers shouldn't be used together"
+            }
+            if (this.explicitLayer !== explicitLayer) {
+                // reset previous layer object first if the explicitLayer changed
+                this.explicitLayer = null
+                updateLayerBlock(null)
+                this.explicitLayer = explicitLayer
+            }
+            if (layer == null) {
+                layer = layoutNode.requireOwner().createLayer(
+                    drawBlock,
+                    invalidateParentLayer,
+                    explicitLayer
+                ).apply {
+                    resize(measuredSize)
+                    move(position)
+                }
+                layoutNode.innerLayerCoordinatorIsDirty = true
+                invalidateParentLayer()
+            }
+        } else {
+            releaseExplicitLayer()
+            updateLayerBlock(layerBlock)
+        }
         if (this.position != position) {
             this.position = position
             layoutNode.layoutDelegate.measurePassDelegate
@@ -344,12 +390,20 @@
         }
     }
 
+    fun releaseExplicitLayer() {
+        if (explicitLayer != null) {
+            explicitLayer = null
+            updateLayerBlock(null)
+        }
+    }
+
     fun placeSelfApparentToRealOffset(
         position: IntOffset,
         zIndex: Float,
-        layerBlock: (GraphicsLayerScope.() -> Unit)?
+        layerBlock: (GraphicsLayerScope.() -> Unit)?,
+        layer: GraphicsLayer?
     ) {
-        placeSelf(position + apparentToRealOffset, zIndex, layerBlock)
+        placeSelf(position + apparentToRealOffset, zIndex, layerBlock, layer)
     }
 
     /**
@@ -358,9 +412,7 @@
     fun draw(canvas: Canvas, graphicsLayer: GraphicsLayer?) {
         val layer = layer
         if (layer != null) {
-            // todo graphicsLayer should be used as a parent layer here when we migrate
-            //  the local implementation to the new implementation.
-            layer.drawLayer(canvas)
+            layer.drawLayer(canvas, graphicsLayer)
         } else {
             val x = position.x.toFloat()
             val y = position.y.toFloat()
@@ -395,8 +447,7 @@
     private val drawBlock: (Canvas) -> Unit = { canvas ->
         if (layoutNode.isPlaced) {
             snapshotObserver.observeReads(this, onCommitAffectingLayer) {
-                // todo local layers will be passing the reference here when we migrate them.
-                drawContainedDrawModifiers(canvas, null)
+                drawContainedDrawModifiers(canvas, explicitLayer)
             }
             lastLayerDrawingWasSkipped = false
         } else {
@@ -411,14 +462,17 @@
         layerBlock: (GraphicsLayerScope.() -> Unit)?,
         forceUpdateLayerParameters: Boolean = false
     ) {
+        requirePrecondition(layerBlock == null || explicitLayer == null) {
+            "layerBlock can't be provided when explicitLayer is provided"
+        }
         val layoutNode = layoutNode
         val updateParameters = forceUpdateLayerParameters || this.layerBlock !== layerBlock ||
             layerDensity != layoutNode.density || layerLayoutDirection != layoutNode.layoutDirection
-        this.layerBlock = layerBlock
         this.layerDensity = layoutNode.density
         this.layerLayoutDirection = layoutNode.layoutDirection
 
         if (layoutNode.isAttached && layerBlock != null) {
+            this.layerBlock = layerBlock
             if (layer == null) {
                 layer = layoutNode.requireOwner().createLayer(
                     drawBlock,
@@ -434,6 +488,7 @@
                 updateLayerParameters()
             }
         } else {
+            this.layerBlock = null
             layer?.let {
                 it.destroy()
                 layoutNode.innerLayerCoordinatorIsDirty = true
@@ -448,6 +503,10 @@
     }
 
     private fun updateLayerParameters(invokeOnLayoutChange: Boolean = true) {
+        if (explicitLayer != null) {
+            // the parameters of the explicit layers are configured differently.
+            return
+        }
         val layer = layer
         if (layer != null) {
             val layerBlock = checkPreconditionNotNull(layerBlock) {
@@ -491,6 +550,8 @@
     var layer: OwnedLayer? = null
         private set
 
+    private var explicitLayer: GraphicsLayer? = null
+
     override val isValidOwnerScope: Boolean
         get() = layer != null && !released && layoutNode.isAttached
 
@@ -955,6 +1016,7 @@
      * released or when the [NodeCoordinator] is released (will not be used anymore).
      */
     fun onRelease() {
+        releaseExplicitLayer()
         released = true
         // It is important to call invalidateParentLayer() here, even though updateLayerBlock() may
         // call it. The reason is because we end up calling this from the bottom up, which means
@@ -1210,6 +1272,7 @@
             if (coordinator.isValidOwnerScope) {
                 // coordinator.layerPositionalProperties should always be non-null here, but
                 // we'll just be careful with a null check.
+                // todo how this will be communicated to us in the new impl?
                 val layerPositionalProperties = coordinator.layerPositionalProperties
                 if (layerPositionalProperties == null) {
                     coordinator.updateLayerParameters()
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 f55d67f..e669d1e 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
@@ -268,6 +268,9 @@
             coordinator.onRelease()
         }
     }
+    if (Nodes.LayoutAware in selfKindSet && node is LayoutAwareModifierNode) {
+        node.requireLayoutNode().invalidateMeasurements()
+    }
     if (Nodes.GlobalPositionAware in selfKindSet && node is GlobalPositionAwareModifierNode) {
         node.requireLayoutNode().invalidateOnPositioned()
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
index a82996a..23fdbd4 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnedLayer.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -59,7 +60,7 @@
     /**
      * Causes the layer to be drawn into [canvas]
      */
-    fun drawLayer(canvas: Canvas)
+    fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?)
 
     /**
      * Updates the drawing on the current canvas.
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
index de231dd..dbc9f47 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/Owner.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.GraphicsContext
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.key.KeyEvent
@@ -254,7 +255,11 @@
     /**
      * Creates an [OwnedLayer] which will be drawing the passed [drawBlock].
      */
-    fun createLayer(drawBlock: (Canvas) -> Unit, invalidateParentLayer: () -> Unit): OwnedLayer
+    fun createLayer(
+        drawBlock: (Canvas) -> Unit,
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer? = null
+    ): OwnedLayer
 
     /**
      * The semantics have changed. This function will be called when a SemanticsNode is added to
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ClipboardManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ClipboardManager.kt
index 9100166..56b5817 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ClipboardManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/ClipboardManager.kt
@@ -45,38 +45,24 @@
     fun hasText(): Boolean = getText()?.isNotEmpty() == true
 
     /**
-     * Returns the primary clipboard entry that's provided by the platform's ClipboardManager.
+     * Returns the clipboard entry that's provided by the platform's ClipboardManager.
+     *
      * This item can include arbitrary content like images, videos, or any data that may be provided
      * through a mediator. Returned entry may contain multiple items with different types.
      *
-     * Calling this method may show a Clipboard access warning message to the user on mobile
-     * platforms since it usually accesses the Clipboard contents.
+     * It's safe to call this function without triggering Clipboard access warnings on mobile
+     * platforms.
      */
     fun getClip(): ClipEntry? = null
 
     /**
-     * Returns a [ClipMetadata] which describes the primary clip entry. This is an ideal way to
-     * check whether to accept or reject what may be pasted from the clipboard without explicitly
-     * reading the content.
-     *
-     * Calling this function does not trigger any content access warnings on any platform.
-     */
-    fun getClipMetadata(): ClipMetadata? = null
-
-    /**
      * Puts the given [clipEntry] in platform's ClipboardManager.
      *
-     * @param clipEntry Platform specific clip object that either holds data or links to it.
+     * @param clipEntry Platform specific clip object that either holds data or links to it. Pass
+     * null to clear the clipboard.
      */
     @Suppress("GetterSetterNames")
-    fun setClip(clipEntry: ClipEntry) = Unit
-
-    /**
-     * Returns true if there is currently a primary clip on the platform Clipboard. Even though
-     * [getClip] should be available immediately, [getClipMetadata] may still return null if the
-     * platform doesn't support clip descriptions.
-     */
-    fun hasClip(): Boolean = false
+    fun setClip(clipEntry: ClipEntry?) = Unit
 
     /**
      * Returns the native clipboard that exposes the full functionality of platform clipboard.
@@ -93,7 +79,17 @@
 /**
  * Platform specific protocol that expresses an item in the native Clipboard.
  */
-expect class ClipEntry
+expect class ClipEntry {
+
+    /**
+     * Returns a [ClipMetadata] which describes the contents of this [ClipEntry]. This is an ideal
+     * way to check whether to accept or reject what may be pasted from the clipboard without
+     * explicitly reading the content.
+     *
+     * Calling this function does not trigger any content access warnings on any platform.
+     */
+    fun getMetadata(): ClipMetadata
+}
 
 /**
  * Platform specific protocol that describes an item in the native Clipboard. This object should
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt
index 5ebaf46..000dd66 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsConfiguration.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.semantics
 
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.platform.simpleIdentityToString
 
 /**
@@ -71,11 +70,6 @@
         }
     }
 
-    @ExperimentalComposeUiApi
-    override fun <T> unset(key: SemanticsPropertyKey<T>) {
-       props.remove(key)
-    }
-
     operator fun <T> contains(key: SemanticsPropertyKey<T>): Boolean {
         return props.containsKey(key)
     }
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 7f7220d..eae2518 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
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextLayoutResult
@@ -98,24 +99,16 @@
     /**
      * @see SemanticsPropertyReceiver.isContainer
      */
-    @Deprecated("Use `isTraversalGroup` and `isOpaque` instead.")
+    @Deprecated("Use `isTraversalGroup` instead.",
+        replaceWith = ReplaceWith("IsTraversalGroup"),
+    )
     val IsContainer: SemanticsPropertyKey<Boolean>
         get() = IsTraversalGroup
 
     /**
      * @see SemanticsPropertyReceiver.isTraversalGroup
      */
-    val IsTraversalGroup = SemanticsPropertyKey<Boolean>("IsTraversalGroup")
-
-    /**
-     * @see SemanticsPropertyReceiver.isOpaque
-     */
-    val IsOpaque = AccessibilityKey<Unit>(
-        name = "IsOpaque",
-        mergePolicy = { parentValue, _ ->
-            parentValue
-        }
-    )
+    val IsTraversalGroup = AccessibilityKey<Boolean>("IsTraversalGroup")
 
     /**
      * @see SemanticsPropertyReceiver.invisibleToUser
@@ -131,7 +124,7 @@
     /**
      * @see SemanticsPropertyReceiver.traversalIndex
      */
-    val TraversalIndex = SemanticsPropertyKey<Float>(
+    val TraversalIndex = AccessibilityKey<Float>(
         name = "TraversalIndex",
         mergePolicy = { parentValue, _ ->
             // Never merge traversal indices
@@ -302,6 +295,11 @@
     val ScrollBy = ActionPropertyKey<(x: Float, y: Float) -> Boolean>("ScrollBy")
 
     /**
+     * @see SemanticsPropertyReceiver.scrollByOffset
+     */
+    val ScrollByOffset = SemanticsPropertyKey<suspend (offset: Offset) -> Offset>("ScrollByOffset")
+
+    /**
      * @see SemanticsPropertyReceiver.scrollToIndex
      */
     val ScrollToIndex = ActionPropertyKey<(Int) -> Boolean>("ScrollToIndex")
@@ -823,43 +821,6 @@
  */
 interface SemanticsPropertyReceiver {
     operator fun <T> set(key: SemanticsPropertyKey<T>, value: T)
-
-    /**
-     * Unset an individual property.
-     *
-     * Note: this can only unset properties originally on the same modifier chain, not properties
-     * merged from children (for those, use [clearAndSetSemantics] instead). And because the
-     * semantics system processes modifier chains back-to-front, the unset must be ordered earlier
-     * on the modifier chain if it's in a separate `semantics {}` block.
-
-     * 1. Examples of correct uses (resulting in empty semantics):
-     *
-     *   ```
-     *   Modifier.semantics { heading(); unset(SemanticsProperties.Heading) }
-     *   Modifier.semantics { unset(SemanticsProperties.Heading) }.semantics { heading() }
-     *   ```
-     *
-     * 2. Examples of ineffective, no-op uses (where the heading remains instead of being unset):
-     *
-     *   ```
-     *   Modifier.semantics { unset(SemanticsProperties.Heading); heading() } // order
-     *   Modifier.semantics { heading() }.semantics { unset(SemanticsProperties.Heading) } // order
-     *   Box(Modifier.semantics(mergeDescendants = true) { unset(SemanticsProperties.Heading) }) {
-     *       Box(Modifier.semantics { heading() }) // not originally on the same modifier chain`
-     *   }
-     *
-     * 3. Examples of complex cases where there is more than one set:
-     *
-     *   ```
-     *   // Result is empty semantics:
-     *   Modifier.semantics { unset(SemanticsProperties.TestTag) }.testTag("b").testTag("a")
-     *
-     *   // Result is testTag = "b":`
-     *   Modifier.testTag("b").semantics { unset(SemanticsProperties.TestTag) }.testTag("a")
-     *   ```
-     */
-    @ExperimentalComposeUiApi
-    fun <T> unset(key: SemanticsPropertyKey<T>)
 }
 
 /**
@@ -952,46 +913,20 @@
  *
  * @see SemanticsProperties.IsContainer
  */
-@Deprecated("Use `isTraversalGroup` and `isOpaque` instead.")
-@get:Deprecated("Use `isTraversalGroup` and `isOpaque` instead.")
-@set:Deprecated("Use `isTraversalGroup` and `isOpaque` instead.")
-@OptIn(ExperimentalComposeUiApi::class)
-var SemanticsPropertyReceiver.isContainer: Boolean
-    get() = throwSemanticsGetNotSupported()
-    set(bool) {
-        isTraversalGroup = bool
-        if (bool) {
-            this[SemanticsProperties.IsOpaque] = Unit
-        } else {
-            unset(SemanticsProperties.IsOpaque)
-        }
-    }
+@Deprecated("Use `isTraversalGroup` instead.",
+    replaceWith = ReplaceWith("isTraversalGroup"),
+)
+var SemanticsPropertyReceiver.isContainer by SemanticsProperties.IsTraversalGroup
 
 /**
- * Whether this semantics node is a traversal group.
- *
- * See https://developer.android.com/jetpack/compose/accessibility#modify-traversal-order
+ * Whether this semantics node is a traversal group. This is defined as a node whose function
+ * is to serve as a boundary or border in organizing its children.
  *
  * @see SemanticsProperties.IsTraversalGroup
  */
 var SemanticsPropertyReceiver.isTraversalGroup by SemanticsProperties.IsTraversalGroup
 
 /**
- * Non-mergeable property used to mark that whether a node is semantically opaque.
- *
- * In other words, whether nodes fully covered by it ought to be pruned from the a11y tree.  (Note
- * that most semantic properties other than testTag also have this effect, so it should be rarely
- * needed.)
- *
- * If true, then a11y nodes behind will be pruned unless this node's graphics alpha is 0.
- *
- * @see SemanticsProperties.IsOpaque
- */
-fun SemanticsPropertyReceiver.isOpaque() {
-    this[SemanticsProperties.IsOpaque] = Unit
-}
-
-/**
  * Whether this node is specially known to be invisible to the user.
  *
  * For example, if the node is currently occluded by a dark semitransparent
@@ -1244,12 +1179,15 @@
 }
 
 /**
- * Action to scroll by a specified amount.
+ * Action to asynchronously scroll by a specified amount.
  *
- * Expected to be used in conjunction with verticalScrollAxisRange/horizontalScrollAxisRange.
+ * [scrollByOffset] should be preferred in most cases, since it is synchronous and returns the
+ * amount of scroll that was actually consumed.
+ *
+ * Expected to be used in conjunction with [verticalScrollAxisRange]/[horizontalScrollAxisRange].
  *
  * @param label Optional label for this action.
- * @param action Action to be performed when the [SemanticsActions.ScrollBy] is called.
+ * @param action Action to be performed when [SemanticsActions.ScrollBy] is called.
  */
 fun SemanticsPropertyReceiver.scrollBy(
     label: String? = null,
@@ -1259,6 +1197,23 @@
 }
 
 /**
+ * Action to scroll by a specified amount and return how much of the offset was actually consumed.
+ * E.g. if the node can't scroll at all in the given direction, [Offset.Zero] should be returned.
+ * The action should not return until the scroll operation has finished.
+ *
+ * Expected to be used in conjunction with [verticalScrollAxisRange]/[horizontalScrollAxisRange].
+ *
+ * Unlike [scrollBy], this action is synchronous, and returns the amount of scroll consumed.
+ *
+ * @param action Action to be performed when [SemanticsActions.ScrollByOffset] is called.
+ */
+fun SemanticsPropertyReceiver.scrollByOffset(
+    action: suspend (offset: Offset) -> Offset
+) {
+    this[SemanticsActions.ScrollByOffset] = action
+}
+
+/**
  * Action to scroll a container to the index of one of its items.
  *
  * The [action] should throw an [IllegalArgumentException] if the index is out of bounds.
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.desktop.kt
index b909150..5d3abb7 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/PlatformClipboardManager.desktop.kt
@@ -24,7 +24,6 @@
 import java.awt.datatransfer.Transferable
 import java.awt.datatransfer.UnsupportedFlavorException
 import java.io.IOException
-import java.lang.IllegalStateException
 
 internal actual class PlatformClipboardManager : ClipboardManager {
     internal val systemClipboard = try {
@@ -55,25 +54,9 @@
         }
     }
 
-    override fun getClipMetadata(): ClipMetadata? {
-        return try {
-            systemClipboard?.getContents(this)?.let(::ClipMetadata)
-        } catch (_: IllegalStateException) {
-            null
-        }
-    }
-
-    override fun setClip(clipEntry: ClipEntry) {
+    override fun setClip(clipEntry: ClipEntry?) {
         // Ignore clipDescription.
-        systemClipboard?.setContents(clipEntry.transferable, null)
-    }
-
-    override fun hasClip(): Boolean {
-        return try {
-            systemClipboard?.availableDataFlavors?.isNotEmpty() ?: false
-        } catch (_: IllegalStateException) {
-            false
-        }
+        systemClipboard?.setContents(clipEntry?.transferable, null)
     }
 
     /**
@@ -96,6 +79,10 @@
     fun getTransferData(flavor: DataFlavor): Any? {
         return transferable.getTransferData(flavor)
     }
+
+    actual fun getMetadata(): ClipMetadata {
+        return ClipMetadata(transferable)
+    }
 }
 
 // Defining this class not as a typealias but a wrapper gives us flexibility in the future to
diff --git a/compose/ui/ui/src/main/java/androidx/compose/ui/platform/coreshims/ViewStructureCompat.java b/compose/ui/ui/src/main/java/androidx/compose/ui/platform/coreshims/ViewStructureCompat.java
index 1dd6bdb..1052324 100644
--- a/compose/ui/ui/src/main/java/androidx/compose/ui/platform/coreshims/ViewStructureCompat.java
+++ b/compose/ui/ui/src/main/java/androidx/compose/ui/platform/coreshims/ViewStructureCompat.java
@@ -75,6 +75,27 @@
     }
 
     /**
+     * Set the identifier for this view.
+     *
+     * @param id The view's identifier, as per {@link android.view.View#getId View.getId()}.
+     * @param packageName The package name of the view's identifier, or null if there is none.
+     * @param typeName The type name of the view's identifier, or null if there is none.
+     * @param entryName The entry name of the view's identifier, or null if there is none.
+     *
+     * Compatibility behavior:
+     * <ul>
+     * <li>SDK 23 and above, this method matches platform behavior.
+     * <li>SDK 22 and below, this method does nothing.
+     * </ul>
+     */
+    public void setId(int id, @Nullable String packageName, @Nullable String typeName,
+            @Nullable String entryName) {
+        if (SDK_INT >= 23) {
+            Api23Impl.setId((ViewStructure) mWrappedObj, id, packageName, typeName, entryName);
+        }
+    }
+
+    /**
      * Set the text that is associated with this view.  There is no selection
      * associated with the text.  The text may have style spans to supply additional
      * display and semantic information.
@@ -194,6 +215,11 @@
             // This class is not instantiable.
         }
 
+        static void setId(ViewStructure viewStructure, int id, String packageName, String typeName,
+                String entryName) {
+            viewStructure.setId(id, packageName, typeName, entryName);
+        }
+
         @DoNotInline
         static void setDimens(ViewStructure viewStructure, int left, int top, int scrollX,
                 int scrollY, int width, int height) {
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 07efa2e..bc03008 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
@@ -43,6 +43,7 @@
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.asComposeCanvas
 import androidx.compose.ui.graphics.layer.GraphicsContext
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.input.InputMode.Companion.Keyboard
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.InputModeManagerImpl
@@ -376,7 +377,8 @@
 
     override fun createLayer(
         drawBlock: (Canvas) -> Unit,
-        invalidateParentLayer: () -> Unit
+        invalidateParentLayer: () -> Unit,
+        explicitLayer: GraphicsLayer?
     ) = SkiaLayer(
         density,
         invalidateParentLayer = {
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
index 18568a9..5f8b1e8 100644
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/SkiaLayer.skiko.kt
@@ -38,6 +38,7 @@
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.asComposeCanvas
 import androidx.compose.ui.graphics.asSkiaPath
+import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.graphics.toSkiaRRect
@@ -212,7 +213,7 @@
         }
     }
 
-    override fun drawLayer(canvas: Canvas) {
+    override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
         if (picture == null) {
             val bounds = size.toSize().toRect()
             val pictureCanvas = pictureRecorder.beginRecording(bounds.toSkiaRect())
diff --git a/concurrent/concurrent-futures/api/restricted_current.txt b/concurrent/concurrent-futures/api/restricted_current.txt
index 9926fa1..32be46b 100644
--- a/concurrent/concurrent-futures/api/restricted_current.txt
+++ b/concurrent/concurrent-futures/api/restricted_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.concurrent.futures {
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements com.google.common.util.concurrent.ListenableFuture<V> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AbstractResolvableFuture<V> implements com.google.common.util.concurrent.ListenableFuture<V!> {
     ctor protected AbstractResolvableFuture();
     method public final void addListener(Runnable!, java.util.concurrent.Executor!);
     method protected void afterDone();
@@ -34,7 +34,7 @@
     method public Object? attachCompleter(androidx.concurrent.futures.CallbackToFutureAdapter.Completer<T!>) throws java.lang.Exception;
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ResolvableFuture<V> extends androidx.concurrent.futures.AbstractResolvableFuture<V!> {
     method public static <V> androidx.concurrent.futures.ResolvableFuture<V!> create();
     method public boolean set(V?);
     method public boolean setException(Throwable);
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/build.gradle b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/build.gradle
index 488e08f..2f9c076 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/build.gradle
+++ b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark/build.gradle
@@ -26,8 +26,6 @@
     }
     namespace "androidx.constraintlayout.compose.integration.macrobenchmark"
 
-    // We need animations to work for MotionLayout
-    testOptions.animationsDisabled  false
     targetProjectPath = ":constraintlayout:constraintlayout-compose:integration-tests:macrobenchmark-target"
     experimentalProperties["android.experimental.self-instrumenting"] = true
 }
diff --git a/constraintlayout/constraintlayout-core/api/current.txt b/constraintlayout/constraintlayout-core/api/current.txt
index 6d82925..184a753 100644
--- a/constraintlayout/constraintlayout-core/api/current.txt
+++ b/constraintlayout/constraintlayout-core/api/current.txt
@@ -182,7 +182,7 @@
     ctor public PriorityGoalRow(androidx.constraintlayout.core.Cache!);
   }
 
-  public class SolverVariable implements java.lang.Comparable<androidx.constraintlayout.core.SolverVariable> {
+  public class SolverVariable implements java.lang.Comparable<androidx.constraintlayout.core.SolverVariable!> {
     ctor public SolverVariable(androidx.constraintlayout.core.SolverVariable.Type!, String!);
     ctor public SolverVariable(String!, androidx.constraintlayout.core.SolverVariable.Type!);
     method public final void addToRow(androidx.constraintlayout.core.ArrayRow!);
@@ -954,7 +954,7 @@
     field public String! mId;
   }
 
-  public class MotionPaths implements java.lang.Comparable<androidx.constraintlayout.core.motion.MotionPaths> {
+  public class MotionPaths implements java.lang.Comparable<androidx.constraintlayout.core.motion.MotionPaths!> {
     ctor public MotionPaths();
     ctor public MotionPaths(int, int, androidx.constraintlayout.core.motion.key.MotionKeyPosition!, androidx.constraintlayout.core.motion.MotionPaths!, androidx.constraintlayout.core.motion.MotionPaths!);
     method public void applyParameters(androidx.constraintlayout.core.motion.MotionWidget!);
@@ -1953,7 +1953,7 @@
     method public void putValue(float);
   }
 
-  public class CLObject extends androidx.constraintlayout.core.parser.CLContainer implements java.lang.Iterable<androidx.constraintlayout.core.parser.CLKey> {
+  public class CLObject extends androidx.constraintlayout.core.parser.CLContainer implements java.lang.Iterable<androidx.constraintlayout.core.parser.CLKey!> {
     ctor public CLObject(char[]!);
     method public static androidx.constraintlayout.core.parser.CLObject! allocate(char[]!);
     method public androidx.constraintlayout.core.parser.CLObject clone();
diff --git a/constraintlayout/constraintlayout-core/api/restricted_current.txt b/constraintlayout/constraintlayout-core/api/restricted_current.txt
index 5975f5e..0b2ac8fe 100644
--- a/constraintlayout/constraintlayout-core/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout-core/api/restricted_current.txt
@@ -182,7 +182,7 @@
     ctor public PriorityGoalRow(androidx.constraintlayout.core.Cache!);
   }
 
-  public class SolverVariable implements java.lang.Comparable<androidx.constraintlayout.core.SolverVariable> {
+  public class SolverVariable implements java.lang.Comparable<androidx.constraintlayout.core.SolverVariable!> {
     ctor public SolverVariable(androidx.constraintlayout.core.SolverVariable.Type!, String!);
     ctor public SolverVariable(String!, androidx.constraintlayout.core.SolverVariable.Type!);
     method public final void addToRow(androidx.constraintlayout.core.ArrayRow!);
@@ -954,7 +954,7 @@
     field public String! mId;
   }
 
-  public class MotionPaths implements java.lang.Comparable<androidx.constraintlayout.core.motion.MotionPaths> {
+  public class MotionPaths implements java.lang.Comparable<androidx.constraintlayout.core.motion.MotionPaths!> {
     ctor public MotionPaths();
     ctor public MotionPaths(int, int, androidx.constraintlayout.core.motion.key.MotionKeyPosition!, androidx.constraintlayout.core.motion.MotionPaths!, androidx.constraintlayout.core.motion.MotionPaths!);
     method public void applyParameters(androidx.constraintlayout.core.motion.MotionWidget!);
@@ -1953,7 +1953,7 @@
     method public void putValue(float);
   }
 
-  public class CLObject extends androidx.constraintlayout.core.parser.CLContainer implements java.lang.Iterable<androidx.constraintlayout.core.parser.CLKey> {
+  public class CLObject extends androidx.constraintlayout.core.parser.CLContainer implements java.lang.Iterable<androidx.constraintlayout.core.parser.CLKey!> {
     ctor public CLObject(char[]!);
     method public static androidx.constraintlayout.core.parser.CLObject! allocate(char[]!);
     method public androidx.constraintlayout.core.parser.CLObject clone();
diff --git a/constraintlayout/constraintlayout/build.gradle b/constraintlayout/constraintlayout/build.gradle
index 7bafbac..93842ed 100644
--- a/constraintlayout/constraintlayout/build.gradle
+++ b/constraintlayout/constraintlayout/build.gradle
@@ -33,7 +33,7 @@
     implementation("androidx.appcompat:appcompat:1.2.0")
     implementation("androidx.core:core:1.3.2")
     implementation(project(":constraintlayout:constraintlayout-core"))
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
     testImplementation(libs.junit)
 
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
index d9acf2e..6db7c6d 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
@@ -1998,7 +1998,6 @@
      * </p>
      *
      * @param level optimization level
-     * @since 1.1
      */
     public void setOptimizationLevel(int level) {
         mOptimizationLevel = level;
@@ -2009,7 +2008,6 @@
      * Return the current optimization level for the layout resolution
      *
      * @return the current level
-     * @since 1.1
      */
     public int getOptimizationLevel() {
         return mLayoutWidget.getOptimizationLevel();
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 9847916..f633da5a 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintSet.java
@@ -3843,7 +3843,6 @@
      *
      * @param viewId ID of view to adjust its height
      * @param height the height of the view
-     * @since 1.1
      */
     public void constrainHeight(int viewId, int height) {
         get(viewId).layout.mHeight = height;
@@ -3855,7 +3854,6 @@
      *
      * @param viewId ID of view to adjust its width
      * @param width  the width of the view
-     * @since 1.1
      */
     public void constrainWidth(int viewId, int width) {
         get(viewId).layout.mWidth = width;
@@ -3868,7 +3866,6 @@
      * @param id     ID of the view we constrain relative to
      * @param radius the radius of the circle in degrees
      * @param angle  the angle
-     * @since 1.1
      */
     public void constrainCircle(int viewId, int id, int radius, float angle) {
         Constraint constraint = get(viewId);
@@ -3883,7 +3880,6 @@
      *
      * @param viewId ID of view to adjust it height
      * @param height the maximum height of the constraint
-     * @since 1.1
      */
     public void constrainMaxHeight(int viewId, int height) {
         get(viewId).layout.heightMax = height;
@@ -3895,7 +3891,6 @@
      *
      * @param viewId ID of view to adjust its max height
      * @param width  the maximum width of the view
-     * @since 1.1
      */
     public void constrainMaxWidth(int viewId, int width) {
         get(viewId).layout.widthMax = width;
@@ -3907,7 +3902,6 @@
      *
      * @param viewId ID of view to adjust its min height
      * @param height the minimum height of the view
-     * @since 1.1
      */
     public void constrainMinHeight(int viewId, int height) {
         get(viewId).layout.heightMin = height;
@@ -3919,7 +3913,6 @@
      *
      * @param viewId ID of view to adjust its min height
      * @param width  the minimum width of the view
-     * @since 1.1
      */
     public void constrainMinWidth(int viewId, int width) {
         get(viewId).layout.widthMin = width;
@@ -3930,7 +3923,6 @@
      *
      * @param viewId
      * @param percent
-     * @since 1.1
      */
     public void constrainPercentWidth(int viewId, float percent) {
         get(viewId).layout.widthPercent = percent;
@@ -3941,7 +3933,6 @@
      *
      * @param viewId
      * @param percent
-     * @since 1.1
      */
     public void constrainPercentHeight(int viewId, float percent) {
         get(viewId).layout.heightPercent = percent;
@@ -3953,7 +3944,6 @@
      *
      * @param viewId ID of view to adjust its matchConstraintDefaultHeight
      * @param height MATCH_CONSTRAINT_WRAP or MATCH_CONSTRAINT_SPREAD
-     * @since 1.1
      */
     public void constrainDefaultHeight(int viewId, int height) {
         get(viewId).layout.heightDefault = height;
@@ -3965,7 +3955,6 @@
      *
      * @param viewId      ID of view to adjust its matchConstraintDefaultWidth
      * @param constrained if true with will be constrained
-     * @since 1.1
      */
     public void constrainedWidth(int viewId, boolean constrained) {
         get(viewId).layout.constrainedWidth = constrained;
@@ -3977,7 +3966,6 @@
      *
      * @param viewId      ID of view to adjust its matchConstraintDefaultHeight
      * @param constrained if true height will be constrained
-     * @since 1.1
      */
     public void constrainedHeight(int viewId, boolean constrained) {
         get(viewId).layout.constrainedHeight = constrained;
@@ -3989,7 +3977,6 @@
      *
      * @param viewId ID of view to adjust its matchConstraintDefaultWidth
      * @param width  SPREAD or WRAP
-     * @since 1.1
      */
     public void constrainDefaultWidth(int viewId, int width) {
         get(viewId).layout.widthDefault = width;
@@ -4216,7 +4203,6 @@
      * @param id
      * @param direction  Barrier.{LEFT,RIGHT,TOP,BOTTOM,START,END}
      * @param referenced
-     * @since 1.1
      */
     public void createBarrier(int id, int direction, int margin, int... referenced) {
         Constraint constraint = get(id);
@@ -4285,7 +4271,6 @@
      *
      * @param id
      * @param referenced
-     * @since 2.0
      */
     public void setReferencedIds(int id, int... referenced) {
         Constraint constraint = get(id);
diff --git a/core/core-animation/api/current.txt b/core/core-animation/api/current.txt
index 71e774e..54cc9b1 100644
--- a/core/core-animation/api/current.txt
+++ b/core/core-animation/api/current.txt
@@ -122,12 +122,12 @@
     method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
   }
 
-  public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+  public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer!> {
     method public Integer evaluate(float, Integer, Integer);
     method public static androidx.core.animation.ArgbEvaluator getInstance();
   }
 
-  public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T,V> {
+  public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T!,V!> {
     ctor public BidirectionalTypeConverter(Class<T!>, Class<V!>);
     method public abstract T convertBack(V);
     method public androidx.core.animation.BidirectionalTypeConverter<V!,T!> invert();
@@ -151,36 +151,36 @@
     method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
   }
 
-  public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]> {
+  public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]!> {
     ctor public FloatArrayEvaluator();
     ctor public FloatArrayEvaluator(float[]?);
     method public float[] evaluate(float, float[], float[]);
   }
 
-  public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float> {
+  public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float!> {
     method public Float evaluate(float, Float, Float);
     method public static androidx.core.animation.FloatEvaluator getInstance();
   }
 
-  public abstract class FloatProperty<T> extends android.util.Property<T,java.lang.Float> {
+  public abstract class FloatProperty<T> extends android.util.Property<T!,java.lang.Float!> {
     ctor public FloatProperty();
     ctor public FloatProperty(String);
     method public final void set(T, Float);
     method public abstract void setValue(T, float);
   }
 
-  public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]> {
+  public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]!> {
     ctor public IntArrayEvaluator();
     ctor public IntArrayEvaluator(int[]?);
     method public int[] evaluate(float, int[], int[]);
   }
 
-  public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+  public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer!> {
     method public Integer evaluate(float, Integer, Integer);
     method public static androidx.core.animation.IntEvaluator getInstance();
   }
 
-  public abstract class IntProperty<T> extends android.util.Property<T,java.lang.Integer> {
+  public abstract class IntProperty<T> extends android.util.Property<T!,java.lang.Integer!> {
     ctor public IntProperty();
     ctor public IntProperty(String);
     method public final void set(T, Integer);
@@ -265,7 +265,7 @@
     method public float getInterpolation(@FloatRange(from=0, to=1) float);
   }
 
-  public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF> {
+  public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF!> {
     ctor public PointFEvaluator();
     ctor public PointFEvaluator(android.graphics.PointF);
     method public android.graphics.PointF evaluate(float, android.graphics.PointF, android.graphics.PointF);
@@ -303,7 +303,7 @@
     method public void setPropertyName(String);
   }
 
-  public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect> {
+  public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect!> {
     ctor public RectEvaluator();
     ctor public RectEvaluator(android.graphics.Rect);
     method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
diff --git a/core/core-animation/api/restricted_current.txt b/core/core-animation/api/restricted_current.txt
index 71e774e..54cc9b1 100644
--- a/core/core-animation/api/restricted_current.txt
+++ b/core/core-animation/api/restricted_current.txt
@@ -122,12 +122,12 @@
     method @FloatRange(to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
   }
 
-  public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+  public final class ArgbEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer!> {
     method public Integer evaluate(float, Integer, Integer);
     method public static androidx.core.animation.ArgbEvaluator getInstance();
   }
 
-  public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T,V> {
+  public abstract class BidirectionalTypeConverter<T, V> extends androidx.core.animation.TypeConverter<T!,V!> {
     ctor public BidirectionalTypeConverter(Class<T!>, Class<V!>);
     method public abstract T convertBack(V);
     method public androidx.core.animation.BidirectionalTypeConverter<V!,T!> invert();
@@ -151,36 +151,36 @@
     method @FloatRange(from=0, to=1) public float getInterpolation(@FloatRange(from=0, to=1) float);
   }
 
-  public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]> {
+  public final class FloatArrayEvaluator implements androidx.core.animation.TypeEvaluator<float[]!> {
     ctor public FloatArrayEvaluator();
     ctor public FloatArrayEvaluator(float[]?);
     method public float[] evaluate(float, float[], float[]);
   }
 
-  public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float> {
+  public final class FloatEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Float!> {
     method public Float evaluate(float, Float, Float);
     method public static androidx.core.animation.FloatEvaluator getInstance();
   }
 
-  public abstract class FloatProperty<T> extends android.util.Property<T,java.lang.Float> {
+  public abstract class FloatProperty<T> extends android.util.Property<T!,java.lang.Float!> {
     ctor public FloatProperty();
     ctor public FloatProperty(String);
     method public final void set(T, Float);
     method public abstract void setValue(T, float);
   }
 
-  public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]> {
+  public class IntArrayEvaluator implements androidx.core.animation.TypeEvaluator<int[]!> {
     ctor public IntArrayEvaluator();
     ctor public IntArrayEvaluator(int[]?);
     method public int[] evaluate(float, int[], int[]);
   }
 
-  public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer> {
+  public class IntEvaluator implements androidx.core.animation.TypeEvaluator<java.lang.Integer!> {
     method public Integer evaluate(float, Integer, Integer);
     method public static androidx.core.animation.IntEvaluator getInstance();
   }
 
-  public abstract class IntProperty<T> extends android.util.Property<T,java.lang.Integer> {
+  public abstract class IntProperty<T> extends android.util.Property<T!,java.lang.Integer!> {
     ctor public IntProperty();
     ctor public IntProperty(String);
     method public final void set(T, Integer);
@@ -265,7 +265,7 @@
     method public float getInterpolation(@FloatRange(from=0, to=1) float);
   }
 
-  public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF> {
+  public class PointFEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.PointF!> {
     ctor public PointFEvaluator();
     ctor public PointFEvaluator(android.graphics.PointF);
     method public android.graphics.PointF evaluate(float, android.graphics.PointF, android.graphics.PointF);
@@ -303,7 +303,7 @@
     method public void setPropertyName(String);
   }
 
-  public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect> {
+  public class RectEvaluator implements androidx.core.animation.TypeEvaluator<android.graphics.Rect!> {
     ctor public RectEvaluator();
     ctor public RectEvaluator(android.graphics.Rect);
     method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
diff --git a/core/core-remoteviews/api/1.1.0-beta01.txt b/core/core-remoteviews/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..8bd3b49
--- /dev/null
+++ b/core/core-remoteviews/api/1.1.0-beta01.txt
@@ -0,0 +1,294 @@
+// Signature format: 4.0
+package androidx.core.widget {
+
+  public final class AppWidgetManagerCompat {
+    method public static android.widget.RemoteViews createExactSizeAppWidget(android.appwidget.AppWidgetManager appWidgetManager, int appWidgetId, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+    method public static android.widget.RemoteViews createResponsiveSizeAppWidget(android.appwidget.AppWidgetManager appWidgetManager, int appWidgetId, java.util.Collection<androidx.core.util.SizeFCompat> dpSizes, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+    method public static void updateAppWidget(android.appwidget.AppWidgetManager, int appWidgetId, java.util.Collection<androidx.core.util.SizeFCompat> dpSizes, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+    method public static void updateAppWidget(android.appwidget.AppWidgetManager, int appWidgetId, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+  }
+
+  public final class RemoteViewsCompat {
+    method public static void setChronometerBase(android.widget.RemoteViews, @IdRes int viewId, long base);
+    method public static void setChronometerFormat(android.widget.RemoteViews, @IdRes int viewId, String? format);
+    method @RequiresApi(31) public static void setCompoundButtonDrawable(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setCompoundButtonIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? icon);
+    method @RequiresApi(31) public static void setCompoundButtonTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? tintMode);
+    method @RequiresApi(31) public static void setCompoundButtonTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setCompoundButtonTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNight, android.content.res.ColorStateList? night);
+    method @RequiresApi(31) public static void setCompoundButtonTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setCompoundButtonTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setFrameLayoutForegroundGravity(android.widget.RemoteViews, @IdRes int viewId, int foregroundGravity);
+    method public static void setFrameLayoutMeasureAllChildren(android.widget.RemoteViews, @IdRes int viewId, boolean measureAll);
+    method @RequiresApi(31) public static void setGridLayoutAlignmentMode(android.widget.RemoteViews, @IdRes int viewId, int alignmentMode);
+    method @RequiresApi(31) public static void setGridLayoutColumnCount(android.widget.RemoteViews, @IdRes int viewId, int columnCount);
+    method @RequiresApi(31) public static void setGridLayoutRowCount(android.widget.RemoteViews, @IdRes int viewId, int rowCount);
+    method @RequiresApi(31) public static void setGridViewColumnWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setGridViewColumnWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int columnWidth);
+    method @RequiresApi(31) public static void setGridViewColumnWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int columnWidth);
+    method @RequiresApi(31) public static void setGridViewGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method @RequiresApi(31) public static void setGridViewHorizontalSpacing(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setGridViewHorizontalSpacingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setGridViewHorizontalSpacingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setGridViewNumColumns(android.widget.RemoteViews, @IdRes int viewId, int numColumns);
+    method @RequiresApi(31) public static void setGridViewStretchMode(android.widget.RemoteViews, @IdRes int viewId, int stretchMode);
+    method @RequiresApi(31) public static void setGridViewVerticalSpacing(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setGridViewVerticalSpacingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setGridViewVerticalSpacingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setImageViewAdjustViewBounds(android.widget.RemoteViews, @IdRes int viewId, boolean adjustViewBounds);
+    method public static void setImageViewColorFilter(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setImageViewColorFilter(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setImageViewColorFilterAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setImageViewColorFilterResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setImageViewImageAlpha(android.widget.RemoteViews, @IdRes int viewId, int alpha);
+    method public static void setImageViewImageLevel(android.widget.RemoteViews, @IdRes int viewId, int level);
+    method @RequiresApi(31) public static void setImageViewImageTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setImageViewImageTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setImageViewImageTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setImageViewImageTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setImageViewImageTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setImageViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int maxHeight);
+    method @RequiresApi(31) public static void setImageViewMaxHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setImageViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int maxWidth);
+    method @RequiresApi(31) public static void setImageViewMaxWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setLinearLayoutBaselineAligned(android.widget.RemoteViews, @IdRes int viewId, boolean baselineAligned);
+    method public static void setLinearLayoutBaselineAlignedChildIndex(android.widget.RemoteViews, @IdRes int viewId, int i);
+    method public static void setLinearLayoutGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method public static void setLinearLayoutHorizontalGravity(android.widget.RemoteViews, @IdRes int viewId, int horizontalGravity);
+    method public static void setLinearLayoutMeasureWithLargestChildEnabled(android.widget.RemoteViews, @IdRes int viewId, boolean enabled);
+    method public static void setLinearLayoutVerticalGravity(android.widget.RemoteViews, @IdRes int viewId, int verticalGravity);
+    method public static void setLinearLayoutWeightSum(android.widget.RemoteViews, @IdRes int viewId, float weightSum);
+    method public static void setProgressBarIndeterminate(android.widget.RemoteViews, @IdRes int viewId, boolean indeterminate);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setProgressBarMax(android.widget.RemoteViews, @IdRes int viewId, int max);
+    method @RequiresApi(26) public static void setProgressBarMin(android.widget.RemoteViews, @IdRes int viewId, int min);
+    method public static void setProgressBarProgress(android.widget.RemoteViews, @IdRes int viewId, int progress);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setProgressBarProgressTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarProgressTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarProgressTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setProgressBarSecondaryProgress(android.widget.RemoteViews, @IdRes int viewId, int secondaryProgress);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setProgressBarStateDescription(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setProgressBarStateDescription(android.widget.RemoteViews, @IdRes int viewId, CharSequence? stateDescription);
+    method @RequiresApi(31) public static void setProgressBarStateDescriptionAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setRelativeLayoutGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method public static void setRelativeLayoutHorizontalGravity(android.widget.RemoteViews, @IdRes int viewId, int horizontalGravity);
+    method public static void setRelativeLayoutIgnoreGravity(android.widget.RemoteViews, @IdRes int viewId, @IdRes int childViewId);
+    method public static void setRelativeLayoutVerticalGravity(android.widget.RemoteViews, @IdRes int viewId, int verticalGravity);
+    method public static void setRemoteAdapter(android.content.Context context, android.widget.RemoteViews remoteViews, int appWidgetId, @IdRes int viewId, androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems items);
+    method @RequiresApi(31) public static void setSwitchMinWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setSwitchMinWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setSwitchMinWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchPadding(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setSwitchPaddingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setSwitchPaddingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchShowText(android.widget.RemoteViews, @IdRes int viewId, boolean showText);
+    method @RequiresApi(31) public static void setSwitchSplitTrack(android.widget.RemoteViews, @IdRes int viewId, boolean splitTrack);
+    method @RequiresApi(31) public static void setSwitchTextOff(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setSwitchTextOff(android.widget.RemoteViews, @IdRes int viewId, CharSequence? textOff);
+    method @RequiresApi(31) public static void setSwitchTextOffAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchTextOn(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setSwitchTextOn(android.widget.RemoteViews, @IdRes int viewId, CharSequence? textOn);
+    method @RequiresApi(31) public static void setSwitchTextOnAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? icon);
+    method @RequiresApi(31) public static void setSwitchThumbIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? notNight, android.graphics.drawable.Icon? night);
+    method @RequiresApi(31) public static void setSwitchThumbResource(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTextPadding(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setSwitchThumbTextPaddingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTextPaddingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setSwitchThumbTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setSwitchThumbTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNight, android.content.res.ColorStateList? night);
+    method @RequiresApi(31) public static void setSwitchThumbTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchTrackIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? icon);
+    method @RequiresApi(31) public static void setSwitchTrackIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? notNight, android.graphics.drawable.Icon? night);
+    method @RequiresApi(31) public static void setSwitchTrackResource(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setSwitchTrackTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setSwitchTrackTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setSwitchTrackTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNight, android.content.res.ColorStateList? night);
+    method @RequiresApi(31) public static void setSwitchTrackTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setSwitchTrackTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextClockFormat12Hour(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextClockFormat12Hour(android.widget.RemoteViews, @IdRes int viewId, CharSequence? format);
+    method @RequiresApi(31) public static void setTextClockFormat12HourAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextClockFormat24Hour(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextClockFormat24Hour(android.widget.RemoteViews, @IdRes int viewId, CharSequence? format);
+    method @RequiresApi(31) public static void setTextClockFormat24HourAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextClockTimeZone(android.widget.RemoteViews, @IdRes int viewId, String? timeZone);
+    method @RequiresApi(31) public static void setTextViewAllCaps(android.widget.RemoteViews, @IdRes int viewId, boolean allCaps);
+    method public static void setTextViewAutoLinkMask(android.widget.RemoteViews, @IdRes int viewId, int mask);
+    method @RequiresApi(31) public static void setTextViewCompoundDrawablePadding(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewCompoundDrawablePadding(android.widget.RemoteViews, @IdRes int viewId, @Px int pad);
+    method @RequiresApi(31) public static void setTextViewCompoundDrawablePaddingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewCompoundDrawablePaddingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewEms(android.widget.RemoteViews, @IdRes int viewId, int ems);
+    method @RequiresApi(31) public static void setTextViewError(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextViewError(android.widget.RemoteViews, @IdRes int viewId, CharSequence? error);
+    method @RequiresApi(31) public static void setTextViewErrorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(21) public static void setTextViewFontFeatureSettings(android.widget.RemoteViews, @IdRes int viewId, String fontFeatureSettings);
+    method @RequiresApi(31) public static void setTextViewGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method @RequiresApi(31) public static void setTextViewHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int pixels);
+    method @RequiresApi(31) public static void setTextViewHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewHighlightColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewHighlightColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewHighlightColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewHighlightColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setTextViewHint(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextViewHint(android.widget.RemoteViews, @IdRes int viewId, CharSequence? hint);
+    method @RequiresApi(31) public static void setTextViewHintAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewHintTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewHintTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewHintTextColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewHintTextColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setTextViewJustificationMode(android.widget.RemoteViews, @IdRes int viewId, int justificationMode);
+    method @RequiresApi(21) public static void setTextViewLetterSpacing(android.widget.RemoteViews, @IdRes int viewId, float letterSpacing);
+    method @RequiresApi(31) public static void setTextViewLineHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setTextViewLineHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewLineHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewLines(android.widget.RemoteViews, @IdRes int viewId, int lines);
+    method public static void setTextViewLinkTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewLinkTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewLinkTextColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewLinkTextColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setTextViewLinksClickable(android.widget.RemoteViews, @IdRes int viewId, boolean whether);
+    method public static void setTextViewMaxEms(android.widget.RemoteViews, @IdRes int viewId, int maxems);
+    method @RequiresApi(31) public static void setTextViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int maxHeight);
+    method @RequiresApi(31) public static void setTextViewMaxHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMaxHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewMaxLines(android.widget.RemoteViews, @IdRes int viewId, int maxLines);
+    method @RequiresApi(31) public static void setTextViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int maxWidth);
+    method @RequiresApi(31) public static void setTextViewMaxWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMaxWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewMinEms(android.widget.RemoteViews, @IdRes int viewId, int minems);
+    method @RequiresApi(31) public static void setTextViewMinHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMinHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int minHeight);
+    method @RequiresApi(31) public static void setTextViewMinHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMinHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewMinLines(android.widget.RemoteViews, @IdRes int viewId, int minLines);
+    method @RequiresApi(31) public static void setTextViewMinWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMinWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int minWidth);
+    method @RequiresApi(31) public static void setTextViewMinWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMinWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewPaintFlags(android.widget.RemoteViews, @IdRes int viewId, int flags);
+    method public static void setTextViewSelectAllOnFocus(android.widget.RemoteViews, @IdRes int viewId, boolean selectAllOnFocus);
+    method public static void setTextViewSingleLine(android.widget.RemoteViews, @IdRes int viewId, boolean singleLine);
+    method public static void setTextViewText(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList colors);
+    method @RequiresApi(31) public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList notNight, android.content.res.ColorStateList night);
+    method public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewTextColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setTextViewTextScaleX(android.widget.RemoteViews, @IdRes int viewId, float size);
+    method @RequiresApi(31) public static void setTextViewTextSizeDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextSizeDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int pixels);
+    method @RequiresApi(31) public static void setTextViewWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewAlpha(android.widget.RemoteViews, @IdRes int viewId, float alpha);
+    method public static void setViewBackgroundColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setViewBackgroundColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setViewBackgroundColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setViewBackgroundColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setViewBackgroundResource(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setViewBackgroundTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setViewBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setViewBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setViewBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setViewBackgroundTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewClipToOutline(android.widget.RemoteViews, @IdRes int viewId, boolean clipToOutline);
+    method @RequiresApi(31) public static void setViewContentDescription(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setViewContentDescription(android.widget.RemoteViews, @IdRes int viewId, CharSequence? contentDescription);
+    method @RequiresApi(31) public static void setViewContentDescriptionAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewElevationDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewElevationDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewElevationDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(24) public static void setViewEnabled(android.widget.RemoteViews, @IdRes int viewId, boolean enabled);
+    method @RequiresApi(31) public static void setViewFocusable(android.widget.RemoteViews, @IdRes int viewId, boolean focusable);
+    method @RequiresApi(31) public static void setViewFocusable(android.widget.RemoteViews, @IdRes int viewId, int focusable);
+    method @RequiresApi(31) public static void setViewFocusableInTouchMode(android.widget.RemoteViews, @IdRes int viewId, boolean focusableInTouchMode);
+    method @RequiresApi(31) public static void setViewFocusedByDefault(android.widget.RemoteViews, @IdRes int viewId, boolean isFocusedByDefault);
+    method @RequiresApi(31) public static void setViewForegroundTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setViewForegroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setViewForegroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setViewForegroundTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setViewForegroundTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setViewLayoutDirection(android.widget.RemoteViews, @IdRes int viewId, int layoutDirection);
+    method @RequiresApi(31) public static void setViewMinimumHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(24) public static void setViewMinimumHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int minHeight);
+    method @RequiresApi(31) public static void setViewMinimumHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewMinimumHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewMinimumWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewMinimumWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewMinimumWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewPivotX(android.widget.RemoteViews, @IdRes int viewId, float pivotX);
+    method @RequiresApi(31) public static void setViewPivotY(android.widget.RemoteViews, @IdRes int viewId, float pivotY);
+    method @RequiresApi(31) public static void setViewRotation(android.widget.RemoteViews, @IdRes int viewId, float rotation);
+    method @RequiresApi(31) public static void setViewRotationX(android.widget.RemoteViews, @IdRes int viewId, float rotationX);
+    method @RequiresApi(31) public static void setViewRotationY(android.widget.RemoteViews, @IdRes int viewId, float rotationY);
+    method @RequiresApi(31) public static void setViewScaleX(android.widget.RemoteViews, @IdRes int viewId, float scaleX);
+    method @RequiresApi(31) public static void setViewScaleY(android.widget.RemoteViews, @IdRes int viewId, float scaleY);
+    method @RequiresApi(31) public static void setViewScrollIndicators(android.widget.RemoteViews, @IdRes int viewId, int scrollIndicators);
+    method @RequiresApi(31) public static void setViewStateDescription(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(30) public static void setViewStateDescription(android.widget.RemoteViews, @IdRes int viewId, CharSequence? stateDescription);
+    method @RequiresApi(31) public static void setViewStateDescriptionAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setViewStubInflatedId(android.widget.RemoteViews, @IdRes int viewId, int inflatedId);
+    method public static void setViewStubLayoutResource(android.widget.RemoteViews, @IdRes int viewId, @LayoutRes int layoutResource);
+    method @RequiresApi(31) public static void setViewTranslationXDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewTranslationXDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationXDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationYDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewTranslationYDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationYDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationZDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewTranslationZDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationZDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    field public static final androidx.core.widget.RemoteViewsCompat INSTANCE;
+  }
+
+  public static final class RemoteViewsCompat.RemoteCollectionItems {
+    method public int getItemCount();
+    method public long getItemId(int position);
+    method public android.widget.RemoteViews getItemView(int position);
+    method public int getViewTypeCount();
+    method public boolean hasStableIds();
+    property public final int itemCount;
+    property public final int viewTypeCount;
+  }
+
+  public static final class RemoteViewsCompat.RemoteCollectionItems.Builder {
+    ctor public RemoteViewsCompat.RemoteCollectionItems.Builder();
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems.Builder addItem(long id, android.widget.RemoteViews view);
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems build();
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems.Builder setHasStableIds(boolean hasStableIds);
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems.Builder setViewTypeCount(int viewTypeCount);
+  }
+
+}
+
diff --git a/core/core-remoteviews/api/res-1.1.0-beta01.txt b/core/core-remoteviews/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-remoteviews/api/res-1.1.0-beta01.txt
diff --git a/core/core-remoteviews/api/restricted_1.1.0-beta01.txt b/core/core-remoteviews/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..07071af
--- /dev/null
+++ b/core/core-remoteviews/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,299 @@
+// Signature format: 4.0
+package androidx.core.widget {
+
+  public final class AppWidgetManagerCompat {
+    method public static android.widget.RemoteViews createExactSizeAppWidget(android.appwidget.AppWidgetManager appWidgetManager, int appWidgetId, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+    method public static android.widget.RemoteViews createResponsiveSizeAppWidget(android.appwidget.AppWidgetManager appWidgetManager, int appWidgetId, java.util.Collection<androidx.core.util.SizeFCompat> dpSizes, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+    method public static void updateAppWidget(android.appwidget.AppWidgetManager, int appWidgetId, java.util.Collection<androidx.core.util.SizeFCompat> dpSizes, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+    method public static void updateAppWidget(android.appwidget.AppWidgetManager, int appWidgetId, kotlin.jvm.functions.Function1<? super androidx.core.util.SizeFCompat,? extends android.widget.RemoteViews> factory);
+  }
+
+  public final class RemoteViewsCompat {
+    method public static void setChronometerBase(android.widget.RemoteViews, @IdRes int viewId, long base);
+    method public static void setChronometerFormat(android.widget.RemoteViews, @IdRes int viewId, String? format);
+    method @RequiresApi(31) public static void setCompoundButtonDrawable(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setCompoundButtonIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? icon);
+    method @RequiresApi(31) public static void setCompoundButtonTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? tintMode);
+    method @RequiresApi(31) public static void setCompoundButtonTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setCompoundButtonTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNight, android.content.res.ColorStateList? night);
+    method @RequiresApi(31) public static void setCompoundButtonTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setCompoundButtonTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setFrameLayoutForegroundGravity(android.widget.RemoteViews, @IdRes int viewId, int foregroundGravity);
+    method public static void setFrameLayoutMeasureAllChildren(android.widget.RemoteViews, @IdRes int viewId, boolean measureAll);
+    method @RequiresApi(31) public static void setGridLayoutAlignmentMode(android.widget.RemoteViews, @IdRes int viewId, int alignmentMode);
+    method @RequiresApi(31) public static void setGridLayoutColumnCount(android.widget.RemoteViews, @IdRes int viewId, int columnCount);
+    method @RequiresApi(31) public static void setGridLayoutRowCount(android.widget.RemoteViews, @IdRes int viewId, int rowCount);
+    method @RequiresApi(31) public static void setGridViewColumnWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setGridViewColumnWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int columnWidth);
+    method @RequiresApi(31) public static void setGridViewColumnWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int columnWidth);
+    method @RequiresApi(31) public static void setGridViewGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method @RequiresApi(31) public static void setGridViewHorizontalSpacing(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setGridViewHorizontalSpacingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setGridViewHorizontalSpacingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setGridViewNumColumns(android.widget.RemoteViews, @IdRes int viewId, int numColumns);
+    method @RequiresApi(31) public static void setGridViewStretchMode(android.widget.RemoteViews, @IdRes int viewId, int stretchMode);
+    method @RequiresApi(31) public static void setGridViewVerticalSpacing(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setGridViewVerticalSpacingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setGridViewVerticalSpacingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setImageViewAdjustViewBounds(android.widget.RemoteViews, @IdRes int viewId, boolean adjustViewBounds);
+    method public static void setImageViewColorFilter(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setImageViewColorFilter(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setImageViewColorFilterAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setImageViewColorFilterResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setImageViewImageAlpha(android.widget.RemoteViews, @IdRes int viewId, int alpha);
+    method public static void setImageViewImageLevel(android.widget.RemoteViews, @IdRes int viewId, int level);
+    method @RequiresApi(31) public static void setImageViewImageTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setImageViewImageTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setImageViewImageTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setImageViewImageTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setImageViewImageTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setImageViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int maxHeight);
+    method @RequiresApi(31) public static void setImageViewMaxHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setImageViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int maxWidth);
+    method @RequiresApi(31) public static void setImageViewMaxWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setImageViewMaxWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setLinearLayoutBaselineAligned(android.widget.RemoteViews, @IdRes int viewId, boolean baselineAligned);
+    method public static void setLinearLayoutBaselineAlignedChildIndex(android.widget.RemoteViews, @IdRes int viewId, int i);
+    method public static void setLinearLayoutGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method public static void setLinearLayoutHorizontalGravity(android.widget.RemoteViews, @IdRes int viewId, int horizontalGravity);
+    method public static void setLinearLayoutMeasureWithLargestChildEnabled(android.widget.RemoteViews, @IdRes int viewId, boolean enabled);
+    method public static void setLinearLayoutVerticalGravity(android.widget.RemoteViews, @IdRes int viewId, int verticalGravity);
+    method public static void setLinearLayoutWeightSum(android.widget.RemoteViews, @IdRes int viewId, float weightSum);
+    method public static void setProgressBarIndeterminate(android.widget.RemoteViews, @IdRes int viewId, boolean indeterminate);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarIndeterminateTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setProgressBarMax(android.widget.RemoteViews, @IdRes int viewId, int max);
+    method @RequiresApi(26) public static void setProgressBarMin(android.widget.RemoteViews, @IdRes int viewId, int min);
+    method public static void setProgressBarProgress(android.widget.RemoteViews, @IdRes int viewId, int progress);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarProgressBackgroundTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setProgressBarProgressTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarProgressTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarProgressTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setProgressBarSecondaryProgress(android.widget.RemoteViews, @IdRes int viewId, int secondaryProgress);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setProgressBarSecondaryProgressTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setProgressBarStateDescription(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setProgressBarStateDescription(android.widget.RemoteViews, @IdRes int viewId, CharSequence? stateDescription);
+    method @RequiresApi(31) public static void setProgressBarStateDescriptionAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setRelativeLayoutGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method public static void setRelativeLayoutHorizontalGravity(android.widget.RemoteViews, @IdRes int viewId, int horizontalGravity);
+    method public static void setRelativeLayoutIgnoreGravity(android.widget.RemoteViews, @IdRes int viewId, @IdRes int childViewId);
+    method public static void setRelativeLayoutVerticalGravity(android.widget.RemoteViews, @IdRes int viewId, int verticalGravity);
+    method public static void setRemoteAdapter(android.content.Context context, android.widget.RemoteViews remoteViews, int appWidgetId, @IdRes int viewId, androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems items);
+    method @RequiresApi(31) public static void setSwitchMinWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setSwitchMinWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setSwitchMinWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchPadding(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setSwitchPaddingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setSwitchPaddingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchShowText(android.widget.RemoteViews, @IdRes int viewId, boolean showText);
+    method @RequiresApi(31) public static void setSwitchSplitTrack(android.widget.RemoteViews, @IdRes int viewId, boolean splitTrack);
+    method @RequiresApi(31) public static void setSwitchTextOff(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setSwitchTextOff(android.widget.RemoteViews, @IdRes int viewId, CharSequence? textOff);
+    method @RequiresApi(31) public static void setSwitchTextOffAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchTextOn(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setSwitchTextOn(android.widget.RemoteViews, @IdRes int viewId, CharSequence? textOn);
+    method @RequiresApi(31) public static void setSwitchTextOnAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? icon);
+    method @RequiresApi(31) public static void setSwitchThumbIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? notNight, android.graphics.drawable.Icon? night);
+    method @RequiresApi(31) public static void setSwitchThumbResource(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTextPadding(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setSwitchThumbTextPaddingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTextPaddingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setSwitchThumbTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setSwitchThumbTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNight, android.content.res.ColorStateList? night);
+    method @RequiresApi(31) public static void setSwitchThumbTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setSwitchThumbTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setSwitchTrackIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? icon);
+    method @RequiresApi(31) public static void setSwitchTrackIcon(android.widget.RemoteViews, @IdRes int viewId, android.graphics.drawable.Icon? notNight, android.graphics.drawable.Icon? night);
+    method @RequiresApi(31) public static void setSwitchTrackResource(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setSwitchTrackTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setSwitchTrackTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setSwitchTrackTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNight, android.content.res.ColorStateList? night);
+    method @RequiresApi(31) public static void setSwitchTrackTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setSwitchTrackTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextClockFormat12Hour(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextClockFormat12Hour(android.widget.RemoteViews, @IdRes int viewId, CharSequence? format);
+    method @RequiresApi(31) public static void setTextClockFormat12HourAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextClockFormat24Hour(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextClockFormat24Hour(android.widget.RemoteViews, @IdRes int viewId, CharSequence? format);
+    method @RequiresApi(31) public static void setTextClockFormat24HourAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextClockTimeZone(android.widget.RemoteViews, @IdRes int viewId, String? timeZone);
+    method @RequiresApi(31) public static void setTextViewAllCaps(android.widget.RemoteViews, @IdRes int viewId, boolean allCaps);
+    method public static void setTextViewAutoLinkMask(android.widget.RemoteViews, @IdRes int viewId, int mask);
+    method @RequiresApi(31) public static void setTextViewCompoundDrawablePadding(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewCompoundDrawablePadding(android.widget.RemoteViews, @IdRes int viewId, @Px int pad);
+    method @RequiresApi(31) public static void setTextViewCompoundDrawablePaddingDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewCompoundDrawablePaddingDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewEms(android.widget.RemoteViews, @IdRes int viewId, int ems);
+    method @RequiresApi(31) public static void setTextViewError(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextViewError(android.widget.RemoteViews, @IdRes int viewId, CharSequence? error);
+    method @RequiresApi(31) public static void setTextViewErrorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(21) public static void setTextViewFontFeatureSettings(android.widget.RemoteViews, @IdRes int viewId, String fontFeatureSettings);
+    method @RequiresApi(31) public static void setTextViewGravity(android.widget.RemoteViews, @IdRes int viewId, int gravity);
+    method @RequiresApi(31) public static void setTextViewHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int pixels);
+    method @RequiresApi(31) public static void setTextViewHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewHighlightColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewHighlightColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewHighlightColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewHighlightColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setTextViewHint(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setTextViewHint(android.widget.RemoteViews, @IdRes int viewId, CharSequence? hint);
+    method @RequiresApi(31) public static void setTextViewHintAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewHintTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewHintTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewHintTextColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewHintTextColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setTextViewJustificationMode(android.widget.RemoteViews, @IdRes int viewId, int justificationMode);
+    method @RequiresApi(21) public static void setTextViewLetterSpacing(android.widget.RemoteViews, @IdRes int viewId, float letterSpacing);
+    method @RequiresApi(31) public static void setTextViewLineHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setTextViewLineHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewLineHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewLines(android.widget.RemoteViews, @IdRes int viewId, int lines);
+    method public static void setTextViewLinkTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewLinkTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewLinkTextColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewLinkTextColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setTextViewLinksClickable(android.widget.RemoteViews, @IdRes int viewId, boolean whether);
+    method public static void setTextViewMaxEms(android.widget.RemoteViews, @IdRes int viewId, int maxems);
+    method @RequiresApi(31) public static void setTextViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMaxHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int maxHeight);
+    method @RequiresApi(31) public static void setTextViewMaxHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMaxHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewMaxLines(android.widget.RemoteViews, @IdRes int viewId, int maxLines);
+    method @RequiresApi(31) public static void setTextViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMaxWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int maxWidth);
+    method @RequiresApi(31) public static void setTextViewMaxWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMaxWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewMinEms(android.widget.RemoteViews, @IdRes int viewId, int minems);
+    method @RequiresApi(31) public static void setTextViewMinHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMinHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int minHeight);
+    method @RequiresApi(31) public static void setTextViewMinHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMinHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewMinLines(android.widget.RemoteViews, @IdRes int viewId, int minLines);
+    method @RequiresApi(31) public static void setTextViewMinWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewMinWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int minWidth);
+    method @RequiresApi(31) public static void setTextViewMinWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewMinWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setTextViewPaintFlags(android.widget.RemoteViews, @IdRes int viewId, int flags);
+    method public static void setTextViewSelectAllOnFocus(android.widget.RemoteViews, @IdRes int viewId, boolean selectAllOnFocus);
+    method public static void setTextViewSingleLine(android.widget.RemoteViews, @IdRes int viewId, boolean singleLine);
+    method public static void setTextViewText(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList colors);
+    method @RequiresApi(31) public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList notNight, android.content.res.ColorStateList night);
+    method public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setTextViewTextColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setTextViewTextColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setTextViewTextScaleX(android.widget.RemoteViews, @IdRes int viewId, float size);
+    method @RequiresApi(31) public static void setTextViewTextSizeDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewTextSizeDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setTextViewWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method public static void setTextViewWidth(android.widget.RemoteViews, @IdRes int viewId, @Px int pixels);
+    method @RequiresApi(31) public static void setTextViewWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setTextViewWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewAlpha(android.widget.RemoteViews, @IdRes int viewId, float alpha);
+    method public static void setViewBackgroundColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int color);
+    method @RequiresApi(31) public static void setViewBackgroundColor(android.widget.RemoteViews, @IdRes int viewId, @ColorInt int notNight, @ColorInt int night);
+    method @RequiresApi(31) public static void setViewBackgroundColorAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setViewBackgroundColorResource(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method public static void setViewBackgroundResource(android.widget.RemoteViews, @IdRes int viewId, @DrawableRes int resId);
+    method @RequiresApi(31) public static void setViewBackgroundTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setViewBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setViewBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setViewBackgroundTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setViewBackgroundTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewClipToOutline(android.widget.RemoteViews, @IdRes int viewId, boolean clipToOutline);
+    method @RequiresApi(31) public static void setViewContentDescription(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method public static void setViewContentDescription(android.widget.RemoteViews, @IdRes int viewId, CharSequence? contentDescription);
+    method @RequiresApi(31) public static void setViewContentDescriptionAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewElevationDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewElevationDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewElevationDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(24) public static void setViewEnabled(android.widget.RemoteViews, @IdRes int viewId, boolean enabled);
+    method @RequiresApi(31) public static void setViewFocusable(android.widget.RemoteViews, @IdRes int viewId, boolean focusable);
+    method @RequiresApi(31) public static void setViewFocusable(android.widget.RemoteViews, @IdRes int viewId, int focusable);
+    method @RequiresApi(31) public static void setViewFocusableInTouchMode(android.widget.RemoteViews, @IdRes int viewId, boolean focusableInTouchMode);
+    method @RequiresApi(31) public static void setViewFocusedByDefault(android.widget.RemoteViews, @IdRes int viewId, boolean isFocusedByDefault);
+    method @RequiresApi(31) public static void setViewForegroundTintBlendMode(android.widget.RemoteViews, @IdRes int viewId, android.graphics.BlendMode? blendMode);
+    method @RequiresApi(31) public static void setViewForegroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? tint);
+    method @RequiresApi(31) public static void setViewForegroundTintList(android.widget.RemoteViews, @IdRes int viewId, android.content.res.ColorStateList? notNightTint, android.content.res.ColorStateList? nightTint);
+    method @RequiresApi(31) public static void setViewForegroundTintList(android.widget.RemoteViews, @IdRes int viewId, @ColorRes int resId);
+    method @RequiresApi(31) public static void setViewForegroundTintListAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setViewLayoutDirection(android.widget.RemoteViews, @IdRes int viewId, int layoutDirection);
+    method @RequiresApi(31) public static void setViewMinimumHeight(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(24) public static void setViewMinimumHeight(android.widget.RemoteViews, @IdRes int viewId, @Px int minHeight);
+    method @RequiresApi(31) public static void setViewMinimumHeightDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewMinimumHeightDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewMinimumWidth(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewMinimumWidthDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewMinimumWidthDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewPivotX(android.widget.RemoteViews, @IdRes int viewId, float pivotX);
+    method @RequiresApi(31) public static void setViewPivotY(android.widget.RemoteViews, @IdRes int viewId, float pivotY);
+    method @RequiresApi(31) public static void setViewRotation(android.widget.RemoteViews, @IdRes int viewId, float rotation);
+    method @RequiresApi(31) public static void setViewRotationX(android.widget.RemoteViews, @IdRes int viewId, float rotationX);
+    method @RequiresApi(31) public static void setViewRotationY(android.widget.RemoteViews, @IdRes int viewId, float rotationY);
+    method @RequiresApi(31) public static void setViewScaleX(android.widget.RemoteViews, @IdRes int viewId, float scaleX);
+    method @RequiresApi(31) public static void setViewScaleY(android.widget.RemoteViews, @IdRes int viewId, float scaleY);
+    method @RequiresApi(31) public static void setViewScrollIndicators(android.widget.RemoteViews, @IdRes int viewId, int scrollIndicators);
+    method @RequiresApi(31) public static void setViewStateDescription(android.widget.RemoteViews, @IdRes int viewId, @StringRes int resId);
+    method @RequiresApi(30) public static void setViewStateDescription(android.widget.RemoteViews, @IdRes int viewId, CharSequence? stateDescription);
+    method @RequiresApi(31) public static void setViewStateDescriptionAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method public static void setViewStubInflatedId(android.widget.RemoteViews, @IdRes int viewId, int inflatedId);
+    method public static void setViewStubLayoutResource(android.widget.RemoteViews, @IdRes int viewId, @LayoutRes int layoutResource);
+    method @RequiresApi(31) public static void setViewTranslationXDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewTranslationXDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationXDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationYDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewTranslationYDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationYDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationZDimen(android.widget.RemoteViews, @IdRes int viewId, float value, int unit);
+    method @RequiresApi(31) public static void setViewTranslationZDimen(android.widget.RemoteViews, @IdRes int viewId, @DimenRes int resId);
+    method @RequiresApi(31) public static void setViewTranslationZDimenAttr(android.widget.RemoteViews, @IdRes int viewId, @AttrRes int resId);
+    field public static final androidx.core.widget.RemoteViewsCompat INSTANCE;
+  }
+
+  public static final class RemoteViewsCompat.RemoteCollectionItems {
+    method public int getItemCount();
+    method public long getItemId(int position);
+    method public android.widget.RemoteViews getItemView(int position);
+    method public int getViewTypeCount();
+    method public boolean hasStableIds();
+    property public final int itemCount;
+    property public final int viewTypeCount;
+  }
+
+  public static final class RemoteViewsCompat.RemoteCollectionItems.Builder {
+    ctor public RemoteViewsCompat.RemoteCollectionItems.Builder();
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems.Builder addItem(long id, android.widget.RemoteViews view);
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems build();
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems.Builder setHasStableIds(boolean hasStableIds);
+    method public androidx.core.widget.RemoteViewsCompat.RemoteCollectionItems.Builder setViewTypeCount(int viewTypeCount);
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class RemoteViewsCompatService extends android.widget.RemoteViewsService {
+    ctor public RemoteViewsCompatService();
+    method public android.widget.RemoteViewsService.RemoteViewsFactory onGetViewFactory(android.content.Intent intent);
+  }
+
+}
+
diff --git a/core/core/api/1.13.0-beta01.txt b/core/core/api/1.13.0-beta01.txt
index f0bc2c9..57c66b5 100644
--- a/core/core/api/1.13.0-beta01.txt
+++ b/core/core/api/1.13.0-beta01.txt
@@ -1060,7 +1060,7 @@
     method public void onSharedElementsReady();
   }
 
-  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent!> {
     method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index f0bc2c9..57c66b5 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1060,7 +1060,7 @@
     method public void onSharedElementsReady();
   }
 
-  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent!> {
     method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
diff --git a/core/core/api/restricted_1.13.0-beta01.txt b/core/core/api/restricted_1.13.0-beta01.txt
index a226921..b090f80 100644
--- a/core/core/api/restricted_1.13.0-beta01.txt
+++ b/core/core/api/restricted_1.13.0-beta01.txt
@@ -1183,7 +1183,7 @@
     method public void onSharedElementsReady();
   }
 
-  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent!> {
     method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index a226921..b090f80 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1183,7 +1183,7 @@
     method public void onSharedElementsReady();
   }
 
-  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+  public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent!> {
     method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
     method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
diff --git a/core/core/src/androidTest/AndroidManifest.xml b/core/core/src/androidTest/AndroidManifest.xml
index 30756c9..284af21 100644
--- a/core/core/src/androidTest/AndroidManifest.xml
+++ b/core/core/src/androidTest/AndroidManifest.xml
@@ -118,6 +118,16 @@
             android:theme="@android:style/Theme.Light.NoTitleBar" />
 
         <activity
+            android:name="androidx.core.view.LightSystemBarsActivity"
+            android:exported="true"
+            android:theme="@style/LightSystemBarsTheme" />
+
+        <activity
+            android:name="androidx.core.view.DarkSystemBarsActivity"
+            android:exported="true"
+            android:theme="@style/DarkSystemBarsTheme" />
+
+        <activity
             android:name="androidx.core.view.WindowInsetsCompatActivity"
             android:exported="true"
             android:theme="@android:style/Theme.Light.NoTitleBar" />
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/core/core/src/androidTest/java/androidx/core/view/DarkSystemBarsActivity.java
similarity index 62%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to core/core/src/androidTest/java/androidx/core/view/DarkSystemBarsActivity.java
index 3e91597..f635cc5 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/DarkSystemBarsActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,6 +14,15 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.core.view;
+
+import android.support.v4.BaseTestActivity;
+
+import androidx.core.test.R;
+
+public class DarkSystemBarsActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.insets_compat_activity;
+    }
+}
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/core/core/src/androidTest/java/androidx/core/view/LightSystemBarsActivity.java
similarity index 62%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to core/core/src/androidTest/java/androidx/core/view/LightSystemBarsActivity.java
index 3e91597..6aff709 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/LightSystemBarsActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,6 +14,15 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.core.view;
+
+import android.support.v4.BaseTestActivity;
+
+import androidx.core.test.R;
+
+public class LightSystemBarsActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.insets_compat_activity;
+    }
+}
diff --git a/core/core/src/androidTest/java/androidx/core/view/ThemeSystemBarsTest.kt b/core/core/src/androidTest/java/androidx/core/view/ThemeSystemBarsTest.kt
new file mode 100644
index 0000000..8f5b9c0
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/view/ThemeSystemBarsTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.view
+
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ThemeSystemBarsTest {
+
+    @SdkSuppress(maxSdkVersion = 22)
+    @Test
+    fun statusBar_dark_before_supported() {
+        val scenario = ActivityScenario.launch(LightSystemBarsActivity::class.java)
+
+        val insetsController = scenario.withActivity {
+            WindowCompat.getInsetsController(window, window.decorView)
+        }
+
+        assertThat(insetsController.isAppearanceLightStatusBars).isFalse()
+
+        scenario.close()
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    fun statusBar_light() {
+        val scenario = ActivityScenario.launch(LightSystemBarsActivity::class.java)
+
+        val insetsController = scenario.withActivity {
+            WindowCompat.getInsetsController(window, window.decorView)
+        }
+
+        assertThat(insetsController.isAppearanceLightStatusBars).isTrue()
+
+        scenario.close()
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    fun statusBar_dark() {
+        val scenario = ActivityScenario.launch(DarkSystemBarsActivity::class.java)
+
+        val insetsController = scenario.withActivity {
+            WindowCompat.getInsetsController(window, window.decorView)
+        }
+
+        assertThat(insetsController.isAppearanceLightStatusBars).isFalse()
+
+        scenario.close()
+    }
+
+    @SdkSuppress(maxSdkVersion = 26)
+    @Test
+    fun navigationBar_dark_before_supported() {
+        val scenario = ActivityScenario.launch(LightSystemBarsActivity::class.java)
+
+        val insetsController = scenario.withActivity {
+            WindowCompat.getInsetsController(window, window.decorView)
+        }
+
+        assertThat(insetsController.isAppearanceLightNavigationBars).isFalse()
+
+        scenario.close()
+    }
+
+    @SdkSuppress(minSdkVersion = 27)
+    @Test
+    fun navigationBar_light() {
+        val scenario = ActivityScenario.launch(LightSystemBarsActivity::class.java)
+
+        val insetsController = scenario.withActivity {
+            WindowCompat.getInsetsController(window, window.decorView)
+        }
+
+        assertThat(insetsController.isAppearanceLightNavigationBars).isTrue()
+
+        scenario.close()
+    }
+
+    @SdkSuppress(minSdkVersion = 27)
+    @Test
+    fun navigationBar_dark() {
+        val scenario = ActivityScenario.launch(DarkSystemBarsActivity::class.java)
+
+        val insetsController = scenario.withActivity {
+            WindowCompat.getInsetsController(window, window.decorView)
+        }
+
+        assertThat(insetsController.isAppearanceLightNavigationBars).isFalse()
+
+        scenario.close()
+    }
+}
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
index ddf7d18..a48a1d0 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
@@ -19,7 +19,6 @@
 import android.app.Dialog
 import android.os.Build
 import android.view.View
-import android.view.WindowInsetsController
 import android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
 import android.widget.EditText
 import android.widget.TextView
@@ -27,6 +26,7 @@
 import androidx.core.graphics.Insets
 import androidx.core.test.R
 import androidx.test.core.app.ActivityScenario
+import androidx.test.espresso.Espresso
 import androidx.test.espresso.Espresso.onView
 import androidx.test.espresso.action.ViewActions.click
 import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
@@ -76,7 +76,7 @@
 
         scenario.withActivity {
             WindowCompat.getInsetsController(window, container).systemBarsBehavior =
-                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+                WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
             // Needed on API 23 to report the nav bar insets
             this.window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
         }
@@ -234,6 +234,33 @@
 
     @SdkSuppress(minSdkVersion = 23)
     @Test
+    public fun initial_statusBar_light() {
+        // Set the flag with the systemUiVisibility flags even on newer APIs to emulate this value
+        // being set in the theme
+        scenario.onActivity {
+            it.window.decorView.systemUiVisibility =
+                it.window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+        }
+        Espresso.onIdle()
+        assertThat(windowInsetsController.isAppearanceLightStatusBars(), `is`(true))
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
+    public fun initial_statusBar_dark() {
+        // Set the flag with the systemUiVisibility flags even on newer APIs to emulate this value
+        // being set in the theme
+        scenario.onActivity {
+            it.window.decorView.systemUiVisibility =
+                it.window.decorView.systemUiVisibility and
+                    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
+        }
+        Espresso.onIdle()
+        assertThat(windowInsetsController.isAppearanceLightStatusBars(), `is`(false))
+    }
+
+    @SdkSuppress(minSdkVersion = 23)
+    @Test
     public fun statusBar_light() {
         scenario.onActivity { windowInsetsController.setAppearanceLightStatusBars(true) }
         if (Build.VERSION.SDK_INT < 31) {
@@ -269,6 +296,33 @@
 
     @SdkSuppress(minSdkVersion = 26)
     @Test
+    public fun initial_navigationBar_light() {
+        // Set the flag with the systemUiVisibility flags even on newer APIs to emulate this value
+        // being set in the theme
+        scenario.onActivity {
+            it.window.decorView.systemUiVisibility =
+                it.window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+        }
+        Espresso.onIdle()
+        assertThat(windowInsetsController.isAppearanceLightNavigationBars(), `is`(true))
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    public fun initial_navigationBar_dark() {
+        // Set the flag with the systemUiVisibility flags even on newer APIs to emulate this value
+        // being set in the theme
+        scenario.onActivity {
+            it.window.decorView.systemUiVisibility =
+                it.window.decorView.systemUiVisibility and
+                    View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
+        }
+        Espresso.onIdle()
+        assertThat(windowInsetsController.isAppearanceLightNavigationBars(), `is`(false))
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
     public fun navigationBar_light() {
         scenario.onActivity { windowInsetsController.setAppearanceLightNavigationBars(true) }
         val systemUiVisibility = scenario.withActivity { window.decorView }.systemUiVisibility
diff --git a/core/core/src/androidTest/res/values/styles.xml b/core/core/src/androidTest/res/values/styles.xml
index 8f5f021..c8ad0f0 100644
--- a/core/core/src/androidTest/res/values/styles.xml
+++ b/core/core/src/androidTest/res/values/styles.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools">
     <style name="TestActivityTheme" parent="@android:style/Theme.Holo">
         <item name="android:windowAnimationStyle">@null</item>
     </style>
@@ -38,4 +38,14 @@
     <style name="ThemeOverlay.Core.ColorStateListInflaterCompat" parent="">
         <item name="android:textColorPrimary">@color/text_color</item>
     </style>
+
+    <style name="LightSystemBarsTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowLightStatusBar" tools:targetApi="23">true</item>
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">true</item>
+    </style>
+
+    <style name="DarkSystemBarsTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowLightStatusBar" tools:targetApi="23">false</item>
+        <item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/core/core/src/main/java/androidx/core/util/Predicate.java b/core/core/src/main/java/androidx/core/util/Predicate.java
index 1f4d872..57669dc 100644
--- a/core/core/src/main/java/androidx/core/util/Predicate.java
+++ b/core/core/src/main/java/androidx/core/util/Predicate.java
@@ -121,7 +121,6 @@
      * @return a predicate that negates the results of the supplied
      * predicate
      * @throws NullPointerException if target is null
-     * @since 11
      */
     @SuppressWarnings("unchecked")
     @SuppressLint("MissingNullability")
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
index 3f1564f..ef3d133 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
@@ -194,6 +194,12 @@
      * Checks if the foreground of the status bar is set to light.
      * <p>
      * This method always returns false on API < 23.
+     * <p>
+     * If this value is being set in the theme (via {@link android.R.attr#windowLightStatusBar}),
+     * then the correct value will only be returned once attached to the window.
+     * <p>
+     * Once this method is called, modifying `systemUiVisibility` directly to change the
+     * appearance is undefined behavior.
      *
      * @return true if the foreground is light
      * @see #setAppearanceLightStatusBars(boolean)
@@ -207,6 +213,9 @@
      * bar can be read clearly. If false, reverts to the default appearance.
      * <p>
      * This method has no effect on API < 23.
+     * <p>
+     * Once this method is called, modifying `systemUiVisibility` directly to change the
+     * appearance is undefined behavior.
      *
      * @see #isAppearanceLightStatusBars()
      */
@@ -218,6 +227,13 @@
      * Checks if the foreground of the navigation bar is set to light.
      * <p>
      * This method always returns false on API < 26.
+     * <p>
+     * If this value is being set in the theme (via
+     * {@link android.R.attr#windowLightNavigationBar}),
+     * then the correct value will only be returned once attached to the window.
+     * <p>
+     * Once this method is called, modifying `systemUiVisibility` directly to change the
+     * appearance is undefined behavior.
      *
      * @return true if the foreground is light
      * @see #setAppearanceLightNavigationBars(boolean)
@@ -231,6 +247,9 @@
      * the bar can be read clearly. If false, reverts to the default appearance.
      * <p>
      * This method has no effect on API < 26.
+     * <p>
+     * Once this method is called, modifying `systemUiVisibility` directly to change the
+     * appearance is undefined behavior.
      *
      * @see #isAppearanceLightNavigationBars()
      */
@@ -637,6 +656,13 @@
 
         @Override
         public boolean isAppearanceLightStatusBars() {
+            // This is a side-effectful workaround
+            // Because the mask is zero, this won't change the system bar appearance
+            // However, it "unlocks" reading the effective system bar appearance in the following
+            // call. Without this being "unlocked," the system bar appearance will always return
+            // nothing, even if it has been set in the theme or by the system ui flags before
+            // querying for it.
+            mInsetsController.setSystemBarsAppearance(0, 0);
             return (mInsetsController.getSystemBarsAppearance()
                     & WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS) != 0;
         }
@@ -664,6 +690,13 @@
 
         @Override
         public boolean isAppearanceLightNavigationBars() {
+            // This is a side-effectful workaround
+            // Because the mask is zero, this won't change the system bar appearance
+            // However, it "unlocks" reading the effective system bar appearance in the following
+            // call. Without this being "unlocked," the system bar appearance will always return
+            // nothing, even if it has been set in the theme or by the system ui flags before
+            // querying for it.
+            mInsetsController.setSystemBarsAppearance(0, 0);
             return (mInsetsController.getSystemBarsAppearance()
                     & WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
         }
diff --git a/credentials/credentials-e2ee/build.gradle b/credentials/credentials-e2ee/build.gradle
index 92e6911..62d59c4 100644
--- a/credentials/credentials-e2ee/build.gradle
+++ b/credentials/credentials-e2ee/build.gradle
@@ -42,4 +42,5 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2023"
     description = "Create Identity Keys, signing keys for E2EE in AOSP."
+    mavenVersion = LibraryVersions.CREDENTIALS_E2EE_QUARANTINE
 }
diff --git a/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.android.kt b/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.android.kt
index 30f06ef..3d3edff 100644
--- a/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.android.kt
+++ b/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessCoordinator.android.kt
@@ -26,7 +26,6 @@
 import kotlin.contracts.ExperimentalContracts
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
@@ -37,9 +36,6 @@
 ) : InterProcessCoordinator {
     // TODO(b/269375542): the flow should `flowOn` the provided [context]
     override val updateNotifications: Flow<Unit> = MulticastFileObserver.observe(file)
-        // MulticastFileObserver dispatches 1 value upon connecting to the FileSystem, which
-        // is useful for its tests but not necessary here.
-        .drop(1)
 
     // run block with the exclusive lock
     override suspend fun <T> lock(block: suspend () -> T): T {
diff --git a/development/bench-flame-diff/README.md b/development/bench-flame-diff/README.md
index 87977c0..cfc9889b 100644
--- a/development/bench-flame-diff/README.md
+++ b/development/bench-flame-diff/README.md
@@ -56,7 +56,9 @@
 
 ## CLI completion
 
-Generate completion files with `./generate-completion-files.sh` and source in your shell config, e.g.:
+Generate shell-specific completion files with `./generate-completion.sh`.
+
+Then, source in your shell config, e.g.:
 - For `bash`: `dst="$(pwd)/completion_bash.sh"; echo "source '$dst'" >> ~/.bashrc`
 - For `zsh`: `dst="$(pwd)/completion_zsh.sh"; echo "source '$dst'" >> ~/.zshrc`
 
diff --git a/development/bench-flame-diff/bench-flame-diff.sh b/development/bench-flame-diff/bench-flame-diff.sh
index 66a6e40..4f75303 100755
--- a/development/bench-flame-diff/bench-flame-diff.sh
+++ b/development/bench-flame-diff/bench-flame-diff.sh
@@ -1,3 +1,11 @@
 #!/usr/bin/env bash
 
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)"
+pushd "$script_dir" >/dev/null
+
 ./gradlew --quiet installDist && ./app/build/install/bench-flame-diff/bin/bench-flame-diff "$@"
+exit_code=$?
+
+popd >/dev/null
+
+exit $exit_code
diff --git a/development/build_log_simplifier/build_log_simplifier.py b/development/build_log_simplifier/build_log_simplifier.py
index 974acd6..7bff81e 100755
--- a/development/build_log_simplifier/build_log_simplifier.py
+++ b/development/build_log_simplifier/build_log_simplifier.py
@@ -117,13 +117,10 @@
     result = []
     prev_line_is_boring = False
     for line in lines:
-        if line.startswith("\tat org.gradle"):
+        if line.startswith("\tat ") and not line.startswith("\tat androidx"):
+            # non-androidx stack frame
             if not prev_line_is_boring:
-                result.append("\tat org.gradle...\n")
-            prev_line_is_boring = True
-        elif line.startswith("\tat java.base"):
-            if not prev_line_is_boring:
-                result.append("\tat java.base...\n")
+                result.append(line.replace("\n", "...\n"))
             prev_line_is_boring = True
         else:
             result.append(line)
@@ -204,6 +201,11 @@
             prev_blank = False
     return result
 
+def remove_trailing_blank_lines(lines):
+    while len(lines) > 0 and lines[-1].strip() == "":
+        del lines[-1]
+    return lines
+
 def extract_task_name(line):
     prefix = "> Task "
     if line.startswith(prefix):
@@ -522,6 +524,7 @@
     interesting_lines = remove_by_regexes(interesting_lines, exemption_regexes, validate)
     interesting_lines = collapse_tasks_having_no_output(interesting_lines)
     interesting_lines = collapse_consecutive_blank_lines(interesting_lines)
+    interesting_lines = remove_trailing_blank_lines(interesting_lines)
 
     # process results
     if update:
diff --git a/development/build_log_simplifier/message-flakes.ignore b/development/build_log_simplifier/message-flakes.ignore
index 21a9218..7eaea26 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -122,7 +122,15 @@
 > Run with \-\-info or \-\-debug option to get more log output\.
 > Run with \-\-scan to get full insights\.
 # developers already see this message when local builds fail and don't need to also see it in CI
-\* Get more help at https://help\.gradle\.org
+> Get more help at https://help\.gradle\.org.
+# developers already can tell when a build failed
+FAILURE: Build failed with an exception.
+# developers already expect the output to explain what went wrong
+\* What went wrong:
+# the compilation log is already displayed in the output
+.*> Compilation error. See log for more details
+# In practice, these failures are compilation failures
+> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers\$GradleKotlinCompilerWorkAction
 # Flaky printout from kapt
 WARNING: Illegal reflective access by org\.jetbrains\.kotlin\.kapt3\.util\.ModuleManipulationUtilsKt .* to constructor com\.sun\.tools\.javac\.util\.Context\(\)
 WARNING: Please consider reporting this to the maintainers of org\.jetbrains\.kotlin\.kapt3\.util\.ModuleManipulationUtilsKt
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index dcb1024..505369b 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -395,7 +395,7 @@
 WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/ComplicationSlot\.kt:[0-9]+ Unable to find reference @param supportedTypes in DClass Builder\. Are you trying to refer to something not visible to users\?
 WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/Renderer\.kt:[0-9]+ Link does not resolve for @throws Renderer\.GlesException in DClass GlesRenderer2\. Is it from a package that the containing file does not import\? Are docs inherited by an un-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name, e\.g\. `@throws java\.io\.IOException under some conditions`\.
 WARN: .*\/unzippedJvmSources\/androidx\/wear\/watchface\/style\/CurrentUserStyleRepository\.kt:[0-9]+ Unable to find reference @param copySelectedOptions in DClass UserStyle\. Are you trying to refer to something not visible to users\?
-WARN\: \$OUT_DIR\/androidx\/docs\-tip\-of\-tree\/build\/unzippedMultiplatformSources\/nonJvmMain\/androidx\/lifecycle\/LifecycleRegistry\.nonJvm\.kt\:[0-9]+ Link does not resolve for \@throws IllegalStateException in DFunction addObserver\. Is it from a package that the containing file does not import\? Are docs inherited by an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\, e\.g\. \`\@throws java\.io\.IOException under some conditions\`\.
+WARN\: .*\/unzippedMultiplatformSources\/nonJvmMain\/androidx\/lifecycle\/LifecycleRegistry\.nonJvm\.kt\:[0-9]+ Link does not resolve for \@throws IllegalStateException in DFunction addObserver\. Is it from a package that the containing file does not import\? Are docs inherited by an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\, e\.g\. \`\@throws java\.io\.IOException under some conditions\`\.
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/CookieManagerCompat\.java:UnknownLine Missing @param tag for parameter `cookieManager` in DFunction getCookieInfo
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebSettingsCompat\.java:[0-9]+ Missing @param tag for parameter `settings` in DFunction setSafeBrowsingEnabled
 WARN: .*\/unzippedJvmSources\/androidx\/webkit\/WebSettingsCompat\.java:[0-9]+ Missing @param tag for parameter `settings` in DFunction setDisabledActionModeMenuItems
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 75f8f3d..6609255 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -15,15 +15,15 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.9.0-alpha03")
-    docs("androidx.activity:activity-compose:1.9.0-alpha03")
-    samples("androidx.activity:activity-compose-samples:1.9.0-alpha03")
-    docs("androidx.activity:activity-ktx:1.9.0-alpha03")
+    docs("androidx.activity:activity:1.9.0-beta01")
+    docs("androidx.activity:activity-compose:1.9.0-beta01")
+    samples("androidx.activity:activity-compose-samples:1.9.0-beta01")
+    docs("androidx.activity:activity-ktx:1.9.0-beta01")
     // ads-identifier is deprecated
     docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
     docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
     docsWithoutApiSince("androidx.ads:ads-identifier-provider:1.0.0-alpha05")
-    kmpDocs("androidx.annotation:annotation:1.8.0-alpha01")
+    kmpDocs("androidx.annotation:annotation:1.8.0-alpha02")
     docs("androidx.annotation:annotation-experimental:1.4.0-rc01")
     docs("androidx.appcompat:appcompat:1.7.0-alpha03")
     docs("androidx.appcompat:appcompat-resources:1.7.0-alpha03")
@@ -39,10 +39,10 @@
     docs("androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
     docs("androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
     docs("androidx.autofill:autofill:1.3.0-alpha01")
-    docs("androidx.benchmark:benchmark-common:1.3.0-alpha01")
-    docs("androidx.benchmark:benchmark-junit4:1.3.0-alpha01")
-    docs("androidx.benchmark:benchmark-macro:1.3.0-alpha01")
-    docs("androidx.benchmark:benchmark-macro-junit4:1.3.0-alpha01")
+    docs("androidx.benchmark:benchmark-common:1.3.0-alpha02")
+    docs("androidx.benchmark:benchmark-junit4:1.3.0-alpha02")
+    docs("androidx.benchmark:benchmark-macro:1.3.0-alpha02")
+    docs("androidx.benchmark:benchmark-macro-junit4:1.3.0-alpha02")
     docs("androidx.biometric:biometric:1.2.0-alpha05")
     docs("androidx.biometric:biometric-ktx:1.2.0-alpha05")
     samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
@@ -69,74 +69,78 @@
     docs("androidx.cardview:cardview:1.0.0")
     kmpDocs("androidx.collection:collection:1.4.0")
     docs("androidx.collection:collection-ktx:1.4.0-rc01")
-    kmpDocs("androidx.compose.animation:animation:1.7.0-alpha04")
-    kmpDocs("androidx.compose.animation:animation-core:1.7.0-alpha04")
-    kmpDocs("androidx.compose.animation:animation-graphics:1.7.0-alpha04")
-    samples("androidx.compose.animation:animation-samples:1.7.0-alpha04")
-    samples("androidx.compose.animation:animation-core-samples:1.7.0-alpha04")
-    samples("androidx.compose.animation:animation-graphics-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.foundation:foundation:1.7.0-alpha04")
-    kmpDocs("androidx.compose.foundation:foundation-layout:1.7.0-alpha04")
-    samples("androidx.compose.foundation:foundation-layout-samples:1.7.0-alpha04")
-    samples("androidx.compose.foundation:foundation-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.material3.adaptive:adaptive:1.0.0-alpha08")
-    kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha08")
-    samples("androidx.compose.material3.adaptive:adaptive-samples:1.0.0-alpha08")
-    kmpDocs("androidx.compose.material3:material3:1.3.0-alpha02")
+    kmpDocs("androidx.compose.animation:animation:1.7.0-alpha05")
+    kmpDocs("androidx.compose.animation:animation-core:1.7.0-alpha05")
+    kmpDocs("androidx.compose.animation:animation-graphics:1.7.0-alpha05")
+    samples("androidx.compose.animation:animation-samples:1.7.0-alpha05")
+    samples("androidx.compose.animation:animation-core-samples:1.7.0-alpha05")
+    samples("androidx.compose.animation:animation-graphics-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.foundation:foundation:1.7.0-alpha05")
+    kmpDocs("androidx.compose.foundation:foundation-layout:1.7.0-alpha05")
+    samples("androidx.compose.foundation:foundation-layout-samples:1.7.0-alpha05")
+    samples("androidx.compose.foundation:foundation-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.material3.adaptive:adaptive:1.0.0-alpha09")
+    kmpDocs("androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha09")
+    docs("androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha09")
+    samples("androidx.compose.material3.adaptive:adaptive-samples:1.0.0-alpha09")
+    kmpDocs("androidx.compose.material3:material3:1.3.0-alpha03")
     kmpDocs("androidx.compose.material3:material3-adaptive-navigation-suite:1.0.0-alpha05")
-    samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.3.0-alpha02")
-    samples("androidx.compose.material3:material3-samples:1.3.0-alpha02")
-    kmpDocs("androidx.compose.material3:material3-window-size-class:1.3.0-alpha02")
-    samples("androidx.compose.material3:material3-window-size-class-samples:1.3.0-alpha02")
-    kmpDocs("androidx.compose.material:material:1.7.0-alpha04")
-    kmpDocs("androidx.compose.material:material-icons-core:1.7.0-alpha04")
-    samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.material:material-ripple:1.7.0-alpha04")
-    samples("androidx.compose.material:material-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha04")
-    docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha04")
-    samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha04")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha04")
-    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha04")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha04")
-    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha04")
-    samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha04")
-    samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha04")
+    samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.3.0-alpha03")
+    docs("androidx.compose.material3:material3-common:1.0.0-alpha01")
+    samples("androidx.compose.material3:material3-samples:1.3.0-alpha03")
+    kmpDocs("androidx.compose.material3:material3-window-size-class:1.3.0-alpha03")
+    samples("androidx.compose.material3:material3-window-size-class-samples:1.3.0-alpha03")
+    kmpDocs("androidx.compose.material:material:1.7.0-alpha05")
+    kmpDocs("androidx.compose.material:material-icons-core:1.7.0-alpha05")
+    samples("androidx.compose.material:material-icons-core-samples:1.7.0-alpha05")
+    docs("androidx.compose.material:material-navigation:1.7.0-alpha05")
+    samples("androidx.compose.material:material-navigation-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.material:material-ripple:1.7.0-alpha05")
+    samples("androidx.compose.material:material-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.runtime:runtime:1.7.0-alpha05")
+    docs("androidx.compose.runtime:runtime-livedata:1.7.0-alpha05")
+    samples("androidx.compose.runtime:runtime-livedata-samples:1.7.0-alpha05")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.7.0-alpha05")
+    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.7.0-alpha05")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.7.0-alpha05")
+    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.runtime:runtime-saveable:1.7.0-alpha05")
+    samples("androidx.compose.runtime:runtime-saveable-samples:1.7.0-alpha05")
+    samples("androidx.compose.runtime:runtime-samples:1.7.0-alpha05")
     docs("androidx.compose.runtime:runtime-tracing:1.0.0-beta01")
-    kmpDocs("androidx.compose.ui:ui:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-geometry:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-graphics:1.7.0-alpha04")
-    samples("androidx.compose.ui:ui-graphics-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-test:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-test-junit4:1.7.0-alpha04")
-    samples("androidx.compose.ui:ui-test-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-text:1.7.0-alpha04")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.7.0-alpha04")
-    samples("androidx.compose.ui:ui-text-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-tooling:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-tooling-data:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-unit:1.7.0-alpha04")
-    samples("androidx.compose.ui:ui-unit-samples:1.7.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-util:1.7.0-alpha04")
-    docs("androidx.compose.ui:ui-viewbinding:1.7.0-alpha04")
-    samples("androidx.compose.ui:ui-viewbinding-samples:1.7.0-alpha04")
-    samples("androidx.compose.ui:ui-samples:1.7.0-alpha04")
-    docs("androidx.concurrent:concurrent-futures:1.2.0-alpha02")
-    docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha02")
+    kmpDocs("androidx.compose.ui:ui:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-geometry:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-graphics:1.7.0-alpha05")
+    samples("androidx.compose.ui:ui-graphics-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-test:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-test-junit4:1.7.0-alpha05")
+    samples("androidx.compose.ui:ui-test-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-text:1.7.0-alpha05")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.7.0-alpha05")
+    samples("androidx.compose.ui:ui-text-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-tooling:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-tooling-data:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-unit:1.7.0-alpha05")
+    samples("androidx.compose.ui:ui-unit-samples:1.7.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-util:1.7.0-alpha05")
+    docs("androidx.compose.ui:ui-viewbinding:1.7.0-alpha05")
+    samples("androidx.compose.ui:ui-viewbinding-samples:1.7.0-alpha05")
+    samples("androidx.compose.ui:ui-samples:1.7.0-alpha05")
+    docs("androidx.concurrent:concurrent-futures:1.2.0-alpha03")
+    docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha03")
     docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
     kmpDocs("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha13")
     docs("androidx.constraintlayout:constraintlayout-core:1.1.0-alpha13")
     docs("androidx.contentpager:contentpager:1.0.0")
     docs("androidx.coordinatorlayout:coordinatorlayout:1.3.0-alpha02")
-    docs("androidx.core:core:1.13.0-alpha05")
+    docs("androidx.core:core:1.13.0-beta01")
     // TODO(b/294531403): Turn on apiSince for core-animation when it releases as alpha
     docsWithoutApiSince("androidx.core:core-animation:1.0.0-rc01")
     docsWithoutApiSince("androidx.core:core-animation-testing:1.0.0-rc01")
     docs("androidx.core:core-google-shortcuts:1.2.0-alpha01")
     docs("androidx.core:core-i18n:1.0.0-alpha01")
-    docs("androidx.core:core-ktx:1.13.0-alpha05")
+    docs("androidx.core:core-ktx:1.13.0-beta01")
     docs("androidx.core:core-location-altitude:1.0.0-alpha01")
     docs("androidx.core:core-performance:1.0.0")
     docs("androidx.core:core-performance-play-services:1.0.0")
@@ -146,7 +150,7 @@
     docs("androidx.core:core-role:1.2.0-alpha01")
     docs("androidx.core:core-splashscreen:1.1.0-alpha02")
     docs("androidx.core:core-telecom:1.0.0-alpha02")
-    docs("androidx.core:core-testing:1.13.0-alpha05")
+    docs("androidx.core:core-testing:1.13.0-beta01")
     docs("androidx.core.uwb:uwb:1.0.0-alpha08")
     docs("androidx.core.uwb:uwb-rxjava3:1.0.0-alpha08")
     docs("androidx.credentials:credentials:1.3.0-alpha01")
@@ -182,10 +186,10 @@
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.6")
-    docs("androidx.fragment:fragment:1.7.0-alpha10")
-    docs("androidx.fragment:fragment-compose:1.7.0-alpha10")
-    docs("androidx.fragment:fragment-ktx:1.7.0-alpha10")
-    docs("androidx.fragment:fragment-testing:1.7.0-alpha10")
+    docs("androidx.fragment:fragment:1.7.0-beta01")
+    docs("androidx.fragment:fragment-compose:1.7.0-beta01")
+    docs("androidx.fragment:fragment-ktx:1.7.0-beta01")
+    docs("androidx.fragment:fragment-testing:1.7.0-beta01")
     docs("androidx.glance:glance:1.1.0-alpha01")
     docs("androidx.glance:glance-appwidget:1.1.0-alpha01")
     samples("androidx.glance:glance-appwidget-samples:1.1.0-alpha01")
@@ -221,27 +225,27 @@
     docs("androidx.leanback:leanback-paging:1.1.0-alpha11")
     docs("androidx.leanback:leanback-preference:1.2.0-alpha04")
     docs("androidx.leanback:leanback-tab:1.1.0-beta01")
-    docs("androidx.lifecycle:lifecycle-common:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-common-java8:2.8.0-alpha01")
+    kmpDocs("androidx.lifecycle:lifecycle-common:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-common-java8:2.8.0-alpha03")
     docs("androidx.lifecycle:lifecycle-extensions:2.2.0")
-    docs("androidx.lifecycle:lifecycle-livedata:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-livedata-core:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-process:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-reactivestreams:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime-compose:2.8.0-alpha01")
-    samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime-testing:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-service:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0-alpha01")
-    samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.0-alpha01")
+    docs("androidx.lifecycle:lifecycle-livedata:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-livedata-core:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-process:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-reactivestreams:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.8.0-alpha03")
+    kmpDocs("androidx.lifecycle:lifecycle-runtime:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-runtime-compose:2.8.0-alpha03")
+    samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.8.0-alpha03")
+    kmpDocs("androidx.lifecycle:lifecycle-runtime-ktx:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-runtime-testing:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-service:2.8.0-alpha03")
+    kmpDocs("androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0-alpha03")
+    samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0-alpha03")
+    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.0-alpha03")
     docs("androidx.loader:loader:1.1.0")
     // localbroadcastmanager is deprecated
     docsWithoutApiSince("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
@@ -276,34 +280,34 @@
     docsWithoutApiSince("androidx.media3:media3-transformer:1.3.0")
     docsWithoutApiSince("androidx.media3:media3-ui:1.3.0")
     docsWithoutApiSince("androidx.media3:media3-ui-leanback:1.3.0")
-    docs("androidx.mediarouter:mediarouter:1.7.0-rc01")
-    docs("androidx.mediarouter:mediarouter-testing:1.7.0-rc01")
+    docs("androidx.mediarouter:mediarouter:1.7.0")
+    docs("androidx.mediarouter:mediarouter-testing:1.7.0")
     docs("androidx.metrics:metrics-performance:1.0.0-beta01")
-    docs("androidx.navigation:navigation-common:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-common-ktx:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-compose:2.8.0-alpha04")
-    samples("androidx.navigation:navigation-compose-samples:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-dynamic-features-fragment:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-dynamic-features-runtime:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-fragment:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-fragment-ktx:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-runtime:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-runtime-ktx:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-testing:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-ui:2.8.0-alpha04")
-    docs("androidx.navigation:navigation-ui-ktx:2.8.0-alpha04")
-    kmpDocs("androidx.paging:paging-common:3.3.0-alpha04")
-    docs("androidx.paging:paging-common-ktx:3.3.0-alpha04")
-    kmpDocs("androidx.paging:paging-compose:3.3.0-alpha04")
-    samples("androidx.paging:paging-compose-samples:3.3.0-alpha04")
-    docs("androidx.paging:paging-guava:3.3.0-alpha04")
-    docs("androidx.paging:paging-runtime:3.3.0-alpha04")
-    docs("androidx.paging:paging-runtime-ktx:3.3.0-alpha04")
-    docs("androidx.paging:paging-rxjava2:3.3.0-alpha04")
-    docs("androidx.paging:paging-rxjava2-ktx:3.3.0-alpha04")
-    docs("androidx.paging:paging-rxjava3:3.3.0-alpha04")
-    samples("androidx.paging:paging-samples:3.3.0-alpha04")
-    kmpDocs("androidx.paging:paging-testing:3.3.0-alpha04")
+    docs("androidx.navigation:navigation-common:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-common-ktx:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-compose:2.8.0-alpha05")
+    samples("androidx.navigation:navigation-compose-samples:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-dynamic-features-fragment:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-dynamic-features-runtime:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-fragment:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-fragment-ktx:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-runtime:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-runtime-ktx:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-testing:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-ui:2.8.0-alpha05")
+    docs("androidx.navigation:navigation-ui-ktx:2.8.0-alpha05")
+    kmpDocs("androidx.paging:paging-common:3.3.0-alpha05")
+    docs("androidx.paging:paging-common-ktx:3.3.0-alpha05")
+    kmpDocs("androidx.paging:paging-compose:3.3.0-alpha05")
+    samples("androidx.paging:paging-compose-samples:3.3.0-alpha05")
+    docs("androidx.paging:paging-guava:3.3.0-alpha05")
+    docs("androidx.paging:paging-runtime:3.3.0-alpha05")
+    docs("androidx.paging:paging-runtime-ktx:3.3.0-alpha05")
+    docs("androidx.paging:paging-rxjava2:3.3.0-alpha05")
+    docs("androidx.paging:paging-rxjava2-ktx:3.3.0-alpha05")
+    docs("androidx.paging:paging-rxjava3:3.3.0-alpha05")
+    samples("androidx.paging:paging-samples:3.3.0-alpha05")
+    kmpDocs("androidx.paging:paging-testing:3.3.0-alpha05")
     docs("androidx.palette:palette:1.0.0")
     docs("androidx.palette:palette-ktx:1.0.0")
     docs("androidx.percentlayout:percentlayout:1.0.1")
@@ -318,7 +322,7 @@
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha13")
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha13")
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-provider:1.0.0-alpha13")
-    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha07")
+    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha08")
     docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha07")
     docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha07")
     docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha07")
@@ -387,8 +391,8 @@
     // TODO(243405142) clean-up
     docsWithoutApiSince("androidx.tracing:tracing-perfetto-common:1.0.0-alpha16")
     docs("androidx.tracing:tracing-perfetto-handshake:1.0.0")
-    docs("androidx.transition:transition:1.5.0-alpha06")
-    docs("androidx.transition:transition-ktx:1.5.0-alpha06")
+    docs("androidx.transition:transition:1.5.0-beta01")
+    docs("androidx.transition:transition-ktx:1.5.0-beta01")
     docs("androidx.tv:tv-foundation:1.0.0-alpha10")
     docs("androidx.tv:tv-material:1.0.0-alpha10")
     samples("androidx.tv:tv-samples:1.0.0-alpha10")
@@ -399,16 +403,16 @@
     docs("androidx.versionedparcelable:versionedparcelable:1.2.0")
     docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
     docs("androidx.viewpager:viewpager:1.1.0-alpha01")
-    docs("androidx.wear.compose:compose-foundation:1.4.0-alpha04")
-    samples("androidx.wear.compose:compose-foundation-samples:1.4.0-alpha04")
-    docs("androidx.wear.compose:compose-material:1.4.0-alpha04")
-    docs("androidx.wear.compose:compose-material-core:1.4.0-alpha04")
-    samples("androidx.wear.compose:compose-material-samples:1.4.0-alpha04")
+    docs("androidx.wear.compose:compose-foundation:1.4.0-alpha05")
+    samples("androidx.wear.compose:compose-foundation-samples:1.4.0-alpha05")
+    docs("androidx.wear.compose:compose-material:1.4.0-alpha05")
+    docs("androidx.wear.compose:compose-material-core:1.4.0-alpha05")
+    samples("androidx.wear.compose:compose-material-samples:1.4.0-alpha05")
     docs("androidx.wear.compose:compose-material3:1.0.0-alpha19")
-    samples("androidx.wear.compose:compose-material3-samples:1.4.0-alpha04")
-    docs("androidx.wear.compose:compose-navigation:1.4.0-alpha04")
-    samples("androidx.wear.compose:compose-navigation-samples:1.4.0-alpha04")
-    docs("androidx.wear.compose:compose-ui-tooling:1.4.0-alpha04")
+    samples("androidx.wear.compose:compose-material3-samples:1.4.0-alpha05")
+    docs("androidx.wear.compose:compose-navigation:1.4.0-alpha05")
+    samples("androidx.wear.compose:compose-navigation-samples:1.4.0-alpha05")
+    docs("androidx.wear.compose:compose-ui-tooling:1.4.0-alpha05")
     docs("androidx.wear.protolayout:protolayout:1.2.0-alpha01")
     docs("androidx.wear.protolayout:protolayout-expression:1.2.0-alpha01")
     docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.2.0-alpha01")
@@ -448,7 +452,7 @@
     docs("androidx.wear:wear-remote-interactions:1.1.0-alpha02")
     samples("androidx.wear:wear-remote-interactions-samples:1.1.0-alpha02")
     docs("androidx.wear:wear-tooling-preview:1.0.0")
-    docs("androidx.webkit:webkit:1.11.0-alpha02")
+    docs("androidx.webkit:webkit:1.11.0-beta01")
     docs("androidx.window.extensions.core:core:1.0.0")
     docs("androidx.window:window:1.3.0-alpha03")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index e9596b8..183343b 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -344,6 +344,7 @@
     docs(project(":security:security-crypto-ktx"))
     docs(project(":security:security-identity-credential"))
     docs(project(":security:security-mls"))
+    docs(project(":security:security-state"))
     docs(project(":sharetarget:sharetarget"))
     docs(project(":slice:slice-builders"))
     docs(project(":slice:slice-builders-ktx"))
diff --git a/dynamicanimation/dynamicanimation/api/current.txt b/dynamicanimation/dynamicanimation/api/current.txt
index 1efc224..c23948c 100644
--- a/dynamicanimation/dynamicanimation/api/current.txt
+++ b/dynamicanimation/dynamicanimation/api/current.txt
@@ -51,10 +51,10 @@
     method public void onAnimationUpdate(androidx.dynamicanimation.animation.DynamicAnimation!, float, float);
   }
 
-  public abstract static class DynamicAnimation.ViewProperty extends androidx.dynamicanimation.animation.FloatPropertyCompat<android.view.View> {
+  public abstract static class DynamicAnimation.ViewProperty extends androidx.dynamicanimation.animation.FloatPropertyCompat<android.view.View!> {
   }
 
-  public final class FlingAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.FlingAnimation> {
+  public final class FlingAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.FlingAnimation!> {
     ctor public FlingAnimation(androidx.dynamicanimation.animation.FloatValueHolder!);
     ctor public <K> FlingAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K!>!);
     method public float getFriction();
@@ -83,7 +83,7 @@
     method public void postFrameCallback(Runnable);
   }
 
-  public final class SpringAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.SpringAnimation> {
+  public final class SpringAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.SpringAnimation!> {
     ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!);
     ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!, float);
     ctor public <K> SpringAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K!>!);
diff --git a/dynamicanimation/dynamicanimation/api/restricted_current.txt b/dynamicanimation/dynamicanimation/api/restricted_current.txt
index 1efc224..c23948c 100644
--- a/dynamicanimation/dynamicanimation/api/restricted_current.txt
+++ b/dynamicanimation/dynamicanimation/api/restricted_current.txt
@@ -51,10 +51,10 @@
     method public void onAnimationUpdate(androidx.dynamicanimation.animation.DynamicAnimation!, float, float);
   }
 
-  public abstract static class DynamicAnimation.ViewProperty extends androidx.dynamicanimation.animation.FloatPropertyCompat<android.view.View> {
+  public abstract static class DynamicAnimation.ViewProperty extends androidx.dynamicanimation.animation.FloatPropertyCompat<android.view.View!> {
   }
 
-  public final class FlingAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.FlingAnimation> {
+  public final class FlingAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.FlingAnimation!> {
     ctor public FlingAnimation(androidx.dynamicanimation.animation.FloatValueHolder!);
     ctor public <K> FlingAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K!>!);
     method public float getFriction();
@@ -83,7 +83,7 @@
     method public void postFrameCallback(Runnable);
   }
 
-  public final class SpringAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.SpringAnimation> {
+  public final class SpringAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.SpringAnimation!> {
     ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!);
     ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!, float);
     ctor public <K> SpringAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K!>!);
diff --git a/emoji2/emoji2/api/current.txt b/emoji2/emoji2/api/current.txt
index a81c4c09..494e7a5 100644
--- a/emoji2/emoji2/api/current.txt
+++ b/emoji2/emoji2/api/current.txt
@@ -84,7 +84,7 @@
     method public androidx.emoji2.text.EmojiSpan createSpan(androidx.emoji2.text.TypefaceEmojiRasterizer);
   }
 
-  public class EmojiCompatInitializer implements androidx.startup.Initializer<java.lang.Boolean> {
+  public class EmojiCompatInitializer implements androidx.startup.Initializer<java.lang.Boolean!> {
     ctor public EmojiCompatInitializer();
     method public Boolean create(android.content.Context);
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>!>!> dependencies();
diff --git a/emoji2/emoji2/api/restricted_current.txt b/emoji2/emoji2/api/restricted_current.txt
index a81c4c09..494e7a5 100644
--- a/emoji2/emoji2/api/restricted_current.txt
+++ b/emoji2/emoji2/api/restricted_current.txt
@@ -84,7 +84,7 @@
     method public androidx.emoji2.text.EmojiSpan createSpan(androidx.emoji2.text.TypefaceEmojiRasterizer);
   }
 
-  public class EmojiCompatInitializer implements androidx.startup.Initializer<java.lang.Boolean> {
+  public class EmojiCompatInitializer implements androidx.startup.Initializer<java.lang.Boolean!> {
     ctor public EmojiCompatInitializer();
     method public Boolean create(android.content.Context);
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>!>!> dependencies();
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
index 817bdc4..3c27b95 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/OnCreateDialogIncorrectCallbackDetector.kt
@@ -74,14 +74,14 @@
 
     private inner class UastHandler(val context: JavaContext) : UElementHandler() {
         override fun visitClass(node: UClass) {
-            if (isKotlin(context.psiFile) &&
+            if (isKotlin(node.lang) &&
                 (node.sourcePsi as? KtClassOrObject)?.getSuperNames()?.firstOrNull() !=
                 DIALOG_FRAGMENT_CLASS
             ) {
                 return
             }
 
-            if (!isKotlin(context.psiFile) &&
+            if (!isKotlin(node.lang) &&
                 (node.uastSuperTypes.firstOrNull()?.type as? PsiClassReferenceType)
                     ?.className != DIALOG_FRAGMENT_CLASS
             ) {
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UnsafeFragmentLifecycleObserverDetector.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UnsafeFragmentLifecycleObserverDetector.kt
index 8ed43ac..b07db9f 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UnsafeFragmentLifecycleObserverDetector.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UnsafeFragmentLifecycleObserverDetector.kt
@@ -178,7 +178,7 @@
             ) {
                 val argType = PsiTypesUtil.getPsiClass(arg.getExpressionType())
                 if (argType == call.getContainingUClass()?.javaPsi) {
-                    val methodFix = if (isKotlin(context.psiFile)) {
+                    val methodFix = if (isKotlin(call.lang)) {
                         "viewLifecycleOwner"
                     } else {
                         "getViewLifecycleOwner()"
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseGetLayoutInflater.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseGetLayoutInflater.kt
index ffd14a5..853bac5 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseGetLayoutInflater.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseGetLayoutInflater.kt
@@ -31,6 +31,7 @@
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiType
 import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
 import org.jetbrains.uast.getContainingUClass
 
 /**
@@ -85,8 +86,8 @@
             issue = ISSUE,
             location = context.getLocation(node),
             message = "Use of LayoutInflater.from($methodParameter) detected. Consider using " +
-                "${correctMethod(context)} instead",
-            quickfixData = createFix(correctMethod(context), methodParameter)
+                "${correctMethod(node)} instead",
+            quickfixData = createFix(correctMethod(node), methodParameter)
         )
     }
 
@@ -105,13 +106,13 @@
             issue = ISSUE,
             location = context.getLocation(node),
             message = "Use of LayoutInflater.from(Context) detected. Consider using " +
-                "${correctMethod(context)} instead",
+                "${correctMethod(node)} instead",
             quickfixData = null
         )
     }
 
-    private fun correctMethod(context: JavaContext): String {
-        return if (isKotlin(context.psiFile)) {
+    private fun correctMethod(context: UElement): String {
+        return if (isKotlin(context.lang)) {
             "layoutInflater"
         } else {
             "getLayoutInflater()"
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt
index 193833b..82e4caf 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/UseRequireInsteadOfGet.kt
@@ -99,7 +99,6 @@
     }
 
     override fun createUastHandler(context: JavaContext): UElementHandler? {
-        val isKotlin = isKotlin(context.psiFile)
         return object : UElementHandler() {
 
             /** This covers Kotlin accessor syntax expressions like "fragment.arguments" */
@@ -159,7 +158,7 @@
                 // Note we go up potentially two parents - the first one may just be the qualified reference expression
                 val nearestNonQualifiedReferenceParent =
                     skipParenthesizedExprUp(node.nearestNonQualifiedReferenceParent) ?: return
-                if (isKotlin && nearestNonQualifiedReferenceParent.isNullCheckBlock()) {
+                if (isKotlin(node.lang) && nearestNonQualifiedReferenceParent.isNullCheckBlock()) {
                     // We're a double-bang expression (!!)
                     val parentSourceToReplace =
                         nearestNonQualifiedReferenceParent.asSourceString()
@@ -216,7 +215,7 @@
                 nearestNonQualifiedRefParent: UCallExpression
             ) = enclosingMethodCall.parameterList.parametersCount == 1 ||
                 (
-                    isKotlin &&
+                    isKotlin(nearestNonQualifiedRefParent.lang) &&
                         nearestNonQualifiedRefParent.getArgumentForParameter(1) == null
                     )
 
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 25050759..42c5a91 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -41,7 +41,7 @@
     api("androidx.lifecycle:lifecycle-livedata-core:2.6.1")
     api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     api("androidx.savedstate:savedstate:1.2.1")
     api("androidx.annotation:annotation-experimental:1.4.0")
     api(libs.kotlinStdlib)
diff --git a/glance/glance-appwidget-testing/api/1.1.0-beta01.txt b/glance/glance-appwidget-testing/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..63b53e2
--- /dev/null
+++ b/glance/glance-appwidget-testing/api/1.1.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.glance.appwidget.testing.unit {
+
+  public sealed interface GlanceAppWidgetUnitTest extends androidx.glance.testing.GlanceNodeAssertionsProvider<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> {
+    method public void awaitIdle();
+    method public void provideComposable(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public void setAppWidgetSize(long size);
+    method public void setContext(android.content.Context context);
+    method public <T> void setState(T state);
+  }
+
+  public final class GlanceAppWidgetUnitTestDefaults {
+    method public androidx.glance.GlanceId glanceId();
+    method public int hostCategory();
+    method public long size();
+    field public static final androidx.glance.appwidget.testing.unit.GlanceAppWidgetUnitTestDefaults INSTANCE;
+  }
+
+  public final class GlanceAppWidgetUnitTestKt {
+    method public static void runGlanceAppWidgetUnitTest(optional long timeout, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.testing.unit.GlanceAppWidgetUnitTest,kotlin.Unit> block);
+  }
+
+  public final class UnitTestAssertionExtensionsKt {
+    method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasRunCallbackClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.content.BroadcastReceiver> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.Intent intent);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String intentAction, optional android.content.ComponentName? componentName);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional boolean isForegroundService);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.Intent intent, optional boolean isForegroundService);
+    method public static inline <reified T extends android.app.Service> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, optional boolean isForegroundService);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertIsChecked(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertIsNotChecked(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+  }
+
+  public final class UnitTestFiltersKt {
+    method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasRunCallbackClickAction(optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.content.BroadcastReceiver> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(android.content.ComponentName componentName);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(android.content.Intent intent);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(String intentAction, optional android.content.ComponentName? componentName);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(android.content.Intent intent, optional boolean isForegroundService);
+    method public static inline <reified T extends android.app.Service> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(optional boolean isForegroundService);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isChecked();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateCircularProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateLinearProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isLinearProgressIndicator(float progress);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isNotChecked();
+  }
+
+}
+
diff --git a/glance/glance-appwidget-testing/api/res-1.1.0-beta01.txt b/glance/glance-appwidget-testing/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/glance/glance-appwidget-testing/api/res-1.1.0-beta01.txt
diff --git a/glance/glance-appwidget-testing/api/restricted_1.1.0-beta01.txt b/glance/glance-appwidget-testing/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..b3ace96
--- /dev/null
+++ b/glance/glance-appwidget-testing/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,60 @@
+// Signature format: 4.0
+package androidx.glance.appwidget.testing.unit {
+
+  public sealed interface GlanceAppWidgetUnitTest extends androidx.glance.testing.GlanceNodeAssertionsProvider<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> {
+    method public void awaitIdle();
+    method public void provideComposable(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public void setAppWidgetSize(long size);
+    method public void setContext(android.content.Context context);
+    method public <T> void setState(T state);
+  }
+
+  public final class GlanceAppWidgetUnitTestDefaults {
+    method public androidx.glance.GlanceId glanceId();
+    method public int hostCategory();
+    method public long size();
+    field public static final androidx.glance.appwidget.testing.unit.GlanceAppWidgetUnitTestDefaults INSTANCE;
+  }
+
+  public final class GlanceAppWidgetUnitTestKt {
+    method public static void runGlanceAppWidgetUnitTest(optional long timeout, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.testing.unit.GlanceAppWidgetUnitTest,kotlin.Unit> block);
+  }
+
+  public final class UnitTestAssertionExtensionsKt {
+    method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasRunCallbackClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, optional androidx.glance.action.ActionParameters parameters);
+    method @kotlin.PublishedApi internal static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasRunCallbackClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<? extends androidx.glance.appwidget.action.ActionCallback> callbackClass, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.content.BroadcastReceiver> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.Intent intent);
+    method @kotlin.PublishedApi internal static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<? extends android.content.BroadcastReceiver> receiverClass);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasSendBroadcastClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String intentAction, optional android.content.ComponentName? componentName);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional boolean isForegroundService);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.Intent intent, optional boolean isForegroundService);
+    method public static inline <reified T extends android.app.Service> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, optional boolean isForegroundService);
+    method @kotlin.PublishedApi internal static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartServiceClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<? extends android.app.Service> serviceClass, optional boolean isForegroundService);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertIsChecked(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertIsNotChecked(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+  }
+
+  public final class UnitTestFiltersKt {
+    method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasRunCallbackClickAction(optional androidx.glance.action.ActionParameters parameters);
+    method @kotlin.PublishedApi internal static <T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasRunCallbackClickAction(Class<T> callbackClass, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.content.BroadcastReceiver> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(android.content.ComponentName componentName);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(android.content.Intent intent);
+    method @kotlin.PublishedApi internal static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(Class<? extends android.content.BroadcastReceiver> receiverClass);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasSendBroadcastAction(String intentAction, optional android.content.ComponentName? componentName);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(android.content.Intent intent, optional boolean isForegroundService);
+    method public static inline <reified T extends android.app.Service> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(optional boolean isForegroundService);
+    method @kotlin.PublishedApi internal static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(Class<? extends android.app.Service> serviceClass, optional boolean isForegroundService);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isChecked();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateCircularProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateLinearProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isLinearProgressIndicator(float progress);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isNotChecked();
+  }
+
+}
+
diff --git a/glance/glance-appwidget/api/1.1.0-beta01.txt b/glance/glance-appwidget/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..c2b95cf
--- /dev/null
+++ b/glance/glance-appwidget/api/1.1.0-beta01.txt
@@ -0,0 +1,311 @@
+// Signature format: 4.0
+package androidx.glance.appwidget {
+
+  public final class AndroidRemoteViewsKt {
+    method @androidx.compose.runtime.Composable public static void AndroidRemoteViews(android.widget.RemoteViews remoteViews, optional androidx.glance.GlanceModifier modifier);
+    method @androidx.compose.runtime.Composable public static void AndroidRemoteViews(android.widget.RemoteViews remoteViews, @IdRes int containerViewId, optional androidx.glance.GlanceModifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class AppWidgetBackgroundKt {
+    method public static androidx.glance.GlanceModifier appWidgetBackground(androidx.glance.GlanceModifier);
+  }
+
+  public final class AppWidgetComposerKt {
+    method public static suspend Object? compose(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle? options, optional androidx.compose.ui.unit.DpSize? size, optional Object? state, kotlin.coroutines.Continuation<? super android.widget.RemoteViews>);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static kotlinx.coroutines.flow.Flow<android.widget.RemoteViews> runComposition(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle options, optional java.util.List<androidx.compose.ui.unit.DpSize>? sizes, optional Object? state);
+  }
+
+  public final class BackgroundKt {
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, long day, long night);
+  }
+
+  public abstract sealed class CheckBoxColors {
+  }
+
+  public final class CheckBoxKt {
+    method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines, optional String? key);
+  }
+
+  public final class CheckboxDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.CheckBoxColors colors();
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.CheckBoxColors colors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.CheckBoxColors colors(long checkedColor, long uncheckedColor);
+    field public static final androidx.glance.appwidget.CheckboxDefaults INSTANCE;
+  }
+
+  public final class CircularProgressIndicatorKt {
+    method @androidx.compose.runtime.Composable public static void CircularProgressIndicator(optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider color);
+  }
+
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> LocalAppWidgetOptions;
+  }
+
+  public final class CornerRadiusKt {
+    method public static androidx.glance.GlanceModifier cornerRadius(androidx.glance.GlanceModifier, float radius);
+    method public static androidx.glance.GlanceModifier cornerRadius(androidx.glance.GlanceModifier, @DimenRes int radius);
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGlanceRemoteViewsApi {
+  }
+
+  public abstract class GlanceAppWidget {
+    ctor public GlanceAppWidget(optional @LayoutRes int errorUiLayout);
+    method public androidx.glance.appwidget.SizeMode getSizeMode();
+    method public androidx.glance.state.GlanceStateDefinition<?>? getStateDefinition();
+    method @kotlin.jvm.Throws(exceptionClasses=Throwable::class) public void onCompositionError(android.content.Context context, androidx.glance.GlanceId glanceId, int appWidgetId, Throwable throwable) throws java.lang.Throwable;
+    method public suspend Object? onDelete(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public abstract suspend Object? provideGlance(android.content.Context context, androidx.glance.GlanceId id, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public final suspend Object? update(android.content.Context context, androidx.glance.GlanceId id, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public androidx.glance.appwidget.SizeMode sizeMode;
+    property public androidx.glance.state.GlanceStateDefinition<?>? stateDefinition;
+  }
+
+  public final class GlanceAppWidgetKt {
+    method public static suspend Object? provideContent(androidx.glance.appwidget.GlanceAppWidget, kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.coroutines.Continuation<?>);
+    method public static suspend Object? updateAll(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend inline <reified State> Object? updateIf(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.jvm.functions.Function1<? super State,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public final class GlanceAppWidgetManager {
+    ctor public GlanceAppWidgetManager(android.content.Context context);
+    method public int getAppWidgetId(androidx.glance.GlanceId glanceId);
+    method public suspend Object? getAppWidgetSizes(androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.compose.ui.unit.DpSize>>);
+    method public androidx.glance.GlanceId? getGlanceIdBy(android.content.Intent configurationIntent);
+    method public androidx.glance.GlanceId getGlanceIdBy(int appWidgetId);
+    method public suspend <T extends androidx.glance.appwidget.GlanceAppWidget> Object? getGlanceIds(Class<T> provider, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.glance.GlanceId>>);
+    method public suspend <T extends androidx.glance.appwidget.GlanceAppWidgetReceiver> Object? requestPinGlanceAppWidget(Class<T> receiver, optional androidx.glance.appwidget.GlanceAppWidget? preview, optional Object? previewState, optional android.app.PendingIntent? successCallback, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+  }
+
+  public abstract class GlanceAppWidgetReceiver extends android.appwidget.AppWidgetProvider {
+    ctor public GlanceAppWidgetReceiver();
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext getCoroutineContext();
+    method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
+    property @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext coroutineContext;
+    property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
+    field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
+    field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
+  }
+
+  public static final class GlanceAppWidgetReceiver.Companion {
+  }
+
+  @SuppressCompatibility @androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi public final class GlanceRemoteViews {
+    ctor public GlanceRemoteViews();
+    method public suspend Object? compose(android.content.Context context, long size, optional Object? state, optional android.os.Bundle appWidgetOptions, kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.coroutines.Continuation<? super androidx.glance.appwidget.RemoteViewsCompositionResult>);
+  }
+
+  public final class ImageProvidersKt {
+    method public static androidx.glance.ImageProvider ImageProvider(android.net.Uri uri);
+  }
+
+  public final class LinearProgressIndicatorKt {
+    method @androidx.compose.runtime.Composable public static void LinearProgressIndicator(optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider color, optional androidx.glance.unit.ColorProvider backgroundColor);
+    method @androidx.compose.runtime.Composable public static void LinearProgressIndicator(float progress, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider color, optional androidx.glance.unit.ColorProvider backgroundColor);
+  }
+
+  public final class ProgressIndicatorDefaults {
+    method public androidx.glance.unit.ColorProvider getBackgroundColorProvider();
+    method public androidx.glance.unit.ColorProvider getIndicatorColorProvider();
+    property public final androidx.glance.unit.ColorProvider BackgroundColorProvider;
+    property public final androidx.glance.unit.ColorProvider IndicatorColorProvider;
+    field public static final androidx.glance.appwidget.ProgressIndicatorDefaults INSTANCE;
+  }
+
+  public final class RadioButtonColors {
+  }
+
+  public final class RadioButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.RadioButtonColors colors();
+    method public androidx.glance.appwidget.RadioButtonColors colors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method public androidx.glance.appwidget.RadioButtonColors colors(long checkedColor, long uncheckedColor);
+    field public static final androidx.glance.appwidget.RadioButtonDefaults INSTANCE;
+  }
+
+  public final class RadioButtonKt {
+    method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, androidx.glance.action.Action? onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines, optional String? key);
+    method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
+  }
+
+  @SuppressCompatibility @androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi public final class RemoteViewsCompositionResult {
+    ctor public RemoteViewsCompositionResult(android.widget.RemoteViews remoteViews);
+    method public android.widget.RemoteViews getRemoteViews();
+    property public final android.widget.RemoteViews remoteViews;
+  }
+
+  public sealed interface SizeMode {
+  }
+
+  public static final class SizeMode.Exact implements androidx.glance.appwidget.SizeMode {
+    field public static final androidx.glance.appwidget.SizeMode.Exact INSTANCE;
+  }
+
+  public static final class SizeMode.Responsive implements androidx.glance.appwidget.SizeMode {
+    ctor public SizeMode.Responsive(java.util.Set<androidx.compose.ui.unit.DpSize> sizes);
+    method public java.util.Set<androidx.compose.ui.unit.DpSize> getSizes();
+    property public final java.util.Set<androidx.compose.ui.unit.DpSize> sizes;
+  }
+
+  public static final class SizeMode.Single implements androidx.glance.appwidget.SizeMode {
+    field public static final androidx.glance.appwidget.SizeMode.Single INSTANCE;
+  }
+
+  public abstract sealed class SwitchColors {
+  }
+
+  public final class SwitchDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.SwitchColors colors();
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.SwitchColors colors(androidx.glance.unit.ColorProvider checkedThumbColor, androidx.glance.unit.ColorProvider uncheckedThumbColor, androidx.glance.unit.ColorProvider checkedTrackColor, androidx.glance.unit.ColorProvider uncheckedTrackColor);
+    field public static final androidx.glance.appwidget.SwitchDefaults INSTANCE;
+  }
+
+  public final class SwitchKt {
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines, optional String? key);
+  }
+
+}
+
+package androidx.glance.appwidget.action {
+
+  public interface ActionCallback {
+    method public suspend Object? onAction(android.content.Context context, androidx.glance.GlanceId glanceId, androidx.glance.action.ActionParameters parameters, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public final class RunCallbackActionKt {
+    method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(optional androidx.glance.action.ActionParameters parameters);
+    method public static <T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(Class<T> callbackClass, optional androidx.glance.action.ActionParameters parameters);
+  }
+
+  public final class SendBroadcastActionKt {
+    method public static inline <reified T extends android.content.BroadcastReceiver> androidx.glance.action.Action actionSendBroadcast();
+    method public static androidx.glance.action.Action actionSendBroadcast(android.content.ComponentName componentName);
+    method public static androidx.glance.action.Action actionSendBroadcast(android.content.Intent intent);
+    method public static <T extends android.content.BroadcastReceiver> androidx.glance.action.Action actionSendBroadcast(Class<T> receiver);
+    method public static androidx.glance.action.Action actionSendBroadcast(String action, optional android.content.ComponentName? componentName);
+  }
+
+  public final class StartActivityIntentActionKt {
+    method public static androidx.glance.action.Action actionStartActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static androidx.glance.action.Action actionStartActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+  }
+
+  public final class StartServiceActionKt {
+    method public static androidx.glance.action.Action actionStartService(android.content.ComponentName componentName, optional boolean isForegroundService);
+    method public static androidx.glance.action.Action actionStartService(android.content.Intent intent, optional boolean isForegroundService);
+    method public static inline <reified T extends android.app.Service> androidx.glance.action.Action actionStartService(optional boolean isForegroundService);
+    method public static <T extends android.app.Service> androidx.glance.action.Action actionStartService(Class<T> service, optional boolean isForegroundService);
+  }
+
+  public final class ToggleableKt {
+    method public static androidx.glance.action.ActionParameters.Key<java.lang.Boolean> getToggleableStateKey();
+    property public static final androidx.glance.action.ActionParameters.Key<java.lang.Boolean> ToggleableStateKey;
+  }
+
+}
+
+package androidx.glance.appwidget.components {
+
+  public final class ButtonsKt {
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+  }
+
+  public final class ScaffoldKt {
+    method @androidx.compose.runtime.Composable public static void Scaffold(kotlin.jvm.functions.Function0<kotlin.Unit> titleBar, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider backgroundColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class TitleBarKt {
+    method @androidx.compose.runtime.Composable public static void TitleBar(androidx.glance.ImageProvider startIcon, String title, optional androidx.glance.unit.ColorProvider? iconColor, optional androidx.glance.unit.ColorProvider textColor, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.FontFamily? fontFamily, optional kotlin.jvm.functions.Function1<? super androidx.glance.layout.RowScope,kotlin.Unit> actions);
+  }
+
+}
+
+package androidx.glance.appwidget.lazy {
+
+  public abstract sealed class GridCells {
+  }
+
+  @RequiresApi(31) public static final class GridCells.Adaptive extends androidx.glance.appwidget.lazy.GridCells {
+    ctor public GridCells.Adaptive(float minSize);
+    method public float getMinSize();
+    property public final float minSize;
+  }
+
+  public static final class GridCells.Fixed extends androidx.glance.appwidget.lazy.GridCells {
+    ctor public GridCells.Fixed(int count);
+    method public int getCount();
+    property public final int count;
+  }
+
+  @androidx.glance.appwidget.lazy.LazyScopeMarker public interface LazyItemScope {
+  }
+
+  public final class LazyListKt {
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void LazyColumn(android.os.Bundle activityOptions, optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyListScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyListScope, T[] items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyListScope, T[] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  @androidx.glance.appwidget.lazy.LazyScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyListScope {
+    method public void item(optional long itemId, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    field public static final androidx.glance.appwidget.lazy.LazyListScope.Companion Companion;
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public static final class LazyListScope.Companion {
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  @kotlin.DslMarker public @interface LazyScopeMarker {
+  }
+
+  public final class LazyVerticalGridKt {
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, android.os.Bundle activityOptions, optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyVerticalGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyVerticalGridScope, T[] items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyVerticalGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyVerticalGridScope, T[] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  @androidx.glance.appwidget.lazy.LazyScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyVerticalGridScope {
+    method public void item(optional long itemId, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    field public static final androidx.glance.appwidget.lazy.LazyVerticalGridScope.Companion Companion;
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public static final class LazyVerticalGridScope.Companion {
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+}
+
+package androidx.glance.appwidget.state {
+
+  public final class GlanceAppWidgetStateKt {
+    method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T>);
+    method public static suspend <T> Object? getAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T>);
+    method public static suspend Object? updateAppWidgetState(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.core.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> updateState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T>);
+  }
+
+}
+
diff --git a/glance/glance-appwidget/api/res-1.1.0-beta01.txt b/glance/glance-appwidget/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..dba4906
--- /dev/null
+++ b/glance/glance-appwidget/api/res-1.1.0-beta01.txt
@@ -0,0 +1,2 @@
+bool glance_appwidget_available
+layout glance_default_loading_layout
diff --git a/glance/glance-appwidget/api/restricted_1.1.0-beta01.txt b/glance/glance-appwidget/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..c2b95cf
--- /dev/null
+++ b/glance/glance-appwidget/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,311 @@
+// Signature format: 4.0
+package androidx.glance.appwidget {
+
+  public final class AndroidRemoteViewsKt {
+    method @androidx.compose.runtime.Composable public static void AndroidRemoteViews(android.widget.RemoteViews remoteViews, optional androidx.glance.GlanceModifier modifier);
+    method @androidx.compose.runtime.Composable public static void AndroidRemoteViews(android.widget.RemoteViews remoteViews, @IdRes int containerViewId, optional androidx.glance.GlanceModifier modifier, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class AppWidgetBackgroundKt {
+    method public static androidx.glance.GlanceModifier appWidgetBackground(androidx.glance.GlanceModifier);
+  }
+
+  public final class AppWidgetComposerKt {
+    method public static suspend Object? compose(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle? options, optional androidx.compose.ui.unit.DpSize? size, optional Object? state, kotlin.coroutines.Continuation<? super android.widget.RemoteViews>);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static kotlinx.coroutines.flow.Flow<android.widget.RemoteViews> runComposition(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle options, optional java.util.List<androidx.compose.ui.unit.DpSize>? sizes, optional Object? state);
+  }
+
+  public final class BackgroundKt {
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, long day, long night);
+  }
+
+  public abstract sealed class CheckBoxColors {
+  }
+
+  public final class CheckBoxKt {
+    method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void CheckBox(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.CheckBoxColors colors, optional int maxLines, optional String? key);
+  }
+
+  public final class CheckboxDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.CheckBoxColors colors();
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.CheckBoxColors colors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.CheckBoxColors colors(long checkedColor, long uncheckedColor);
+    field public static final androidx.glance.appwidget.CheckboxDefaults INSTANCE;
+  }
+
+  public final class CircularProgressIndicatorKt {
+    method @androidx.compose.runtime.Composable public static void CircularProgressIndicator(optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider color);
+  }
+
+  public final class CompositionLocalsKt {
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> getLocalAppWidgetOptions();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.os.Bundle> LocalAppWidgetOptions;
+  }
+
+  public final class CornerRadiusKt {
+    method public static androidx.glance.GlanceModifier cornerRadius(androidx.glance.GlanceModifier, float radius);
+    method public static androidx.glance.GlanceModifier cornerRadius(androidx.glance.GlanceModifier, @DimenRes int radius);
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGlanceRemoteViewsApi {
+  }
+
+  public abstract class GlanceAppWidget {
+    ctor public GlanceAppWidget(optional @LayoutRes int errorUiLayout);
+    method public androidx.glance.appwidget.SizeMode getSizeMode();
+    method public androidx.glance.state.GlanceStateDefinition<?>? getStateDefinition();
+    method @kotlin.jvm.Throws(exceptionClasses=Throwable::class) public void onCompositionError(android.content.Context context, androidx.glance.GlanceId glanceId, int appWidgetId, Throwable throwable) throws java.lang.Throwable;
+    method public suspend Object? onDelete(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public abstract suspend Object? provideGlance(android.content.Context context, androidx.glance.GlanceId id, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public final suspend Object? update(android.content.Context context, androidx.glance.GlanceId id, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public androidx.glance.appwidget.SizeMode sizeMode;
+    property public androidx.glance.state.GlanceStateDefinition<?>? stateDefinition;
+  }
+
+  public final class GlanceAppWidgetKt {
+    method public static suspend Object? provideContent(androidx.glance.appwidget.GlanceAppWidget, kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.coroutines.Continuation<?>);
+    method public static suspend Object? updateAll(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend inline <reified State> Object? updateIf(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.jvm.functions.Function1<? super State,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public final class GlanceAppWidgetManager {
+    ctor public GlanceAppWidgetManager(android.content.Context context);
+    method public int getAppWidgetId(androidx.glance.GlanceId glanceId);
+    method public suspend Object? getAppWidgetSizes(androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.compose.ui.unit.DpSize>>);
+    method public androidx.glance.GlanceId? getGlanceIdBy(android.content.Intent configurationIntent);
+    method public androidx.glance.GlanceId getGlanceIdBy(int appWidgetId);
+    method public suspend <T extends androidx.glance.appwidget.GlanceAppWidget> Object? getGlanceIds(Class<T> provider, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.glance.GlanceId>>);
+    method public suspend <T extends androidx.glance.appwidget.GlanceAppWidgetReceiver> Object? requestPinGlanceAppWidget(Class<T> receiver, optional androidx.glance.appwidget.GlanceAppWidget? preview, optional Object? previewState, optional android.app.PendingIntent? successCallback, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+  }
+
+  public abstract class GlanceAppWidgetReceiver extends android.appwidget.AppWidgetProvider {
+    ctor public GlanceAppWidgetReceiver();
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext getCoroutineContext();
+    method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
+    property @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public kotlin.coroutines.CoroutineContext coroutineContext;
+    property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
+    field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
+    field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
+  }
+
+  public static final class GlanceAppWidgetReceiver.Companion {
+  }
+
+  @SuppressCompatibility @androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi public final class GlanceRemoteViews {
+    ctor public GlanceRemoteViews();
+    method public suspend Object? compose(android.content.Context context, long size, optional Object? state, optional android.os.Bundle appWidgetOptions, kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.coroutines.Continuation<? super androidx.glance.appwidget.RemoteViewsCompositionResult>);
+  }
+
+  public final class ImageProvidersKt {
+    method public static androidx.glance.ImageProvider ImageProvider(android.net.Uri uri);
+  }
+
+  public final class LinearProgressIndicatorKt {
+    method @androidx.compose.runtime.Composable public static void LinearProgressIndicator(optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider color, optional androidx.glance.unit.ColorProvider backgroundColor);
+    method @androidx.compose.runtime.Composable public static void LinearProgressIndicator(float progress, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider color, optional androidx.glance.unit.ColorProvider backgroundColor);
+  }
+
+  public final class ProgressIndicatorDefaults {
+    method public androidx.glance.unit.ColorProvider getBackgroundColorProvider();
+    method public androidx.glance.unit.ColorProvider getIndicatorColorProvider();
+    property public final androidx.glance.unit.ColorProvider BackgroundColorProvider;
+    property public final androidx.glance.unit.ColorProvider IndicatorColorProvider;
+    field public static final androidx.glance.appwidget.ProgressIndicatorDefaults INSTANCE;
+  }
+
+  public final class RadioButtonColors {
+  }
+
+  public final class RadioButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.RadioButtonColors colors();
+    method public androidx.glance.appwidget.RadioButtonColors colors(androidx.glance.unit.ColorProvider checkedColor, androidx.glance.unit.ColorProvider uncheckedColor);
+    method public androidx.glance.appwidget.RadioButtonColors colors(long checkedColor, long uncheckedColor);
+    field public static final androidx.glance.appwidget.RadioButtonDefaults INSTANCE;
+  }
+
+  public final class RadioButtonKt {
+    method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, androidx.glance.action.Action? onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void RadioButton(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.RadioButtonColors colors, optional int maxLines, optional String? key);
+    method public static androidx.glance.GlanceModifier selectableGroup(androidx.glance.GlanceModifier);
+  }
+
+  @SuppressCompatibility @androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi public final class RemoteViewsCompositionResult {
+    ctor public RemoteViewsCompositionResult(android.widget.RemoteViews remoteViews);
+    method public android.widget.RemoteViews getRemoteViews();
+    property public final android.widget.RemoteViews remoteViews;
+  }
+
+  public sealed interface SizeMode {
+  }
+
+  public static final class SizeMode.Exact implements androidx.glance.appwidget.SizeMode {
+    field public static final androidx.glance.appwidget.SizeMode.Exact INSTANCE;
+  }
+
+  public static final class SizeMode.Responsive implements androidx.glance.appwidget.SizeMode {
+    ctor public SizeMode.Responsive(java.util.Set<androidx.compose.ui.unit.DpSize> sizes);
+    method public java.util.Set<androidx.compose.ui.unit.DpSize> getSizes();
+    property public final java.util.Set<androidx.compose.ui.unit.DpSize> sizes;
+  }
+
+  public static final class SizeMode.Single implements androidx.glance.appwidget.SizeMode {
+    field public static final androidx.glance.appwidget.SizeMode.Single INSTANCE;
+  }
+
+  public abstract sealed class SwitchColors {
+  }
+
+  public final class SwitchDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.SwitchColors colors();
+    method @androidx.compose.runtime.Composable public androidx.glance.appwidget.SwitchColors colors(androidx.glance.unit.ColorProvider checkedThumbColor, androidx.glance.unit.ColorProvider uncheckedThumbColor, androidx.glance.unit.ColorProvider checkedTrackColor, androidx.glance.unit.ColorProvider uncheckedTrackColor);
+    field public static final androidx.glance.appwidget.SwitchDefaults INSTANCE;
+  }
+
+  public final class SwitchKt {
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, androidx.glance.action.Action? onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Switch(boolean checked, kotlin.jvm.functions.Function0<kotlin.Unit> onCheckedChange, optional androidx.glance.GlanceModifier modifier, optional String text, optional androidx.glance.text.TextStyle? style, optional androidx.glance.appwidget.SwitchColors colors, optional int maxLines, optional String? key);
+  }
+
+}
+
+package androidx.glance.appwidget.action {
+
+  public interface ActionCallback {
+    method public suspend Object? onAction(android.content.Context context, androidx.glance.GlanceId glanceId, androidx.glance.action.ActionParameters parameters, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
+  public final class RunCallbackActionKt {
+    method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(optional androidx.glance.action.ActionParameters parameters);
+    method public static <T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(Class<T> callbackClass, optional androidx.glance.action.ActionParameters parameters);
+  }
+
+  public final class SendBroadcastActionKt {
+    method public static inline <reified T extends android.content.BroadcastReceiver> androidx.glance.action.Action actionSendBroadcast();
+    method public static androidx.glance.action.Action actionSendBroadcast(android.content.ComponentName componentName);
+    method public static androidx.glance.action.Action actionSendBroadcast(android.content.Intent intent);
+    method public static <T extends android.content.BroadcastReceiver> androidx.glance.action.Action actionSendBroadcast(Class<T> receiver);
+    method public static androidx.glance.action.Action actionSendBroadcast(String action, optional android.content.ComponentName? componentName);
+  }
+
+  public final class StartActivityIntentActionKt {
+    method public static androidx.glance.action.Action actionStartActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static androidx.glance.action.Action actionStartActivity(android.content.Intent intent, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+  }
+
+  public final class StartServiceActionKt {
+    method public static androidx.glance.action.Action actionStartService(android.content.ComponentName componentName, optional boolean isForegroundService);
+    method public static androidx.glance.action.Action actionStartService(android.content.Intent intent, optional boolean isForegroundService);
+    method public static inline <reified T extends android.app.Service> androidx.glance.action.Action actionStartService(optional boolean isForegroundService);
+    method public static <T extends android.app.Service> androidx.glance.action.Action actionStartService(Class<T> service, optional boolean isForegroundService);
+  }
+
+  public final class ToggleableKt {
+    method public static androidx.glance.action.ActionParameters.Key<java.lang.Boolean> getToggleableStateKey();
+    property public static final androidx.glance.action.ActionParameters.Key<java.lang.Boolean> ToggleableStateKey;
+  }
+
+}
+
+package androidx.glance.appwidget.components {
+
+  public final class ButtonsKt {
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+  }
+
+  public final class ScaffoldKt {
+    method @androidx.compose.runtime.Composable public static void Scaffold(kotlin.jvm.functions.Function0<kotlin.Unit> titleBar, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.unit.ColorProvider backgroundColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class TitleBarKt {
+    method @androidx.compose.runtime.Composable public static void TitleBar(androidx.glance.ImageProvider startIcon, String title, optional androidx.glance.unit.ColorProvider? iconColor, optional androidx.glance.unit.ColorProvider textColor, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.FontFamily? fontFamily, optional kotlin.jvm.functions.Function1<? super androidx.glance.layout.RowScope,kotlin.Unit> actions);
+  }
+
+}
+
+package androidx.glance.appwidget.lazy {
+
+  public abstract sealed class GridCells {
+  }
+
+  @RequiresApi(31) public static final class GridCells.Adaptive extends androidx.glance.appwidget.lazy.GridCells {
+    ctor public GridCells.Adaptive(float minSize);
+    method public float getMinSize();
+    property public final float minSize;
+  }
+
+  public static final class GridCells.Fixed extends androidx.glance.appwidget.lazy.GridCells {
+    ctor public GridCells.Fixed(int count);
+    method public int getCount();
+    property public final int count;
+  }
+
+  @androidx.glance.appwidget.lazy.LazyScopeMarker public interface LazyItemScope {
+  }
+
+  public final class LazyListKt {
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void LazyColumn(android.os.Bundle activityOptions, optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyListScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyColumn(optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyListScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyListScope, T[] items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyListScope, T[] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  @androidx.glance.appwidget.lazy.LazyScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyListScope {
+    method public void item(optional long itemId, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    field public static final androidx.glance.appwidget.lazy.LazyListScope.Companion Companion;
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public static final class LazyListScope.Companion {
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  @kotlin.DslMarker public @interface LazyScopeMarker {
+  }
+
+  public final class LazyVerticalGridKt {
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, android.os.Bundle activityOptions, optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void LazyVerticalGrid(androidx.glance.appwidget.lazy.GridCells gridCells, optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyVerticalGridScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyVerticalGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.glance.appwidget.lazy.LazyVerticalGridScope, T[] items, optional kotlin.jvm.functions.Function1<? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyVerticalGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.glance.appwidget.lazy.LazyVerticalGridScope, T[] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,java.lang.Long> itemId, kotlin.jvm.functions.Function3<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  @androidx.glance.appwidget.lazy.LazyScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyVerticalGridScope {
+    method public void item(optional long itemId, kotlin.jvm.functions.Function1<? super androidx.glance.appwidget.lazy.LazyItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Long> itemId, kotlin.jvm.functions.Function2<? super androidx.glance.appwidget.lazy.LazyItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    field public static final androidx.glance.appwidget.lazy.LazyVerticalGridScope.Companion Companion;
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+  public static final class LazyVerticalGridScope.Companion {
+    field public static final long UnspecifiedItemId = -9223372036854775808L; // 0x8000000000000000L
+  }
+
+}
+
+package androidx.glance.appwidget.state {
+
+  public final class GlanceAppWidgetStateKt {
+    method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T>);
+    method public static suspend <T> Object? getAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T>);
+    method public static suspend Object? updateAppWidgetState(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super androidx.datastore.preferences.core.MutablePreferences,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> updateState, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T>);
+  }
+
+}
+
diff --git a/glance/glance-material/api/1.1.0-beta01.txt b/glance/glance-material/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..3df106d
--- /dev/null
+++ b/glance/glance-material/api/1.1.0-beta01.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.glance.material {
+
+  public final class MaterialThemesKt {
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material.Colors colors);
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material.Colors light, androidx.compose.material.Colors dark);
+  }
+
+}
+
diff --git a/glance/glance-material/api/res-1.1.0-beta01.txt b/glance/glance-material/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/glance/glance-material/api/res-1.1.0-beta01.txt
diff --git a/glance/glance-material/api/restricted_1.1.0-beta01.txt b/glance/glance-material/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..3df106d
--- /dev/null
+++ b/glance/glance-material/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.glance.material {
+
+  public final class MaterialThemesKt {
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material.Colors colors);
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material.Colors light, androidx.compose.material.Colors dark);
+  }
+
+}
+
diff --git a/glance/glance-material3/api/1.1.0-beta01.txt b/glance/glance-material3/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..36479f9
--- /dev/null
+++ b/glance/glance-material3/api/1.1.0-beta01.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.glance.material3 {
+
+  public final class Material3ThemesKt {
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material3.ColorScheme scheme);
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material3.ColorScheme light, androidx.compose.material3.ColorScheme dark);
+  }
+
+}
+
diff --git a/glance/glance-material3/api/res-1.1.0-beta01.txt b/glance/glance-material3/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/glance/glance-material3/api/res-1.1.0-beta01.txt
diff --git a/glance/glance-material3/api/restricted_1.1.0-beta01.txt b/glance/glance-material3/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..36479f9
--- /dev/null
+++ b/glance/glance-material3/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.glance.material3 {
+
+  public final class Material3ThemesKt {
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material3.ColorScheme scheme);
+    method public static androidx.glance.color.ColorProviders ColorProviders(androidx.compose.material3.ColorScheme light, androidx.compose.material3.ColorScheme dark);
+  }
+
+}
+
diff --git a/glance/glance-testing/api/1.1.0-beta01.txt b/glance/glance-testing/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..cb3b9ca
--- /dev/null
+++ b/glance/glance-testing/api/1.1.0-beta01.txt
@@ -0,0 +1,79 @@
+// Signature format: 4.0
+package androidx.glance.testing {
+
+  public abstract class GlanceNode<T> {
+    method public abstract java.util.List<androidx.glance.testing.GlanceNode<T>> children();
+    method public final T getValue();
+    method public abstract String toDebugString();
+    property public final T value;
+  }
+
+  public final class GlanceNodeAssertion<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> assert(androidx.glance.testing.GlanceNodeMatcher<R> matcher, optional kotlin.jvm.functions.Function0<java.lang.String>? messagePrefixOnError);
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertDoesNotExist();
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertExists();
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onChildren();
+  }
+
+  public final class GlanceNodeAssertionCollection<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAll(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAny(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertCountEquals(int expectedCount);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> filter(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public operator androidx.glance.testing.GlanceNodeAssertion<R,T> get(int index);
+  }
+
+  public interface GlanceNodeAssertionsProvider<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onAllNodes(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> onNode(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+  }
+
+  public final class GlanceNodeMatcher<R> {
+    ctor public GlanceNodeMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.glance.testing.GlanceNode<R>,java.lang.Boolean> matcher);
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> and(androidx.glance.testing.GlanceNodeMatcher<R> other);
+    method public boolean matches(androidx.glance.testing.GlanceNode<R> node);
+    method public boolean matchesAny(Iterable<? extends androidx.glance.testing.GlanceNode<R>> nodes);
+    method public operator androidx.glance.testing.GlanceNodeMatcher<R> not();
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> or(androidx.glance.testing.GlanceNodeMatcher<R> other);
+  }
+
+}
+
+package androidx.glance.testing.unit {
+
+  public final class GlanceMappedNode extends androidx.glance.testing.GlanceNode<androidx.glance.testing.unit.MappedNode> {
+    ctor public GlanceMappedNode(androidx.glance.testing.unit.MappedNode mappedNode);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public java.util.List<androidx.glance.testing.GlanceNode<androidx.glance.testing.unit.MappedNode>> children();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public String toDebugString();
+  }
+
+  public final class MappedNode {
+  }
+
+  public final class UnitTestAssertionExtensionsKt {
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasNoClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTestTag(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String testTag);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+  }
+
+  public final class UnitTestFiltersKt {
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasAnyDescendant(androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> matcher);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasClickAction();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasNoClickAction();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTestTag(String testTag);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text, optional boolean ignoreCase);
+  }
+
+}
+
diff --git a/glance/glance-testing/api/res-1.1.0-beta01.txt b/glance/glance-testing/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/glance/glance-testing/api/res-1.1.0-beta01.txt
diff --git a/glance/glance-testing/api/restricted_1.1.0-beta01.txt b/glance/glance-testing/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..1412248
--- /dev/null
+++ b/glance/glance-testing/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,81 @@
+// Signature format: 4.0
+package androidx.glance.testing {
+
+  public abstract class GlanceNode<T> {
+    method public abstract java.util.List<androidx.glance.testing.GlanceNode<T>> children();
+    method public final T getValue();
+    method public abstract String toDebugString();
+    property public final T value;
+  }
+
+  public final class GlanceNodeAssertion<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> assert(androidx.glance.testing.GlanceNodeMatcher<R> matcher, optional kotlin.jvm.functions.Function0<java.lang.String>? messagePrefixOnError);
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertDoesNotExist();
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertExists();
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onChildren();
+  }
+
+  public final class GlanceNodeAssertionCollection<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAll(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAny(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertCountEquals(int expectedCount);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> filter(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public operator androidx.glance.testing.GlanceNodeAssertion<R,T> get(int index);
+  }
+
+  public interface GlanceNodeAssertionsProvider<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onAllNodes(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertion<R,T> onNode(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+  }
+
+  public final class GlanceNodeMatcher<R> {
+    ctor public GlanceNodeMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.glance.testing.GlanceNode<R>,java.lang.Boolean> matcher);
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> and(androidx.glance.testing.GlanceNodeMatcher<R> other);
+    method public boolean matches(androidx.glance.testing.GlanceNode<R> node);
+    method public boolean matchesAny(Iterable<? extends androidx.glance.testing.GlanceNode<R>> nodes);
+    method public operator androidx.glance.testing.GlanceNodeMatcher<R> not();
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> or(androidx.glance.testing.GlanceNodeMatcher<R> other);
+  }
+
+}
+
+package androidx.glance.testing.unit {
+
+  public final class GlanceMappedNode extends androidx.glance.testing.GlanceNode<androidx.glance.testing.unit.MappedNode> {
+    ctor public GlanceMappedNode(androidx.glance.testing.unit.MappedNode mappedNode);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public java.util.List<androidx.glance.testing.GlanceNode<androidx.glance.testing.unit.MappedNode>> children();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public String toDebugString();
+  }
+
+  public final class MappedNode {
+  }
+
+  public final class UnitTestAssertionExtensionsKt {
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasNoClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method @kotlin.PublishedApi internal static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTestTag(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String testTag);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+  }
+
+  public final class UnitTestFiltersKt {
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasAnyDescendant(androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> matcher);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasClickAction();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasNoClickAction();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method @kotlin.PublishedApi internal static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTestTag(String testTag);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text, optional boolean ignoreCase);
+  }
+
+}
+
diff --git a/glance/glance/api/1.1.0-beta01.txt b/glance/glance/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..d7d9a71
--- /dev/null
+++ b/glance/glance/api/1.1.0-beta01.txt
@@ -0,0 +1,579 @@
+// Signature format: 4.0
+package androidx.glance {
+
+  public final class BackgroundKt {
+    method @Deprecated public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale, optional androidx.glance.ColorFilter? colorFilter);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.unit.ColorProvider colorProvider);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, @ColorRes int color);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, long color);
+  }
+
+  public final class ButtonColors {
+    method public androidx.glance.unit.ColorProvider getBackgroundColor();
+    method public androidx.glance.unit.ColorProvider getContentColor();
+    property public final androidx.glance.unit.ColorProvider backgroundColor;
+    property public final androidx.glance.unit.ColorProvider contentColor;
+  }
+
+  public final class ButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.ButtonColors buttonColors(optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    field public static final androidx.glance.ButtonDefaults INSTANCE;
+  }
+
+  public final class ButtonKt {
+    method @androidx.compose.runtime.Composable public static void Button(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+  }
+
+  public final class ColorFilter {
+    field public static final androidx.glance.ColorFilter.Companion Companion;
+  }
+
+  public static final class ColorFilter.Companion {
+    method public androidx.glance.ColorFilter tint(androidx.glance.unit.ColorProvider colorProvider);
+  }
+
+  public final class CombinedGlanceModifier implements androidx.glance.GlanceModifier {
+    ctor public CombinedGlanceModifier(androidx.glance.GlanceModifier outer, androidx.glance.GlanceModifier inner);
+    method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+  }
+
+  public final class CompositionLocalsKt {
+    method @androidx.compose.runtime.Composable public static inline <reified T> T currentState();
+    method @androidx.compose.runtime.Composable public static inline <reified T> T? currentState(androidx.datastore.preferences.core.Preferences.Key<T> key);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.GlanceId> getLocalGlanceId();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.DpSize> getLocalSize();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Object?> getLocalState();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> LocalContext;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.GlanceId> LocalGlanceId;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.DpSize> LocalSize;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Object?> LocalState;
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGlanceApi {
+  }
+
+  @androidx.compose.runtime.ComposableTargetMarker(description="Glance Composable") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FILE, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.TYPE, kotlin.annotation.AnnotationTarget.TYPE_PARAMETER}) public @interface GlanceComposable {
+  }
+
+  public interface GlanceId {
+  }
+
+  @androidx.compose.runtime.Stable @kotlin.jvm.JvmDefaultWithCompatibility public interface GlanceModifier {
+    method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+    method public default infix androidx.glance.GlanceModifier then(androidx.glance.GlanceModifier other);
+    field public static final androidx.glance.GlanceModifier.Companion Companion;
+  }
+
+  public static final class GlanceModifier.Companion implements androidx.glance.GlanceModifier {
+    method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public static interface GlanceModifier.Element extends androidx.glance.GlanceModifier {
+    method public default boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public default boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public default <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public default <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+  }
+
+  public final class GlanceTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public androidx.glance.color.ColorProviders getColors();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public final androidx.glance.color.ColorProviders colors;
+    field public static final androidx.glance.GlanceTheme INSTANCE;
+  }
+
+  public final class GlanceThemeKt {
+    method @androidx.compose.runtime.Composable public static void GlanceTheme(optional androidx.glance.color.ColorProviders colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class ImageKt {
+    method @androidx.compose.runtime.Composable public static void Image(androidx.glance.ImageProvider provider, String? contentDescription, optional androidx.glance.GlanceModifier modifier, optional int contentScale, optional androidx.glance.ColorFilter? colorFilter);
+    method public static androidx.glance.ImageProvider ImageProvider(android.graphics.Bitmap bitmap);
+    method @RequiresApi(android.os.Build.VERSION_CODES.M) public static androidx.glance.ImageProvider ImageProvider(android.graphics.drawable.Icon icon);
+    method public static androidx.glance.ImageProvider ImageProvider(@DrawableRes int resId);
+  }
+
+  public interface ImageProvider {
+  }
+
+  public enum Visibility {
+    enum_constant public static final androidx.glance.Visibility Gone;
+    enum_constant public static final androidx.glance.Visibility Invisible;
+    enum_constant public static final androidx.glance.Visibility Visible;
+  }
+
+  public final class VisibilityKt {
+    method public static androidx.glance.GlanceModifier visibility(androidx.glance.GlanceModifier, androidx.glance.Visibility visibility);
+  }
+
+}
+
+package androidx.glance.action {
+
+  public interface Action {
+  }
+
+  public final class ActionKt {
+    method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
+    method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick, optional @DrawableRes int rippleOverride);
+    method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    field @DrawableRes public static final int NoRippleOverride = 0; // 0x0
+  }
+
+  public abstract class ActionParameters {
+    method public abstract java.util.Map<androidx.glance.action.ActionParameters.Key<?>,java.lang.Object> asMap();
+    method public abstract operator <T> boolean contains(androidx.glance.action.ActionParameters.Key<T> key);
+    method public abstract operator <T> T? get(androidx.glance.action.ActionParameters.Key<T> key);
+    method public abstract <T> T getOrDefault(androidx.glance.action.ActionParameters.Key<T> key, T defaultValue);
+    method public abstract boolean isEmpty();
+  }
+
+  public static final class ActionParameters.Key<T> {
+    ctor public ActionParameters.Key(String name);
+    method public String getName();
+    method public infix androidx.glance.action.ActionParameters.Pair<T> to(T value);
+    property public final String name;
+  }
+
+  public static final class ActionParameters.Pair<T> {
+  }
+
+  public final class ActionParametersKt {
+    method public static androidx.glance.action.ActionParameters actionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
+    method public static androidx.glance.action.MutableActionParameters mutableActionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
+    method public static androidx.glance.action.MutableActionParameters toMutableParameters(androidx.glance.action.ActionParameters);
+    method public static androidx.glance.action.ActionParameters toParameters(androidx.glance.action.ActionParameters);
+    method public static <T> androidx.glance.action.ActionParameters.Key<T> toParametersKey(androidx.datastore.preferences.core.Preferences.Key<T>);
+  }
+
+  public final class LambdaActionKt {
+    method @androidx.compose.runtime.Composable public static androidx.glance.action.Action action(optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  }
+
+  public final class MutableActionParameters extends androidx.glance.action.ActionParameters {
+    method public java.util.Map<androidx.glance.action.ActionParameters.Key<?>,java.lang.Object> asMap();
+    method public void clear();
+    method public operator <T> boolean contains(androidx.glance.action.ActionParameters.Key<T> key);
+    method public operator <T> T? get(androidx.glance.action.ActionParameters.Key<T> key);
+    method public <T> T getOrDefault(androidx.glance.action.ActionParameters.Key<T> key, T defaultValue);
+    method public boolean isEmpty();
+    method public <T> T? remove(androidx.glance.action.ActionParameters.Key<T> key);
+    method public operator <T> T? set(androidx.glance.action.ActionParameters.Key<T> key, T? value);
+  }
+
+  public final class StartActivityActionKt {
+    method public static androidx.glance.action.Action actionStartActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static androidx.glance.action.Action actionStartActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static inline <reified T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static <T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+  }
+
+}
+
+package androidx.glance.color {
+
+  public abstract sealed class ColorProviders {
+    method public final androidx.glance.unit.ColorProvider getBackground();
+    method public final androidx.glance.unit.ColorProvider getError();
+    method public final androidx.glance.unit.ColorProvider getErrorContainer();
+    method public final androidx.glance.unit.ColorProvider getInverseOnSurface();
+    method public final androidx.glance.unit.ColorProvider getInversePrimary();
+    method public final androidx.glance.unit.ColorProvider getInverseSurface();
+    method public final androidx.glance.unit.ColorProvider getOnBackground();
+    method public final androidx.glance.unit.ColorProvider getOnError();
+    method public final androidx.glance.unit.ColorProvider getOnErrorContainer();
+    method public final androidx.glance.unit.ColorProvider getOnPrimary();
+    method public final androidx.glance.unit.ColorProvider getOnPrimaryContainer();
+    method public final androidx.glance.unit.ColorProvider getOnSecondary();
+    method public final androidx.glance.unit.ColorProvider getOnSecondaryContainer();
+    method public final androidx.glance.unit.ColorProvider getOnSurface();
+    method public final androidx.glance.unit.ColorProvider getOnSurfaceVariant();
+    method public final androidx.glance.unit.ColorProvider getOnTertiary();
+    method public final androidx.glance.unit.ColorProvider getOnTertiaryContainer();
+    method public final androidx.glance.unit.ColorProvider getOutline();
+    method public final androidx.glance.unit.ColorProvider getPrimary();
+    method public final androidx.glance.unit.ColorProvider getPrimaryContainer();
+    method public final androidx.glance.unit.ColorProvider getSecondary();
+    method public final androidx.glance.unit.ColorProvider getSecondaryContainer();
+    method public final androidx.glance.unit.ColorProvider getSurface();
+    method public final androidx.glance.unit.ColorProvider getSurfaceVariant();
+    method public final androidx.glance.unit.ColorProvider getTertiary();
+    method public final androidx.glance.unit.ColorProvider getTertiaryContainer();
+    method public final androidx.glance.unit.ColorProvider getWidgetBackground();
+    property public final androidx.glance.unit.ColorProvider background;
+    property public final androidx.glance.unit.ColorProvider error;
+    property public final androidx.glance.unit.ColorProvider errorContainer;
+    property public final androidx.glance.unit.ColorProvider inverseOnSurface;
+    property public final androidx.glance.unit.ColorProvider inversePrimary;
+    property public final androidx.glance.unit.ColorProvider inverseSurface;
+    property public final androidx.glance.unit.ColorProvider onBackground;
+    property public final androidx.glance.unit.ColorProvider onError;
+    property public final androidx.glance.unit.ColorProvider onErrorContainer;
+    property public final androidx.glance.unit.ColorProvider onPrimary;
+    property public final androidx.glance.unit.ColorProvider onPrimaryContainer;
+    property public final androidx.glance.unit.ColorProvider onSecondary;
+    property public final androidx.glance.unit.ColorProvider onSecondaryContainer;
+    property public final androidx.glance.unit.ColorProvider onSurface;
+    property public final androidx.glance.unit.ColorProvider onSurfaceVariant;
+    property public final androidx.glance.unit.ColorProvider onTertiary;
+    property public final androidx.glance.unit.ColorProvider onTertiaryContainer;
+    property public final androidx.glance.unit.ColorProvider outline;
+    property public final androidx.glance.unit.ColorProvider primary;
+    property public final androidx.glance.unit.ColorProvider primaryContainer;
+    property public final androidx.glance.unit.ColorProvider secondary;
+    property public final androidx.glance.unit.ColorProvider secondaryContainer;
+    property public final androidx.glance.unit.ColorProvider surface;
+    property public final androidx.glance.unit.ColorProvider surfaceVariant;
+    property public final androidx.glance.unit.ColorProvider tertiary;
+    property public final androidx.glance.unit.ColorProvider tertiaryContainer;
+    property public final androidx.glance.unit.ColorProvider widgetBackground;
+  }
+
+  public final class ColorProvidersKt {
+    method public static androidx.glance.color.ColorProviders colorProviders(androidx.glance.unit.ColorProvider primary, androidx.glance.unit.ColorProvider onPrimary, androidx.glance.unit.ColorProvider primaryContainer, androidx.glance.unit.ColorProvider onPrimaryContainer, androidx.glance.unit.ColorProvider secondary, androidx.glance.unit.ColorProvider onSecondary, androidx.glance.unit.ColorProvider secondaryContainer, androidx.glance.unit.ColorProvider onSecondaryContainer, androidx.glance.unit.ColorProvider tertiary, androidx.glance.unit.ColorProvider onTertiary, androidx.glance.unit.ColorProvider tertiaryContainer, androidx.glance.unit.ColorProvider onTertiaryContainer, androidx.glance.unit.ColorProvider error, androidx.glance.unit.ColorProvider errorContainer, androidx.glance.unit.ColorProvider onError, androidx.glance.unit.ColorProvider onErrorContainer, androidx.glance.unit.ColorProvider background, androidx.glance.unit.ColorProvider onBackground, androidx.glance.unit.ColorProvider surface, androidx.glance.unit.ColorProvider onSurface, androidx.glance.unit.ColorProvider surfaceVariant, androidx.glance.unit.ColorProvider onSurfaceVariant, androidx.glance.unit.ColorProvider outline, androidx.glance.unit.ColorProvider inverseOnSurface, androidx.glance.unit.ColorProvider inverseSurface, androidx.glance.unit.ColorProvider inversePrimary);
+    method public static androidx.glance.color.ColorProviders colorProviders(androidx.glance.unit.ColorProvider primary, androidx.glance.unit.ColorProvider onPrimary, androidx.glance.unit.ColorProvider primaryContainer, androidx.glance.unit.ColorProvider onPrimaryContainer, androidx.glance.unit.ColorProvider secondary, androidx.glance.unit.ColorProvider onSecondary, androidx.glance.unit.ColorProvider secondaryContainer, androidx.glance.unit.ColorProvider onSecondaryContainer, androidx.glance.unit.ColorProvider tertiary, androidx.glance.unit.ColorProvider onTertiary, androidx.glance.unit.ColorProvider tertiaryContainer, androidx.glance.unit.ColorProvider onTertiaryContainer, androidx.glance.unit.ColorProvider error, androidx.glance.unit.ColorProvider errorContainer, androidx.glance.unit.ColorProvider onError, androidx.glance.unit.ColorProvider onErrorContainer, androidx.glance.unit.ColorProvider background, androidx.glance.unit.ColorProvider onBackground, androidx.glance.unit.ColorProvider surface, androidx.glance.unit.ColorProvider onSurface, androidx.glance.unit.ColorProvider surfaceVariant, androidx.glance.unit.ColorProvider onSurfaceVariant, androidx.glance.unit.ColorProvider outline, androidx.glance.unit.ColorProvider inverseOnSurface, androidx.glance.unit.ColorProvider inverseSurface, androidx.glance.unit.ColorProvider inversePrimary, androidx.glance.unit.ColorProvider widgetBackground);
+  }
+
+  public final class DayNightColorProvidersKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(long day, long night);
+  }
+
+}
+
+package androidx.glance.layout {
+
+  public final class Alignment {
+    ctor public Alignment(int horizontal, int vertical);
+    method public int getHorizontal();
+    method public int getVertical();
+    property public final int horizontal;
+    property public final int vertical;
+    field public static final androidx.glance.layout.Alignment.Companion Companion;
+  }
+
+  public static final class Alignment.Companion {
+    method public int getBottom();
+    method public androidx.glance.layout.Alignment getBottomCenter();
+    method public androidx.glance.layout.Alignment getBottomEnd();
+    method public androidx.glance.layout.Alignment getBottomStart();
+    method public androidx.glance.layout.Alignment getCenter();
+    method public androidx.glance.layout.Alignment getCenterEnd();
+    method public int getCenterHorizontally();
+    method public androidx.glance.layout.Alignment getCenterStart();
+    method public int getCenterVertically();
+    method public int getEnd();
+    method public int getStart();
+    method public int getTop();
+    method public androidx.glance.layout.Alignment getTopCenter();
+    method public androidx.glance.layout.Alignment getTopEnd();
+    method public androidx.glance.layout.Alignment getTopStart();
+    property public final int Bottom;
+    property public final androidx.glance.layout.Alignment BottomCenter;
+    property public final androidx.glance.layout.Alignment BottomEnd;
+    property public final androidx.glance.layout.Alignment BottomStart;
+    property public final androidx.glance.layout.Alignment Center;
+    property public final androidx.glance.layout.Alignment CenterEnd;
+    property public final int CenterHorizontally;
+    property public final androidx.glance.layout.Alignment CenterStart;
+    property public final int CenterVertically;
+    property public final int End;
+    property public final int Start;
+    property public final int Top;
+    property public final androidx.glance.layout.Alignment TopCenter;
+    property public final androidx.glance.layout.Alignment TopEnd;
+    property public final androidx.glance.layout.Alignment TopStart;
+  }
+
+  @kotlin.jvm.JvmInline public static final value class Alignment.Horizontal {
+    field public static final androidx.glance.layout.Alignment.Horizontal.Companion Companion;
+  }
+
+  public static final class Alignment.Horizontal.Companion {
+    method public int getCenterHorizontally();
+    method public int getEnd();
+    method public int getStart();
+    property public final int CenterHorizontally;
+    property public final int End;
+    property public final int Start;
+  }
+
+  @kotlin.jvm.JvmInline public static final value class Alignment.Vertical {
+    field public static final androidx.glance.layout.Alignment.Vertical.Companion Companion;
+  }
+
+  public static final class Alignment.Vertical.Companion {
+    method public int getBottom();
+    method public int getCenterVertically();
+    method public int getTop();
+    property public final int Bottom;
+    property public final int CenterVertically;
+    property public final int Top;
+  }
+
+  public final class BoxKt {
+    method @androidx.compose.runtime.Composable public static void Box(optional androidx.glance.GlanceModifier modifier, optional androidx.glance.layout.Alignment contentAlignment, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class ColumnKt {
+    method @androidx.compose.runtime.Composable public static void Column(optional androidx.glance.GlanceModifier modifier, optional int verticalAlignment, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.layout.ColumnScope,kotlin.Unit> content);
+  }
+
+  public interface ColumnScope {
+    method public androidx.glance.GlanceModifier defaultWeight(androidx.glance.GlanceModifier);
+  }
+
+  @kotlin.jvm.JvmInline public final value class ContentScale {
+    ctor public ContentScale(int value);
+    field public static final androidx.glance.layout.ContentScale.Companion Companion;
+  }
+
+  public static final class ContentScale.Companion {
+    method public int getCrop();
+    method public int getFillBounds();
+    method public int getFit();
+    property public final int Crop;
+    property public final int FillBounds;
+    property public final int Fit;
+  }
+
+  public final class PaddingKt {
+    method public static androidx.glance.GlanceModifier absolutePadding(androidx.glance.GlanceModifier, optional float left, optional float top, optional float right, optional float bottom);
+    method public static androidx.glance.GlanceModifier absolutePadding(androidx.glance.GlanceModifier, optional @DimenRes int left, optional @DimenRes int top, optional @DimenRes int right, optional @DimenRes int bottom);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, float all);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional float horizontal, optional float vertical);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional float start, optional float top, optional float end, optional float bottom);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, @DimenRes int all);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional @DimenRes int horizontal, optional @DimenRes int vertical);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional @DimenRes int start, optional @DimenRes int top, optional @DimenRes int end, optional @DimenRes int bottom);
+  }
+
+  public final class RowKt {
+    method @androidx.compose.runtime.Composable public static void Row(optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, optional int verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.layout.RowScope,kotlin.Unit> content);
+  }
+
+  public interface RowScope {
+    method public androidx.glance.GlanceModifier defaultWeight(androidx.glance.GlanceModifier);
+  }
+
+  public final class SizeModifiersKt {
+    method public static androidx.glance.GlanceModifier fillMaxHeight(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier fillMaxSize(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier fillMaxWidth(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier height(androidx.glance.GlanceModifier, float height);
+    method public static androidx.glance.GlanceModifier height(androidx.glance.GlanceModifier, @DimenRes int height);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, float size);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, float width, float height);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, @DimenRes int size);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, @DimenRes int width, @DimenRes int height);
+    method public static androidx.glance.GlanceModifier width(androidx.glance.GlanceModifier, float width);
+    method public static androidx.glance.GlanceModifier width(androidx.glance.GlanceModifier, @DimenRes int width);
+    method public static androidx.glance.GlanceModifier wrapContentHeight(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier wrapContentSize(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier wrapContentWidth(androidx.glance.GlanceModifier);
+  }
+
+  public final class SpacerKt {
+    method @androidx.compose.runtime.Composable public static void Spacer(optional androidx.glance.GlanceModifier modifier);
+  }
+
+}
+
+package androidx.glance.semantics {
+
+  public final class SemanticsConfiguration implements androidx.glance.semantics.SemanticsPropertyReceiver {
+    ctor public SemanticsConfiguration();
+    method public operator <T> T get(androidx.glance.semantics.SemanticsPropertyKey<T> key);
+    method public <T> T? getOrElseNullable(androidx.glance.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T?> defaultValue);
+    method public <T> T? getOrNull(androidx.glance.semantics.SemanticsPropertyKey<T> key);
+    method public <T> void set(androidx.glance.semantics.SemanticsPropertyKey<T> key, T value);
+  }
+
+  public final class SemanticsModifierKt {
+    method public static androidx.glance.GlanceModifier semantics(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function1<? super androidx.glance.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
+  }
+
+  public final class SemanticsProperties {
+    method public androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> getContentDescription();
+    method public androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
+    property public final androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> ContentDescription;
+    property public final androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
+    field public static final androidx.glance.semantics.SemanticsProperties INSTANCE;
+  }
+
+  public final class SemanticsPropertiesKt {
+    method public static String getContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver);
+    method public static String getTestTag(androidx.glance.semantics.SemanticsPropertyReceiver);
+    method public static void setContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver, String);
+    method public static void setTestTag(androidx.glance.semantics.SemanticsPropertyReceiver, String);
+  }
+
+  public final class SemanticsPropertyKey<T> {
+    ctor public SemanticsPropertyKey(String name, optional kotlin.jvm.functions.Function2<? super T?,? super T,? extends T?> mergePolicy);
+    method public String getName();
+    method public T? merge(T? parentValue, T childValue);
+    property public final String name;
+  }
+
+  public interface SemanticsPropertyReceiver {
+    method public operator <T> void set(androidx.glance.semantics.SemanticsPropertyKey<T> key, T value);
+  }
+
+}
+
+package androidx.glance.state {
+
+  public interface GlanceStateDefinition<T> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>>);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
+  }
+
+  public final class PreferencesGlanceStateDefinition implements androidx.glance.state.GlanceStateDefinition<androidx.datastore.preferences.core.Preferences> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>>);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
+    field public static final androidx.glance.state.PreferencesGlanceStateDefinition INSTANCE;
+  }
+
+}
+
+package androidx.glance.text {
+
+  public final class FontFamily {
+    ctor public FontFamily(String family);
+    method public String getFamily();
+    property public final String family;
+    field public static final androidx.glance.text.FontFamily.Companion Companion;
+  }
+
+  public static final class FontFamily.Companion {
+    method public androidx.glance.text.FontFamily getCursive();
+    method public androidx.glance.text.FontFamily getMonospace();
+    method public androidx.glance.text.FontFamily getSansSerif();
+    method public androidx.glance.text.FontFamily getSerif();
+    property public final androidx.glance.text.FontFamily Cursive;
+    property public final androidx.glance.text.FontFamily Monospace;
+    property public final androidx.glance.text.FontFamily SansSerif;
+    property public final androidx.glance.text.FontFamily Serif;
+  }
+
+  @kotlin.jvm.JvmInline public final value class FontStyle {
+    field public static final androidx.glance.text.FontStyle.Companion Companion;
+  }
+
+  public static final class FontStyle.Companion {
+    method public int getItalic();
+    method public int getNormal();
+    method public java.util.List<androidx.glance.text.FontStyle> values();
+    property public final int Italic;
+    property public final int Normal;
+  }
+
+  @kotlin.jvm.JvmInline public final value class FontWeight {
+    method public int getValue();
+    property public final int value;
+    field public static final androidx.glance.text.FontWeight.Companion Companion;
+  }
+
+  public static final class FontWeight.Companion {
+    method public int getBold();
+    method public int getMedium();
+    method public int getNormal();
+    property public final int Bold;
+    property public final int Medium;
+    property public final int Normal;
+  }
+
+  @kotlin.jvm.JvmInline public final value class TextAlign {
+    field public static final androidx.glance.text.TextAlign.Companion Companion;
+  }
+
+  public static final class TextAlign.Companion {
+    method public int getCenter();
+    method public int getEnd();
+    method public int getLeft();
+    method public int getRight();
+    method public int getStart();
+    method public java.util.List<androidx.glance.text.TextAlign> values();
+    property public final int Center;
+    property public final int End;
+    property public final int Left;
+    property public final int Right;
+    property public final int Start;
+  }
+
+  @kotlin.jvm.JvmInline public final value class TextDecoration {
+    method @androidx.compose.runtime.Stable public operator boolean contains(int other);
+    method @androidx.compose.runtime.Stable public operator int plus(int decoration);
+    field public static final androidx.glance.text.TextDecoration.Companion Companion;
+  }
+
+  public static final class TextDecoration.Companion {
+    method public int combine(java.util.List<androidx.glance.text.TextDecoration> decorations);
+    method public int getLineThrough();
+    method public int getNone();
+    method public int getUnderline();
+    property public final int LineThrough;
+    property public final int None;
+    property public final int Underline;
+  }
+
+  public final class TextDefaults {
+    method public androidx.glance.unit.ColorProvider getDefaultTextColor();
+    method public androidx.glance.text.TextStyle getDefaultTextStyle();
+    property public final androidx.glance.unit.ColorProvider defaultTextColor;
+    property public final androidx.glance.text.TextStyle defaultTextStyle;
+    field public static final androidx.glance.text.TextDefaults INSTANCE;
+  }
+
+  public final class TextKt {
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle style, optional int maxLines);
+  }
+
+  @androidx.compose.runtime.Immutable public final class TextStyle {
+    ctor public TextStyle(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration, optional androidx.glance.text.FontFamily? fontFamily);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration, optional androidx.glance.text.FontFamily? fontFamily);
+    method public androidx.glance.unit.ColorProvider getColor();
+    method public androidx.glance.text.FontFamily? getFontFamily();
+    method public androidx.compose.ui.unit.TextUnit? getFontSize();
+    method public androidx.glance.text.FontStyle? getFontStyle();
+    method public androidx.glance.text.FontWeight? getFontWeight();
+    method public androidx.glance.text.TextAlign? getTextAlign();
+    method public androidx.glance.text.TextDecoration? getTextDecoration();
+    property public final androidx.glance.unit.ColorProvider color;
+    property public final androidx.glance.text.FontFamily? fontFamily;
+    property public final androidx.compose.ui.unit.TextUnit? fontSize;
+    property public final androidx.glance.text.FontStyle? fontStyle;
+    property public final androidx.glance.text.FontWeight? fontWeight;
+    property public final androidx.glance.text.TextAlign? textAlign;
+    property public final androidx.glance.text.TextDecoration? textDecoration;
+  }
+
+}
+
+package androidx.glance.unit {
+
+  public interface ColorProvider {
+    method public long getColor(android.content.Context context);
+  }
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(long color);
+  }
+
+}
+
diff --git a/glance/glance/api/res-1.1.0-beta01.txt b/glance/glance/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/glance/glance/api/res-1.1.0-beta01.txt
diff --git a/glance/glance/api/restricted_1.1.0-beta01.txt b/glance/glance/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..d7d9a71
--- /dev/null
+++ b/glance/glance/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,579 @@
+// Signature format: 4.0
+package androidx.glance {
+
+  public final class BackgroundKt {
+    method @Deprecated public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale, optional androidx.glance.ColorFilter? colorFilter);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.unit.ColorProvider colorProvider);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, @ColorRes int color);
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, long color);
+  }
+
+  public final class ButtonColors {
+    method public androidx.glance.unit.ColorProvider getBackgroundColor();
+    method public androidx.glance.unit.ColorProvider getContentColor();
+    property public final androidx.glance.unit.ColorProvider backgroundColor;
+    property public final androidx.glance.unit.ColorProvider contentColor;
+  }
+
+  public final class ButtonDefaults {
+    method @androidx.compose.runtime.Composable public androidx.glance.ButtonColors buttonColors(optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    field public static final androidx.glance.ButtonDefaults INSTANCE;
+  }
+
+  public final class ButtonKt {
+    method @androidx.compose.runtime.Composable public static void Button(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static void Button(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.text.TextStyle? style, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+  }
+
+  public final class ColorFilter {
+    field public static final androidx.glance.ColorFilter.Companion Companion;
+  }
+
+  public static final class ColorFilter.Companion {
+    method public androidx.glance.ColorFilter tint(androidx.glance.unit.ColorProvider colorProvider);
+  }
+
+  public final class CombinedGlanceModifier implements androidx.glance.GlanceModifier {
+    ctor public CombinedGlanceModifier(androidx.glance.GlanceModifier outer, androidx.glance.GlanceModifier inner);
+    method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+  }
+
+  public final class CompositionLocalsKt {
+    method @androidx.compose.runtime.Composable public static inline <reified T> T currentState();
+    method @androidx.compose.runtime.Composable public static inline <reified T> T? currentState(androidx.datastore.preferences.core.Preferences.Key<T> key);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.GlanceId> getLocalGlanceId();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.DpSize> getLocalSize();
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Object?> getLocalState();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> LocalContext;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.glance.GlanceId> LocalGlanceId;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.DpSize> LocalSize;
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Object?> LocalState;
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGlanceApi {
+  }
+
+  @androidx.compose.runtime.ComposableTargetMarker(description="Glance Composable") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.FILE, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.TYPE, kotlin.annotation.AnnotationTarget.TYPE_PARAMETER}) public @interface GlanceComposable {
+  }
+
+  public interface GlanceId {
+  }
+
+  @androidx.compose.runtime.Stable @kotlin.jvm.JvmDefaultWithCompatibility public interface GlanceModifier {
+    method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+    method public default infix androidx.glance.GlanceModifier then(androidx.glance.GlanceModifier other);
+    field public static final androidx.glance.GlanceModifier.Companion Companion;
+  }
+
+  public static final class GlanceModifier.Companion implements androidx.glance.GlanceModifier {
+    method public boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public static interface GlanceModifier.Element extends androidx.glance.GlanceModifier {
+    method public default boolean all(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public default boolean any(kotlin.jvm.functions.Function1<? super androidx.glance.GlanceModifier.Element,java.lang.Boolean> predicate);
+    method public default <R> R foldIn(R initial, kotlin.jvm.functions.Function2<? super R,? super androidx.glance.GlanceModifier.Element,? extends R> operation);
+    method public default <R> R foldOut(R initial, kotlin.jvm.functions.Function2<? super androidx.glance.GlanceModifier.Element,? super R,? extends R> operation);
+  }
+
+  public final class GlanceTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public androidx.glance.color.ColorProviders getColors();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.glance.GlanceComposable public final androidx.glance.color.ColorProviders colors;
+    field public static final androidx.glance.GlanceTheme INSTANCE;
+  }
+
+  public final class GlanceThemeKt {
+    method @androidx.compose.runtime.Composable public static void GlanceTheme(optional androidx.glance.color.ColorProviders colors, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class ImageKt {
+    method @androidx.compose.runtime.Composable public static void Image(androidx.glance.ImageProvider provider, String? contentDescription, optional androidx.glance.GlanceModifier modifier, optional int contentScale, optional androidx.glance.ColorFilter? colorFilter);
+    method public static androidx.glance.ImageProvider ImageProvider(android.graphics.Bitmap bitmap);
+    method @RequiresApi(android.os.Build.VERSION_CODES.M) public static androidx.glance.ImageProvider ImageProvider(android.graphics.drawable.Icon icon);
+    method public static androidx.glance.ImageProvider ImageProvider(@DrawableRes int resId);
+  }
+
+  public interface ImageProvider {
+  }
+
+  public enum Visibility {
+    enum_constant public static final androidx.glance.Visibility Gone;
+    enum_constant public static final androidx.glance.Visibility Invisible;
+    enum_constant public static final androidx.glance.Visibility Visible;
+  }
+
+  public final class VisibilityKt {
+    method public static androidx.glance.GlanceModifier visibility(androidx.glance.GlanceModifier, androidx.glance.Visibility visibility);
+  }
+
+}
+
+package androidx.glance.action {
+
+  public interface Action {
+  }
+
+  public final class ActionKt {
+    method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
+    method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick, optional @DrawableRes int rippleOverride);
+    method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    field @DrawableRes public static final int NoRippleOverride = 0; // 0x0
+  }
+
+  public abstract class ActionParameters {
+    method public abstract java.util.Map<androidx.glance.action.ActionParameters.Key<?>,java.lang.Object> asMap();
+    method public abstract operator <T> boolean contains(androidx.glance.action.ActionParameters.Key<T> key);
+    method public abstract operator <T> T? get(androidx.glance.action.ActionParameters.Key<T> key);
+    method public abstract <T> T getOrDefault(androidx.glance.action.ActionParameters.Key<T> key, T defaultValue);
+    method public abstract boolean isEmpty();
+  }
+
+  public static final class ActionParameters.Key<T> {
+    ctor public ActionParameters.Key(String name);
+    method public String getName();
+    method public infix androidx.glance.action.ActionParameters.Pair<T> to(T value);
+    property public final String name;
+  }
+
+  public static final class ActionParameters.Pair<T> {
+  }
+
+  public final class ActionParametersKt {
+    method public static androidx.glance.action.ActionParameters actionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
+    method public static androidx.glance.action.MutableActionParameters mutableActionParametersOf(androidx.glance.action.ActionParameters.Pair<?>... pairs);
+    method public static androidx.glance.action.MutableActionParameters toMutableParameters(androidx.glance.action.ActionParameters);
+    method public static androidx.glance.action.ActionParameters toParameters(androidx.glance.action.ActionParameters);
+    method public static <T> androidx.glance.action.ActionParameters.Key<T> toParametersKey(androidx.datastore.preferences.core.Preferences.Key<T>);
+  }
+
+  public final class LambdaActionKt {
+    method @androidx.compose.runtime.Composable public static androidx.glance.action.Action action(optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+  }
+
+  public final class MutableActionParameters extends androidx.glance.action.ActionParameters {
+    method public java.util.Map<androidx.glance.action.ActionParameters.Key<?>,java.lang.Object> asMap();
+    method public void clear();
+    method public operator <T> boolean contains(androidx.glance.action.ActionParameters.Key<T> key);
+    method public operator <T> T? get(androidx.glance.action.ActionParameters.Key<T> key);
+    method public <T> T getOrDefault(androidx.glance.action.ActionParameters.Key<T> key, T defaultValue);
+    method public boolean isEmpty();
+    method public <T> T? remove(androidx.glance.action.ActionParameters.Key<T> key);
+    method public operator <T> T? set(androidx.glance.action.ActionParameters.Key<T> key, T? value);
+  }
+
+  public final class StartActivityActionKt {
+    method public static androidx.glance.action.Action actionStartActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static androidx.glance.action.Action actionStartActivity(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static inline <reified T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static inline <reified T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+    method public static <T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static <T extends android.app.Activity> androidx.glance.action.Action actionStartActivity(Class<T> activity, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
+  }
+
+}
+
+package androidx.glance.color {
+
+  public abstract sealed class ColorProviders {
+    method public final androidx.glance.unit.ColorProvider getBackground();
+    method public final androidx.glance.unit.ColorProvider getError();
+    method public final androidx.glance.unit.ColorProvider getErrorContainer();
+    method public final androidx.glance.unit.ColorProvider getInverseOnSurface();
+    method public final androidx.glance.unit.ColorProvider getInversePrimary();
+    method public final androidx.glance.unit.ColorProvider getInverseSurface();
+    method public final androidx.glance.unit.ColorProvider getOnBackground();
+    method public final androidx.glance.unit.ColorProvider getOnError();
+    method public final androidx.glance.unit.ColorProvider getOnErrorContainer();
+    method public final androidx.glance.unit.ColorProvider getOnPrimary();
+    method public final androidx.glance.unit.ColorProvider getOnPrimaryContainer();
+    method public final androidx.glance.unit.ColorProvider getOnSecondary();
+    method public final androidx.glance.unit.ColorProvider getOnSecondaryContainer();
+    method public final androidx.glance.unit.ColorProvider getOnSurface();
+    method public final androidx.glance.unit.ColorProvider getOnSurfaceVariant();
+    method public final androidx.glance.unit.ColorProvider getOnTertiary();
+    method public final androidx.glance.unit.ColorProvider getOnTertiaryContainer();
+    method public final androidx.glance.unit.ColorProvider getOutline();
+    method public final androidx.glance.unit.ColorProvider getPrimary();
+    method public final androidx.glance.unit.ColorProvider getPrimaryContainer();
+    method public final androidx.glance.unit.ColorProvider getSecondary();
+    method public final androidx.glance.unit.ColorProvider getSecondaryContainer();
+    method public final androidx.glance.unit.ColorProvider getSurface();
+    method public final androidx.glance.unit.ColorProvider getSurfaceVariant();
+    method public final androidx.glance.unit.ColorProvider getTertiary();
+    method public final androidx.glance.unit.ColorProvider getTertiaryContainer();
+    method public final androidx.glance.unit.ColorProvider getWidgetBackground();
+    property public final androidx.glance.unit.ColorProvider background;
+    property public final androidx.glance.unit.ColorProvider error;
+    property public final androidx.glance.unit.ColorProvider errorContainer;
+    property public final androidx.glance.unit.ColorProvider inverseOnSurface;
+    property public final androidx.glance.unit.ColorProvider inversePrimary;
+    property public final androidx.glance.unit.ColorProvider inverseSurface;
+    property public final androidx.glance.unit.ColorProvider onBackground;
+    property public final androidx.glance.unit.ColorProvider onError;
+    property public final androidx.glance.unit.ColorProvider onErrorContainer;
+    property public final androidx.glance.unit.ColorProvider onPrimary;
+    property public final androidx.glance.unit.ColorProvider onPrimaryContainer;
+    property public final androidx.glance.unit.ColorProvider onSecondary;
+    property public final androidx.glance.unit.ColorProvider onSecondaryContainer;
+    property public final androidx.glance.unit.ColorProvider onSurface;
+    property public final androidx.glance.unit.ColorProvider onSurfaceVariant;
+    property public final androidx.glance.unit.ColorProvider onTertiary;
+    property public final androidx.glance.unit.ColorProvider onTertiaryContainer;
+    property public final androidx.glance.unit.ColorProvider outline;
+    property public final androidx.glance.unit.ColorProvider primary;
+    property public final androidx.glance.unit.ColorProvider primaryContainer;
+    property public final androidx.glance.unit.ColorProvider secondary;
+    property public final androidx.glance.unit.ColorProvider secondaryContainer;
+    property public final androidx.glance.unit.ColorProvider surface;
+    property public final androidx.glance.unit.ColorProvider surfaceVariant;
+    property public final androidx.glance.unit.ColorProvider tertiary;
+    property public final androidx.glance.unit.ColorProvider tertiaryContainer;
+    property public final androidx.glance.unit.ColorProvider widgetBackground;
+  }
+
+  public final class ColorProvidersKt {
+    method public static androidx.glance.color.ColorProviders colorProviders(androidx.glance.unit.ColorProvider primary, androidx.glance.unit.ColorProvider onPrimary, androidx.glance.unit.ColorProvider primaryContainer, androidx.glance.unit.ColorProvider onPrimaryContainer, androidx.glance.unit.ColorProvider secondary, androidx.glance.unit.ColorProvider onSecondary, androidx.glance.unit.ColorProvider secondaryContainer, androidx.glance.unit.ColorProvider onSecondaryContainer, androidx.glance.unit.ColorProvider tertiary, androidx.glance.unit.ColorProvider onTertiary, androidx.glance.unit.ColorProvider tertiaryContainer, androidx.glance.unit.ColorProvider onTertiaryContainer, androidx.glance.unit.ColorProvider error, androidx.glance.unit.ColorProvider errorContainer, androidx.glance.unit.ColorProvider onError, androidx.glance.unit.ColorProvider onErrorContainer, androidx.glance.unit.ColorProvider background, androidx.glance.unit.ColorProvider onBackground, androidx.glance.unit.ColorProvider surface, androidx.glance.unit.ColorProvider onSurface, androidx.glance.unit.ColorProvider surfaceVariant, androidx.glance.unit.ColorProvider onSurfaceVariant, androidx.glance.unit.ColorProvider outline, androidx.glance.unit.ColorProvider inverseOnSurface, androidx.glance.unit.ColorProvider inverseSurface, androidx.glance.unit.ColorProvider inversePrimary);
+    method public static androidx.glance.color.ColorProviders colorProviders(androidx.glance.unit.ColorProvider primary, androidx.glance.unit.ColorProvider onPrimary, androidx.glance.unit.ColorProvider primaryContainer, androidx.glance.unit.ColorProvider onPrimaryContainer, androidx.glance.unit.ColorProvider secondary, androidx.glance.unit.ColorProvider onSecondary, androidx.glance.unit.ColorProvider secondaryContainer, androidx.glance.unit.ColorProvider onSecondaryContainer, androidx.glance.unit.ColorProvider tertiary, androidx.glance.unit.ColorProvider onTertiary, androidx.glance.unit.ColorProvider tertiaryContainer, androidx.glance.unit.ColorProvider onTertiaryContainer, androidx.glance.unit.ColorProvider error, androidx.glance.unit.ColorProvider errorContainer, androidx.glance.unit.ColorProvider onError, androidx.glance.unit.ColorProvider onErrorContainer, androidx.glance.unit.ColorProvider background, androidx.glance.unit.ColorProvider onBackground, androidx.glance.unit.ColorProvider surface, androidx.glance.unit.ColorProvider onSurface, androidx.glance.unit.ColorProvider surfaceVariant, androidx.glance.unit.ColorProvider onSurfaceVariant, androidx.glance.unit.ColorProvider outline, androidx.glance.unit.ColorProvider inverseOnSurface, androidx.glance.unit.ColorProvider inverseSurface, androidx.glance.unit.ColorProvider inversePrimary, androidx.glance.unit.ColorProvider widgetBackground);
+  }
+
+  public final class DayNightColorProvidersKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(long day, long night);
+  }
+
+}
+
+package androidx.glance.layout {
+
+  public final class Alignment {
+    ctor public Alignment(int horizontal, int vertical);
+    method public int getHorizontal();
+    method public int getVertical();
+    property public final int horizontal;
+    property public final int vertical;
+    field public static final androidx.glance.layout.Alignment.Companion Companion;
+  }
+
+  public static final class Alignment.Companion {
+    method public int getBottom();
+    method public androidx.glance.layout.Alignment getBottomCenter();
+    method public androidx.glance.layout.Alignment getBottomEnd();
+    method public androidx.glance.layout.Alignment getBottomStart();
+    method public androidx.glance.layout.Alignment getCenter();
+    method public androidx.glance.layout.Alignment getCenterEnd();
+    method public int getCenterHorizontally();
+    method public androidx.glance.layout.Alignment getCenterStart();
+    method public int getCenterVertically();
+    method public int getEnd();
+    method public int getStart();
+    method public int getTop();
+    method public androidx.glance.layout.Alignment getTopCenter();
+    method public androidx.glance.layout.Alignment getTopEnd();
+    method public androidx.glance.layout.Alignment getTopStart();
+    property public final int Bottom;
+    property public final androidx.glance.layout.Alignment BottomCenter;
+    property public final androidx.glance.layout.Alignment BottomEnd;
+    property public final androidx.glance.layout.Alignment BottomStart;
+    property public final androidx.glance.layout.Alignment Center;
+    property public final androidx.glance.layout.Alignment CenterEnd;
+    property public final int CenterHorizontally;
+    property public final androidx.glance.layout.Alignment CenterStart;
+    property public final int CenterVertically;
+    property public final int End;
+    property public final int Start;
+    property public final int Top;
+    property public final androidx.glance.layout.Alignment TopCenter;
+    property public final androidx.glance.layout.Alignment TopEnd;
+    property public final androidx.glance.layout.Alignment TopStart;
+  }
+
+  @kotlin.jvm.JvmInline public static final value class Alignment.Horizontal {
+    field public static final androidx.glance.layout.Alignment.Horizontal.Companion Companion;
+  }
+
+  public static final class Alignment.Horizontal.Companion {
+    method public int getCenterHorizontally();
+    method public int getEnd();
+    method public int getStart();
+    property public final int CenterHorizontally;
+    property public final int End;
+    property public final int Start;
+  }
+
+  @kotlin.jvm.JvmInline public static final value class Alignment.Vertical {
+    field public static final androidx.glance.layout.Alignment.Vertical.Companion Companion;
+  }
+
+  public static final class Alignment.Vertical.Companion {
+    method public int getBottom();
+    method public int getCenterVertically();
+    method public int getTop();
+    property public final int Bottom;
+    property public final int CenterVertically;
+    property public final int Top;
+  }
+
+  public final class BoxKt {
+    method @androidx.compose.runtime.Composable public static void Box(optional androidx.glance.GlanceModifier modifier, optional androidx.glance.layout.Alignment contentAlignment, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  public final class ColumnKt {
+    method @androidx.compose.runtime.Composable public static void Column(optional androidx.glance.GlanceModifier modifier, optional int verticalAlignment, optional int horizontalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.layout.ColumnScope,kotlin.Unit> content);
+  }
+
+  public interface ColumnScope {
+    method public androidx.glance.GlanceModifier defaultWeight(androidx.glance.GlanceModifier);
+  }
+
+  @kotlin.jvm.JvmInline public final value class ContentScale {
+    ctor public ContentScale(int value);
+    field public static final androidx.glance.layout.ContentScale.Companion Companion;
+  }
+
+  public static final class ContentScale.Companion {
+    method public int getCrop();
+    method public int getFillBounds();
+    method public int getFit();
+    property public final int Crop;
+    property public final int FillBounds;
+    property public final int Fit;
+  }
+
+  public final class PaddingKt {
+    method public static androidx.glance.GlanceModifier absolutePadding(androidx.glance.GlanceModifier, optional float left, optional float top, optional float right, optional float bottom);
+    method public static androidx.glance.GlanceModifier absolutePadding(androidx.glance.GlanceModifier, optional @DimenRes int left, optional @DimenRes int top, optional @DimenRes int right, optional @DimenRes int bottom);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, float all);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional float horizontal, optional float vertical);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional float start, optional float top, optional float end, optional float bottom);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, @DimenRes int all);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional @DimenRes int horizontal, optional @DimenRes int vertical);
+    method public static androidx.glance.GlanceModifier padding(androidx.glance.GlanceModifier, optional @DimenRes int start, optional @DimenRes int top, optional @DimenRes int end, optional @DimenRes int bottom);
+  }
+
+  public final class RowKt {
+    method @androidx.compose.runtime.Composable public static void Row(optional androidx.glance.GlanceModifier modifier, optional int horizontalAlignment, optional int verticalAlignment, kotlin.jvm.functions.Function1<? super androidx.glance.layout.RowScope,kotlin.Unit> content);
+  }
+
+  public interface RowScope {
+    method public androidx.glance.GlanceModifier defaultWeight(androidx.glance.GlanceModifier);
+  }
+
+  public final class SizeModifiersKt {
+    method public static androidx.glance.GlanceModifier fillMaxHeight(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier fillMaxSize(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier fillMaxWidth(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier height(androidx.glance.GlanceModifier, float height);
+    method public static androidx.glance.GlanceModifier height(androidx.glance.GlanceModifier, @DimenRes int height);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, float size);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, float width, float height);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, @DimenRes int size);
+    method public static androidx.glance.GlanceModifier size(androidx.glance.GlanceModifier, @DimenRes int width, @DimenRes int height);
+    method public static androidx.glance.GlanceModifier width(androidx.glance.GlanceModifier, float width);
+    method public static androidx.glance.GlanceModifier width(androidx.glance.GlanceModifier, @DimenRes int width);
+    method public static androidx.glance.GlanceModifier wrapContentHeight(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier wrapContentSize(androidx.glance.GlanceModifier);
+    method public static androidx.glance.GlanceModifier wrapContentWidth(androidx.glance.GlanceModifier);
+  }
+
+  public final class SpacerKt {
+    method @androidx.compose.runtime.Composable public static void Spacer(optional androidx.glance.GlanceModifier modifier);
+  }
+
+}
+
+package androidx.glance.semantics {
+
+  public final class SemanticsConfiguration implements androidx.glance.semantics.SemanticsPropertyReceiver {
+    ctor public SemanticsConfiguration();
+    method public operator <T> T get(androidx.glance.semantics.SemanticsPropertyKey<T> key);
+    method public <T> T? getOrElseNullable(androidx.glance.semantics.SemanticsPropertyKey<T> key, kotlin.jvm.functions.Function0<? extends T?> defaultValue);
+    method public <T> T? getOrNull(androidx.glance.semantics.SemanticsPropertyKey<T> key);
+    method public <T> void set(androidx.glance.semantics.SemanticsPropertyKey<T> key, T value);
+  }
+
+  public final class SemanticsModifierKt {
+    method public static androidx.glance.GlanceModifier semantics(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function1<? super androidx.glance.semantics.SemanticsPropertyReceiver,kotlin.Unit> properties);
+  }
+
+  public final class SemanticsProperties {
+    method public androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> getContentDescription();
+    method public androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> getTestTag();
+    property public final androidx.glance.semantics.SemanticsPropertyKey<java.util.List<java.lang.String>> ContentDescription;
+    property public final androidx.glance.semantics.SemanticsPropertyKey<java.lang.String> TestTag;
+    field public static final androidx.glance.semantics.SemanticsProperties INSTANCE;
+  }
+
+  public final class SemanticsPropertiesKt {
+    method public static String getContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver);
+    method public static String getTestTag(androidx.glance.semantics.SemanticsPropertyReceiver);
+    method public static void setContentDescription(androidx.glance.semantics.SemanticsPropertyReceiver, String);
+    method public static void setTestTag(androidx.glance.semantics.SemanticsPropertyReceiver, String);
+  }
+
+  public final class SemanticsPropertyKey<T> {
+    ctor public SemanticsPropertyKey(String name, optional kotlin.jvm.functions.Function2<? super T?,? super T,? extends T?> mergePolicy);
+    method public String getName();
+    method public T? merge(T? parentValue, T childValue);
+    property public final String name;
+  }
+
+  public interface SemanticsPropertyReceiver {
+    method public operator <T> void set(androidx.glance.semantics.SemanticsPropertyKey<T> key, T value);
+  }
+
+}
+
+package androidx.glance.state {
+
+  public interface GlanceStateDefinition<T> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<T>>);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
+  }
+
+  public final class PreferencesGlanceStateDefinition implements androidx.glance.state.GlanceStateDefinition<androidx.datastore.preferences.core.Preferences> {
+    method public suspend Object? getDataStore(android.content.Context context, String fileKey, kotlin.coroutines.Continuation<? super androidx.datastore.core.DataStore<androidx.datastore.preferences.core.Preferences>>);
+    method public java.io.File getLocation(android.content.Context context, String fileKey);
+    field public static final androidx.glance.state.PreferencesGlanceStateDefinition INSTANCE;
+  }
+
+}
+
+package androidx.glance.text {
+
+  public final class FontFamily {
+    ctor public FontFamily(String family);
+    method public String getFamily();
+    property public final String family;
+    field public static final androidx.glance.text.FontFamily.Companion Companion;
+  }
+
+  public static final class FontFamily.Companion {
+    method public androidx.glance.text.FontFamily getCursive();
+    method public androidx.glance.text.FontFamily getMonospace();
+    method public androidx.glance.text.FontFamily getSansSerif();
+    method public androidx.glance.text.FontFamily getSerif();
+    property public final androidx.glance.text.FontFamily Cursive;
+    property public final androidx.glance.text.FontFamily Monospace;
+    property public final androidx.glance.text.FontFamily SansSerif;
+    property public final androidx.glance.text.FontFamily Serif;
+  }
+
+  @kotlin.jvm.JvmInline public final value class FontStyle {
+    field public static final androidx.glance.text.FontStyle.Companion Companion;
+  }
+
+  public static final class FontStyle.Companion {
+    method public int getItalic();
+    method public int getNormal();
+    method public java.util.List<androidx.glance.text.FontStyle> values();
+    property public final int Italic;
+    property public final int Normal;
+  }
+
+  @kotlin.jvm.JvmInline public final value class FontWeight {
+    method public int getValue();
+    property public final int value;
+    field public static final androidx.glance.text.FontWeight.Companion Companion;
+  }
+
+  public static final class FontWeight.Companion {
+    method public int getBold();
+    method public int getMedium();
+    method public int getNormal();
+    property public final int Bold;
+    property public final int Medium;
+    property public final int Normal;
+  }
+
+  @kotlin.jvm.JvmInline public final value class TextAlign {
+    field public static final androidx.glance.text.TextAlign.Companion Companion;
+  }
+
+  public static final class TextAlign.Companion {
+    method public int getCenter();
+    method public int getEnd();
+    method public int getLeft();
+    method public int getRight();
+    method public int getStart();
+    method public java.util.List<androidx.glance.text.TextAlign> values();
+    property public final int Center;
+    property public final int End;
+    property public final int Left;
+    property public final int Right;
+    property public final int Start;
+  }
+
+  @kotlin.jvm.JvmInline public final value class TextDecoration {
+    method @androidx.compose.runtime.Stable public operator boolean contains(int other);
+    method @androidx.compose.runtime.Stable public operator int plus(int decoration);
+    field public static final androidx.glance.text.TextDecoration.Companion Companion;
+  }
+
+  public static final class TextDecoration.Companion {
+    method public int combine(java.util.List<androidx.glance.text.TextDecoration> decorations);
+    method public int getLineThrough();
+    method public int getNone();
+    method public int getUnderline();
+    property public final int LineThrough;
+    property public final int None;
+    property public final int Underline;
+  }
+
+  public final class TextDefaults {
+    method public androidx.glance.unit.ColorProvider getDefaultTextColor();
+    method public androidx.glance.text.TextStyle getDefaultTextStyle();
+    property public final androidx.glance.unit.ColorProvider defaultTextColor;
+    property public final androidx.glance.text.TextStyle defaultTextStyle;
+    field public static final androidx.glance.text.TextDefaults INSTANCE;
+  }
+
+  public final class TextKt {
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.glance.GlanceModifier modifier, optional androidx.glance.text.TextStyle style, optional int maxLines);
+  }
+
+  @androidx.compose.runtime.Immutable public final class TextStyle {
+    ctor public TextStyle(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration, optional androidx.glance.text.FontFamily? fontFamily);
+    method public androidx.glance.text.TextStyle copy(optional androidx.glance.unit.ColorProvider color, optional androidx.compose.ui.unit.TextUnit? fontSize, optional androidx.glance.text.FontWeight? fontWeight, optional androidx.glance.text.FontStyle? fontStyle, optional androidx.glance.text.TextAlign? textAlign, optional androidx.glance.text.TextDecoration? textDecoration, optional androidx.glance.text.FontFamily? fontFamily);
+    method public androidx.glance.unit.ColorProvider getColor();
+    method public androidx.glance.text.FontFamily? getFontFamily();
+    method public androidx.compose.ui.unit.TextUnit? getFontSize();
+    method public androidx.glance.text.FontStyle? getFontStyle();
+    method public androidx.glance.text.FontWeight? getFontWeight();
+    method public androidx.glance.text.TextAlign? getTextAlign();
+    method public androidx.glance.text.TextDecoration? getTextDecoration();
+    property public final androidx.glance.unit.ColorProvider color;
+    property public final androidx.glance.text.FontFamily? fontFamily;
+    property public final androidx.compose.ui.unit.TextUnit? fontSize;
+    property public final androidx.glance.text.FontStyle? fontStyle;
+    property public final androidx.glance.text.FontWeight? fontWeight;
+    property public final androidx.glance.text.TextAlign? textAlign;
+    property public final androidx.glance.text.TextDecoration? textDecoration;
+  }
+
+}
+
+package androidx.glance.unit {
+
+  public interface ColorProvider {
+    method public long getColor(android.content.Context context);
+  }
+
+  public final class ColorProviderKt {
+    method public static androidx.glance.unit.ColorProvider ColorProvider(long color);
+  }
+
+}
+
diff --git a/gradle.properties b/gradle.properties
index 0204323..0c3fb8f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -45,7 +45,7 @@
 androidx.includeOptionalProjects=false
 
 # Keep ComposeCompiler pinned unless performing Kotlin upgrade & ComposeCompiler release
-androidx.unpinComposeCompiler=true
+androidx.unpinComposeCompiler=false
 
 # Disable features we do not use
 android.defaults.buildfeatures.aidl=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5eac539..b56a64b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -28,7 +28,7 @@
 byteBuddy = "1.14.9"
 asm = "9.3"
 cmake = "3.22.1"
-composeCompilerPlugin = "1.5.9"  # Update when pulling in new stable binaries
+composeCompilerPlugin = "1.5.11"  # Update when pulling in new stable binaries
 dagger = "2.49"
 dexmaker = "2.28.3"
 dokka = "1.8.20-dev-214"
@@ -107,7 +107,7 @@
 checkerframework = { module = "org.checkerframework:checker-qual", version = "2.5.3" }
 checkmark = { module = "net.saff.checkmark:checkmark", version = "0.1.6" }
 constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.0.1"}
-dackka = { module = "com.google.devsite:dackka", version = "1.5.0" }
+dackka = { module = "com.google.devsite:dackka", version = "1.5.1" }
 dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
 daggerCompiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
 desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version = "2.0.3" }
@@ -188,6 +188,7 @@
 kotlinMetadataJvm = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version = "0.9.0" }
 kotlinSerializationCore = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinSerialization" }
 kotlinSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
+kotlinSerializationJsonOkio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "kotlinSerialization" }
 kotlinSerializationProtobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinSerialization" }
 kotlinGradlePluginz = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
 kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 2883a4d..58ece42 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -20,9 +20,9 @@
          <trust group="com.google.mlkit" reason="b/223907608"/>
          <trust group="com.google.testing.platform" reason="b/215430394"/>
          <trust group="gradle" name="gradle"/>
-         <trust file=".*[.]asc" regex="true"/>
          <trust file=".*-javadoc[.]jar" regex="true"/>
          <trust file=".*-sources[.]jar" regex="true"/>
+         <trust file=".*[.]asc" regex="true"/>
          <trust group="^androidx(?!\.compose.compiler\b)\..*" regex="true" reason="not signed yet"/>
          <trust group="^com[.]android($|([.].*))" regex="true" reason="b/215430394"/>
       </trusted-artifacts>
@@ -339,6 +339,7 @@
          <trusted-key id="A4FD709CC4B0515F2E6AF04E218FA0F6A941A037" group="com.github.kevinstern"/>
          <trusted-key id="A5B2DDE7843E7CA3E8CAABD02383163BC40844FD" group="org.reactivestreams"/>
          <trusted-key id="A5BD02B93E7A40482EB1D66A5F69AD087600B22C" group="org.ow2.asm"/>
+         <trusted-key id="A5F483CD733A4EBAEA378B2AE88979FB9B30ACF2" group="^androidx\..*" regex="true"/>
          <trusted-key id="A6D6C97108B8585F91B158748671A8DF71296252" group="^com[.]squareup($|([.].*))" regex="true"/>
          <trusted-key id="A7892505CF1A58076453E52D7999BEFBA1039E8B" group="net.bytebuddy"/>
          <trusted-key id="AA417737BD805456DB3CBDDE6601E5C08DCCBB96" group="info.picocli" name="picocli"/>
@@ -784,6 +785,16 @@
             <pgp value="720746177725A89207A7075BFD5DEA07FCB690A8"/>
          </artifact>
       </component>
+      <component group="org.jetbrains.kotlin" name="kotlin-gradle-plugin" version="1.9.23">
+         <artifact name="kotlin-gradle-plugin-1.9.23-gradle82.jar">
+            <ignored-keys>
+               <ignored-key id="6F538074CCEBF35F28AF9B066A0975F8B1127B83" reason="Temporary diagnostic for b/321949384"/>
+            </ignored-keys>
+            <sha256 value="9cc4a2206fa5f1adbb4e416f267dc22d8207905b8a1e138c02cb3fac24c67e14" origin="Generated by Gradle" reason="Temporary diagnostic for b/321949384">
+               <also-trust value="bcc74a07c69dd11c94b260b1ee8a618969629ccaeac2e6ea0a779da3c3f7ec3f"/>
+            </sha256>
+         </artifact>
+      </component>
       <component group="org.jetbrains.kotlin" name="kotlin-reflect" version="1.3.71">
          <artifact name="kotlin-reflect-1.3.71.pom">
             <sha256 value="4df94aaeee8d900be431386e31ef44e82a66e57c3ae30866aec2875aff01fe70" origin="Generated by Gradle"/>
diff --git a/gradlew b/gradlew
index 28d0634..04379b2 100755
--- a/gradlew
+++ b/gradlew
@@ -243,11 +243,6 @@
   disableCi=false
 fi
 
-# workaround for https://github.com/gradle/gradle/issues/18386
-if [[ " ${@} " =~ " --profile " ]]; then
-  mkdir -p reports
-fi
-
 # Expand some arguments
 for compact in "--ci" "--strict" "--clean" "--no-ci"; do
   expanded=""
@@ -259,7 +254,8 @@
        -Pandroidx.enableAffectedModuleDetection\
        -Pandroidx.printTimestamps\
        --no-watch-fs\
-       -Pandroidx.highMemory"
+       -Pandroidx.highMemory\
+       --profile"
     fi
   fi
   if [ "$compact" == "--strict" ]; then
@@ -302,6 +298,11 @@
   fi
 done
 
+# workaround for https://github.com/gradle/gradle/issues/18386
+if [[ " ${@} " =~ " --profile " ]]; then
+  mkdir -p reports
+fi
+
 raiseMemory=false
 if [[ " ${@} " =~ " -Pandroidx.highMemory " ]]; then
     raiseMemory=true
diff --git a/graphics/graphics-path/build.gradle b/graphics/graphics-path/build.gradle
index 091c671..753e958 100644
--- a/graphics/graphics-path/build.gradle
+++ b/graphics/graphics-path/build.gradle
@@ -67,6 +67,7 @@
                     "-fomit-frame-pointer",
                     "-ffunction-sections",
                     "-fdata-sections",
+                    "-fstack-protector",
                     "-Wl,--gc-sections",
                     "-Wl,-Bsymbolic-functions",
                     "-nostdlib++"
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 65e848f..4a99988 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -117,7 +117,7 @@
 
 package androidx.health.connect.client.contracts {
 
-  public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute> {
+  public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute?> {
     ctor public ExerciseRouteRequestContract();
     method public android.content.Intent createIntent(android.content.Context context, String input);
     method public androidx.health.connect.client.records.ExerciseRoute? parseResult(int resultCode, android.content.Intent? intent);
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index f249592..8eb6800 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -117,7 +117,7 @@
 
 package androidx.health.connect.client.contracts {
 
-  public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute> {
+  public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute?> {
     ctor public ExerciseRouteRequestContract();
     method public android.content.Intent createIntent(android.content.Context context, String input);
     method public androidx.health.connect.client.records.ExerciseRoute? parseResult(int resultCode, android.content.Intent? intent);
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
index b13f7bd..57247c7 100644
--- a/health/health-services-client/api/current.txt
+++ b/health/health-services-client/api/current.txt
@@ -238,6 +238,10 @@
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLOORS_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> GOLF_SHOT_COUNT;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> GOLF_SHOT_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> GROUND_CONTACT_BALANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> GROUND_CONTACT_BALANCE_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> GROUND_CONTACT_TIME;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> GROUND_CONTACT_TIME_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> HEART_RATE_BPM;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> HEART_RATE_BPM_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> INCLINE_DISTANCE;
@@ -260,10 +264,16 @@
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> STEPS_PER_MINUTE;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> STRIDE_LENGTH;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> STRIDE_LENGTH_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VERTICAL_OSCILLATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VERTICAL_OSCILLATION_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VERTICAL_RATIO;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VERTICAL_RATIO_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VO2_MAX_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> WALKING_STEPS;
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
index d3191b4..359c428 100644
--- a/health/health-services-client/api/restricted_current.txt
+++ b/health/health-services-client/api/restricted_current.txt
@@ -238,6 +238,10 @@
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLOORS_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> GOLF_SHOT_COUNT;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> GOLF_SHOT_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> GROUND_CONTACT_BALANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> GROUND_CONTACT_BALANCE_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> GROUND_CONTACT_TIME;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> GROUND_CONTACT_TIME_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> HEART_RATE_BPM;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> HEART_RATE_BPM_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> INCLINE_DISTANCE;
@@ -260,10 +264,16 @@
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> STEPS_PER_MINUTE;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> STRIDE_LENGTH;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> STRIDE_LENGTH_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VERTICAL_OSCILLATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VERTICAL_OSCILLATION_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VERTICAL_RATIO;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VERTICAL_RATIO_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VO2_MAX_STATS;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> WALKING_STEPS;
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
index c646fe6..575812e 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
@@ -597,6 +597,90 @@
             createCumulativeDataType("Rep Count")
 
         /**
+         * The amount of time during a single step that the runner's foot was in contact with the
+         * ground in milliseconds in `long` format.
+         */
+        @JvmField
+        val GROUND_CONTACT_TIME: DeltaDataType<Long, SampleDataPoint<Long>> =
+            createSampleDataType("Ground Contact Time")
+
+        /**
+         * Statistics on the amount of time during a single step that the runner's foot was in
+         * contact with the ground in milliseconds in `long` format.
+         */
+        @JvmField
+        val GROUND_CONTACT_TIME_STATS: AggregateDataType<Long, StatisticalDataPoint<Long>> =
+            createStatsDataType("Ground Contact Time")
+
+        /**
+         * Percentage of time the right foot is on the ground in percentage in `double` format.
+         *
+         * Percentage value is from 0 to 100.0. For instance, 52.0 means the right foot is on the
+         * ground 52% of the time.
+         */
+        @JvmField
+        val GROUND_CONTACT_BALANCE: DeltaDataType<Double, SampleDataPoint<Double>> =
+            createSampleDataType("Ground Contact Balance")
+
+        /**
+         * Statistics on percentage of time the right foot is on the ground in percentage in
+         * `double` format.
+         *
+         * Percentage value is from 0 to 100.0. For instance, 52.0 means the right foot is on the
+         * round 52% of the time.
+         */
+        @JvmField
+        val GROUND_CONTACT_BALANCE_STATS:
+        AggregateDataType<Double, StatisticalDataPoint<Double>> =
+            createStatsDataType("Ground Contact Balance")
+
+        /**
+         * Distance the center of mass moves up-and-down with each step in centimeters in `double`
+         * format.
+         */
+        @JvmField
+        val VERTICAL_OSCILLATION: DeltaDataType<Double, SampleDataPoint<Double>> =
+            createSampleDataType("Vertical Oscillation")
+
+        /**
+         * Statistic on distance the center of mass moves up-and-down with each step in centimeters
+         * in `double` format.
+         */
+        @JvmField
+        val VERTICAL_OSCILLATION_STATS: AggregateDataType<Double, StatisticalDataPoint<Double>> =
+            createStatsDataType("Vertical Oscillation")
+
+        /**
+         * Vertical oscillation / stride length.
+         *
+         * For example, a vertical oscillation of 5.0cm and stride length .8m (80 cm) would have a
+         * vertical ratio of 0.625.
+         */
+        @JvmField
+        val VERTICAL_RATIO: DeltaDataType<Double, SampleDataPoint<Double>> =
+            createSampleDataType("Vertical Ratio")
+
+        /**
+         * Statistics on vertical oscillation / stride length.
+         *
+         * For example, a vertical oscillation of 5.0cm and stride length .8m (80 cm) would have a
+         * vertical ratio of 0.625.
+         */
+        @JvmField
+        val VERTICAL_RATIO_STATS: AggregateDataType<Double, StatisticalDataPoint<Double>> =
+            createStatsDataType("Vertical Ratio")
+
+        /** Distance covered by a single step in meters in `double` format. */
+        @JvmField
+        val STRIDE_LENGTH: DeltaDataType<Double, SampleDataPoint<Double>> =
+            createSampleDataType("Stride Length")
+
+        /** Statistics on distance covered by a single step in meters in `double` format. */
+        @JvmField
+        val STRIDE_LENGTH_STATS: AggregateDataType<Double, StatisticalDataPoint<Double>> =
+            createStatsDataType("Stride Length")
+
+        /**
          * The total step count over a day, where the previous day ends and a new day begins at
          * 12:00 AM local time. Each [DataPoint] of this type will cover the interval from the start
          * of day to now. In the event of time-zone shifts, the interval may be greater than 24hrs.
@@ -662,6 +746,8 @@
             FLAT_GROUND_DURATION,
             FLOORS,
             GOLF_SHOT_COUNT,
+            GROUND_CONTACT_BALANCE,
+            GROUND_CONTACT_TIME,
             HEART_RATE_BPM,
             INCLINE_DISTANCE,
             INCLINE_DURATION,
@@ -673,8 +759,11 @@
             SPEED,
             STEPS,
             STEPS_PER_MINUTE,
+            STRIDE_LENGTH,
             SWIMMING_LAP_COUNT,
             SWIMMING_STROKES,
+            VERTICAL_OSCILLATION,
+            VERTICAL_RATIO,
             VO2_MAX,
             WALKING_STEPS,
         )
@@ -692,6 +781,8 @@
             FLAT_GROUND_DURATION_TOTAL,
             FLOORS_TOTAL,
             GOLF_SHOT_COUNT_TOTAL,
+            GROUND_CONTACT_BALANCE_STATS,
+            GROUND_CONTACT_TIME_STATS,
             HEART_RATE_BPM_STATS,
             INCLINE_DISTANCE_TOTAL,
             INCLINE_DURATION_TOTAL,
@@ -702,8 +793,11 @@
             SPEED_STATS,
             STEPS_PER_MINUTE_STATS,
             STEPS_TOTAL,
+            STRIDE_LENGTH_STATS,
             SWIMMING_LAP_COUNT_TOTAL,
             SWIMMING_STROKES_TOTAL,
+            VERTICAL_OSCILLATION_STATS,
+            VERTICAL_RATIO_STATS,
             VO2_MAX_STATS,
             WALKING_STEPS_TOTAL,
         )
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt
index 93fe6bd..8250710 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt
@@ -26,9 +26,19 @@
 import androidx.health.services.client.data.DataType.Companion.ELEVATION_GAIN_DAILY
 import androidx.health.services.client.data.DataType.Companion.FLOORS_DAILY
 import androidx.health.services.client.data.DataType.Companion.FORMAT_BYTE_ARRAY
+import androidx.health.services.client.data.DataType.Companion.GROUND_CONTACT_BALANCE
+import androidx.health.services.client.data.DataType.Companion.GROUND_CONTACT_BALANCE_STATS
+import androidx.health.services.client.data.DataType.Companion.GROUND_CONTACT_TIME
+import androidx.health.services.client.data.DataType.Companion.GROUND_CONTACT_TIME_STATS
 import androidx.health.services.client.data.DataType.Companion.LOCATION
 import androidx.health.services.client.data.DataType.Companion.STEPS
 import androidx.health.services.client.data.DataType.Companion.STEPS_DAILY
+import androidx.health.services.client.data.DataType.Companion.STRIDE_LENGTH
+import androidx.health.services.client.data.DataType.Companion.STRIDE_LENGTH_STATS
+import androidx.health.services.client.data.DataType.Companion.VERTICAL_OSCILLATION
+import androidx.health.services.client.data.DataType.Companion.VERTICAL_OSCILLATION_STATS
+import androidx.health.services.client.data.DataType.Companion.VERTICAL_RATIO
+import androidx.health.services.client.data.DataType.Companion.VERTICAL_RATIO_STATS
 import androidx.health.services.client.data.DataType.TimeType.Companion.INTERVAL
 import androidx.health.services.client.data.DataType.TimeType.Companion.UNKNOWN
 import androidx.health.services.client.proto.DataProto
@@ -213,6 +223,31 @@
     }
 
     @Test
+    fun aggregatesAndDeltaDataTypeValuesShouldMatch() {
+        val aggregates = DataType.aggregateDataTypes.toMutableSet().apply {
+            // Active duration is special cased and does not have a delta form. Developers get the
+            // Active duration not from a DataPoint, but instead from from a property in the
+            // ExerciseUpdate directly. The DataType is only used to enable setting an ExerciseGoal,
+            // which only operate on aggregates. So, we do not have a delta datatype for this and
+            // instead only have an aggregate.
+            remove(ACTIVE_EXERCISE_DURATION_TOTAL)
+        }.map { it.name to it.valueClass }.toMap()
+        // Certain deltas are expected to not have aggregates
+        val deltas = DataType.deltaDataTypes.toMutableSet().apply {
+            // Aggregate location doesn't make a lot of sense
+            remove(LOCATION)
+            // Dailies are used in passive and passive only deals with deltas
+            remove(CALORIES_DAILY)
+            remove(DISTANCE_DAILY)
+            remove(ELEVATION_GAIN_DAILY)
+            remove(FLOORS_DAILY)
+            remove(STEPS_DAILY)
+        }.map { it.name to it.valueClass }.toMap()
+
+        assertThat(aggregates).isEqualTo(deltas)
+    }
+
+    @Test
     fun allDataTypesShouldBeInEitherDeltaOrAggregateDataTypeSets() {
         // If this test fails, you haven't added a new DataType to one of the sets below:
         val joinedSet = DataType.deltaDataTypes + DataType.aggregateDataTypes
@@ -231,4 +266,22 @@
         assertThat(dataTypesThroughReflection).contains(ABSOLUTE_ELEVATION_STATS)
         assertThat(joinedSet).containsExactlyElementsIn(dataTypesThroughReflection)
     }
+
+    @Test
+    fun sampleDataTypesAreNotAggregates() {
+        assertThat(GROUND_CONTACT_BALANCE.isAggregate).isFalse()
+        assertThat(GROUND_CONTACT_TIME.isAggregate).isFalse()
+        assertThat(VERTICAL_OSCILLATION.isAggregate).isFalse()
+        assertThat(VERTICAL_RATIO.isAggregate).isFalse()
+        assertThat(STRIDE_LENGTH.isAggregate).isFalse()
+    }
+
+    @Test
+    fun statsDataTypesAreAggregates() {
+        assertThat(GROUND_CONTACT_BALANCE_STATS.isAggregate).isTrue()
+        assertThat(GROUND_CONTACT_TIME_STATS.isAggregate).isTrue()
+        assertThat(VERTICAL_OSCILLATION_STATS.isAggregate).isTrue()
+        assertThat(VERTICAL_RATIO_STATS.isAggregate).isTrue()
+        assertThat(STRIDE_LENGTH_STATS.isAggregate).isTrue()
+    }
 }
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
index 2c6a3bc..31c8df8 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
@@ -208,7 +208,7 @@
         assertThat(fakeService.registeredCallbacks).hasSize(2)
         // Stub is not reused.
         assertThat(fakeService.registeredCallbacks[0]).isNotSameInstanceAs(
-            fakeService.registeredCallbacks[1]);
+            fakeService.registeredCallbacks[1])
     }
 
     @Test
diff --git a/kruth/kruth/api/current.txt b/kruth/kruth/api/current.txt
index c59b100..857e856 100644
--- a/kruth/kruth/api/current.txt
+++ b/kruth/kruth/api/current.txt
@@ -265,56 +265,56 @@
     method public void inOrder();
   }
 
-  public final class PrimitiveBooleanArraySubject extends androidx.kruth.Subject<boolean[]> {
+  public final class PrimitiveBooleanArraySubject extends androidx.kruth.Subject<boolean[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Boolean> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveByteArraySubject extends androidx.kruth.Subject<byte[]> {
+  public final class PrimitiveByteArraySubject extends androidx.kruth.Subject<byte[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Byte> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveCharArraySubject extends androidx.kruth.Subject<char[]> {
+  public final class PrimitiveCharArraySubject extends androidx.kruth.Subject<char[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Character> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveDoubleArraySubject extends androidx.kruth.Subject<double[]> {
+  public final class PrimitiveDoubleArraySubject extends androidx.kruth.Subject<double[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Double> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveFloatArraySubject extends androidx.kruth.Subject<float[]> {
+  public final class PrimitiveFloatArraySubject extends androidx.kruth.Subject<float[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Float> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveIntArraySubject extends androidx.kruth.Subject<int[]> {
+  public final class PrimitiveIntArraySubject extends androidx.kruth.Subject<int[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Integer> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveLongArraySubject extends androidx.kruth.Subject<long[]> {
+  public final class PrimitiveLongArraySubject extends androidx.kruth.Subject<long[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Long> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveShortArraySubject extends androidx.kruth.Subject<short[]> {
+  public final class PrimitiveShortArraySubject extends androidx.kruth.Subject<short[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Short> asList();
     method public void hasLength(int length);
     method public void isEmpty();
diff --git a/kruth/kruth/api/restricted_current.txt b/kruth/kruth/api/restricted_current.txt
index d387ad6..90ec259 100644
--- a/kruth/kruth/api/restricted_current.txt
+++ b/kruth/kruth/api/restricted_current.txt
@@ -265,56 +265,56 @@
     method public void inOrder();
   }
 
-  public final class PrimitiveBooleanArraySubject extends androidx.kruth.Subject<boolean[]> {
+  public final class PrimitiveBooleanArraySubject extends androidx.kruth.Subject<boolean[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Boolean> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveByteArraySubject extends androidx.kruth.Subject<byte[]> {
+  public final class PrimitiveByteArraySubject extends androidx.kruth.Subject<byte[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Byte> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveCharArraySubject extends androidx.kruth.Subject<char[]> {
+  public final class PrimitiveCharArraySubject extends androidx.kruth.Subject<char[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Character> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveDoubleArraySubject extends androidx.kruth.Subject<double[]> {
+  public final class PrimitiveDoubleArraySubject extends androidx.kruth.Subject<double[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Double> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveFloatArraySubject extends androidx.kruth.Subject<float[]> {
+  public final class PrimitiveFloatArraySubject extends androidx.kruth.Subject<float[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Float> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveIntArraySubject extends androidx.kruth.Subject<int[]> {
+  public final class PrimitiveIntArraySubject extends androidx.kruth.Subject<int[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Integer> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveLongArraySubject extends androidx.kruth.Subject<long[]> {
+  public final class PrimitiveLongArraySubject extends androidx.kruth.Subject<long[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Long> asList();
     method public void hasLength(int length);
     method public void isEmpty();
     method public void isNotEmpty();
   }
 
-  public final class PrimitiveShortArraySubject extends androidx.kruth.Subject<short[]> {
+  public final class PrimitiveShortArraySubject extends androidx.kruth.Subject<short[]?> {
     method public androidx.kruth.IterableSubject<java.lang.Short> asList();
     method public void hasLength(int length);
     method public void isEmpty();
diff --git a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
index 2d62acb..3fa55bf 100644
--- a/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
+++ b/kruth/kruth/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
@@ -165,6 +165,15 @@
         containsAnyIn(requireNonNull(expected).asList())
     }
 
+    /**
+     * Checks that the actual iterable contains at least all of the expected elements or fails. If
+     * an element appears more than once in the expected elements to this call then it must appear
+     * at least that number of times in the actual elements.
+     *
+     * To also test that the contents appear in the given order, make a call to `inOrder()` on the
+     * object returned by this method. The expected elements must appear in the given order within
+     * the actual elements, but they are not required to be consecutive.
+     */
     fun containsAtLeast(
         firstExpected: Any?,
         secondExpected: Any?,
@@ -172,6 +181,15 @@
     ): Ordered =
         containsAtLeastElementsIn(listOf(firstExpected, secondExpected, *restOfExpected))
 
+    /**
+     * Checks that the actual iterable contains at least all of the expected elements or fails. If
+     * an element appears more than once in the expected elements then it must appear at least that
+     * number of times in the actual elements.
+     *
+     * To also test that the contents appear in the given order, make a call to `inOrder()` on the
+     * object returned by this method. The expected elements must appear in the given order within
+     * the actual elements, but they are not required to be consecutive.
+     */
     fun containsAtLeastElementsIn(expected: Iterable<*>?): Ordered {
         requireNonNull(expected)
         val actualList = requireNonNull(actual).toMutableList()
@@ -520,9 +538,6 @@
             }
     }
 
-    /**
-     * @deprecated You probably meant to call [containsNoneOf] instead.
-     */
     @Deprecated(
         message = "You probably meant to call containsNoneOf instead.",
         replaceWith = ReplaceWith(expression = "containsNoneOf"),
diff --git a/leanback/leanback-preference/api/1.2.0-beta01.txt b/leanback/leanback-preference/api/1.2.0-beta01.txt
index 55e5b84..6ebc263 100644
--- a/leanback/leanback-preference/api/1.2.0-beta01.txt
+++ b/leanback/leanback-preference/api/1.2.0-beta01.txt
@@ -26,7 +26,7 @@
     method @Deprecated public void onSaveInstanceState(android.os.Bundle!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterMulti(CharSequence![]!, CharSequence![]!, java.util.Set<java.lang.String!>!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
@@ -34,7 +34,7 @@
     method @Deprecated public void onItemClick(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterSingle(CharSequence![]!, CharSequence![]!, CharSequence!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
diff --git a/leanback/leanback-preference/api/current.txt b/leanback/leanback-preference/api/current.txt
index 55e5b84..6ebc263 100644
--- a/leanback/leanback-preference/api/current.txt
+++ b/leanback/leanback-preference/api/current.txt
@@ -26,7 +26,7 @@
     method @Deprecated public void onSaveInstanceState(android.os.Bundle!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterMulti(CharSequence![]!, CharSequence![]!, java.util.Set<java.lang.String!>!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
@@ -34,7 +34,7 @@
     method @Deprecated public void onItemClick(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterSingle(CharSequence![]!, CharSequence![]!, CharSequence!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
diff --git a/leanback/leanback-preference/api/restricted_1.2.0-beta01.txt b/leanback/leanback-preference/api/restricted_1.2.0-beta01.txt
index 16b6c76..56d892c 100644
--- a/leanback/leanback-preference/api/restricted_1.2.0-beta01.txt
+++ b/leanback/leanback-preference/api/restricted_1.2.0-beta01.txt
@@ -27,7 +27,7 @@
     method @Deprecated public void onSaveInstanceState(android.os.Bundle!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterMulti(CharSequence![]!, CharSequence![]!, java.util.Set<java.lang.String!>!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
@@ -35,7 +35,7 @@
     method @Deprecated public void onItemClick(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterSingle(CharSequence![]!, CharSequence![]!, CharSequence!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
diff --git a/leanback/leanback-preference/api/restricted_current.txt b/leanback/leanback-preference/api/restricted_current.txt
index 16b6c76..56d892c 100644
--- a/leanback/leanback-preference/api/restricted_current.txt
+++ b/leanback/leanback-preference/api/restricted_current.txt
@@ -27,7 +27,7 @@
     method @Deprecated public void onSaveInstanceState(android.os.Bundle!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterMulti extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterMulti(CharSequence![]!, CharSequence![]!, java.util.Set<java.lang.String!>!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
@@ -35,7 +35,7 @@
     method @Deprecated public void onItemClick(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!);
   }
 
-  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+  @Deprecated public class LeanbackListPreferenceDialogFragment.AdapterSingle extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!> implements androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
     ctor @Deprecated public LeanbackListPreferenceDialogFragment.AdapterSingle(CharSequence![]!, CharSequence![]!, CharSequence!);
     method @Deprecated public int getItemCount();
     method @Deprecated public void onBindViewHolder(androidx.leanback.preference.LeanbackListPreferenceDialogFragment.ViewHolder!, int);
diff --git a/leanback/leanback/api/1.2.0-beta01.txt b/leanback/leanback/api/1.2.0-beta01.txt
index e783a1d..e46ff1c 100644
--- a/leanback/leanback/api/1.2.0-beta01.txt
+++ b/leanback/leanback/api/1.2.0-beta01.txt
@@ -149,7 +149,7 @@
     method @Deprecated public void showTitleView(boolean);
   }
 
-  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public BrowseFragment.ListRowFragmentFactory();
     method @Deprecated public androidx.leanback.app.RowsFragment! createFragment(Object!);
   }
@@ -249,7 +249,7 @@
     method public void showTitleView(boolean);
   }
 
-  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment> {
+  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment!> {
     ctor public BrowseSupportFragment.ListRowFragmentFactory();
     method public androidx.leanback.app.RowsSupportFragment! createFragment(Object!);
   }
@@ -822,11 +822,11 @@
     method @Deprecated public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentAdapter(androidx.leanback.app.RowsFragment!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsFragment!);
   }
 
@@ -861,11 +861,11 @@
     method public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
-  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
@@ -1137,7 +1137,7 @@
     method public boolean setDataSource(android.net.Uri!);
   }
 
-  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], int[], T!);
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], T!);
     method public int[] getFastForwardSpeeds();
@@ -1321,7 +1321,7 @@
     method public void onVideoSizeChanged(int, int);
   }
 
-  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackTransportControlGlue(android.content.Context!, T!);
     method public final androidx.leanback.widget.PlaybackSeekDataProvider! getSeekProvider();
     method public final boolean isSeekEnabled();
@@ -1840,7 +1840,7 @@
     field public static final int NO_CHECK_SET = 0; // 0x0
   }
 
-  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder> {
+  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder!> {
     ctor @Deprecated public GuidedAction.Builder();
     ctor public GuidedAction.Builder(android.content.Context?);
     method public androidx.leanback.widget.GuidedAction build();
@@ -1899,7 +1899,7 @@
     method public void onAutofill(android.view.View!);
   }
 
-  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction> {
+  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction!> {
     ctor public GuidedActionDiffCallback();
     method public boolean areContentsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
     method public boolean areItemsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
@@ -1993,12 +1993,12 @@
     method public void setDate(long);
   }
 
-  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder> {
+  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder!> {
     ctor public GuidedDatePickerAction.Builder(android.content.Context);
     method public androidx.leanback.widget.GuidedDatePickerAction build();
   }
 
-  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B> {
+  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B!> {
     ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
     method protected final void applyDatePickerValues(androidx.leanback.widget.GuidedDatePickerAction);
     method public B! date(long);
@@ -2258,10 +2258,10 @@
     method public void onActionClicked(androidx.leanback.widget.Action);
   }
 
-  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row!> {
   }
 
-  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row!> {
   }
 
   public class PageRow extends androidx.leanback.widget.Row {
@@ -2282,7 +2282,7 @@
     method @CallSuper public void updateValues();
   }
 
-  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Float> {
+  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Float!> {
     ctor public Parallax.FloatProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(float, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(float);
@@ -2298,7 +2298,7 @@
     field public static final float UNKNOWN_BEFORE = -3.4028235E38f;
   }
 
-  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Integer> {
+  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Integer!> {
     ctor public Parallax.IntProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(int, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(int);
@@ -2613,7 +2613,7 @@
     method public void unselect();
   }
 
-  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty> {
+  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty!> {
     ctor public RecyclerViewParallax();
     method public androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty! createProperty(String!, int);
     method public float getMaxValue();
diff --git a/leanback/leanback/api/current.txt b/leanback/leanback/api/current.txt
index e783a1d..e46ff1c 100644
--- a/leanback/leanback/api/current.txt
+++ b/leanback/leanback/api/current.txt
@@ -149,7 +149,7 @@
     method @Deprecated public void showTitleView(boolean);
   }
 
-  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public BrowseFragment.ListRowFragmentFactory();
     method @Deprecated public androidx.leanback.app.RowsFragment! createFragment(Object!);
   }
@@ -249,7 +249,7 @@
     method public void showTitleView(boolean);
   }
 
-  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment> {
+  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment!> {
     ctor public BrowseSupportFragment.ListRowFragmentFactory();
     method public androidx.leanback.app.RowsSupportFragment! createFragment(Object!);
   }
@@ -822,11 +822,11 @@
     method @Deprecated public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentAdapter(androidx.leanback.app.RowsFragment!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsFragment!);
   }
 
@@ -861,11 +861,11 @@
     method public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
-  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
@@ -1137,7 +1137,7 @@
     method public boolean setDataSource(android.net.Uri!);
   }
 
-  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], int[], T!);
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], T!);
     method public int[] getFastForwardSpeeds();
@@ -1321,7 +1321,7 @@
     method public void onVideoSizeChanged(int, int);
   }
 
-  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackTransportControlGlue(android.content.Context!, T!);
     method public final androidx.leanback.widget.PlaybackSeekDataProvider! getSeekProvider();
     method public final boolean isSeekEnabled();
@@ -1840,7 +1840,7 @@
     field public static final int NO_CHECK_SET = 0; // 0x0
   }
 
-  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder> {
+  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder!> {
     ctor @Deprecated public GuidedAction.Builder();
     ctor public GuidedAction.Builder(android.content.Context?);
     method public androidx.leanback.widget.GuidedAction build();
@@ -1899,7 +1899,7 @@
     method public void onAutofill(android.view.View!);
   }
 
-  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction> {
+  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction!> {
     ctor public GuidedActionDiffCallback();
     method public boolean areContentsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
     method public boolean areItemsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
@@ -1993,12 +1993,12 @@
     method public void setDate(long);
   }
 
-  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder> {
+  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder!> {
     ctor public GuidedDatePickerAction.Builder(android.content.Context);
     method public androidx.leanback.widget.GuidedDatePickerAction build();
   }
 
-  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B> {
+  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B!> {
     ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
     method protected final void applyDatePickerValues(androidx.leanback.widget.GuidedDatePickerAction);
     method public B! date(long);
@@ -2258,10 +2258,10 @@
     method public void onActionClicked(androidx.leanback.widget.Action);
   }
 
-  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row!> {
   }
 
-  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row!> {
   }
 
   public class PageRow extends androidx.leanback.widget.Row {
@@ -2282,7 +2282,7 @@
     method @CallSuper public void updateValues();
   }
 
-  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Float> {
+  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Float!> {
     ctor public Parallax.FloatProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(float, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(float);
@@ -2298,7 +2298,7 @@
     field public static final float UNKNOWN_BEFORE = -3.4028235E38f;
   }
 
-  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Integer> {
+  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Integer!> {
     ctor public Parallax.IntProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(int, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(int);
@@ -2613,7 +2613,7 @@
     method public void unselect();
   }
 
-  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty> {
+  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty!> {
     ctor public RecyclerViewParallax();
     method public androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty! createProperty(String!, int);
     method public float getMaxValue();
diff --git a/leanback/leanback/api/restricted_1.2.0-beta01.txt b/leanback/leanback/api/restricted_1.2.0-beta01.txt
index 8bdf344..daf1928 100644
--- a/leanback/leanback/api/restricted_1.2.0-beta01.txt
+++ b/leanback/leanback/api/restricted_1.2.0-beta01.txt
@@ -171,7 +171,7 @@
     method @Deprecated public void showTitleView(boolean);
   }
 
-  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public BrowseFragment.ListRowFragmentFactory();
     method @Deprecated public androidx.leanback.app.RowsFragment! createFragment(Object!);
   }
@@ -271,7 +271,7 @@
     method public void showTitleView(boolean);
   }
 
-  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment> {
+  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment!> {
     ctor public BrowseSupportFragment.ListRowFragmentFactory();
     method public androidx.leanback.app.RowsSupportFragment! createFragment(Object!);
   }
@@ -863,11 +863,11 @@
     method @Deprecated public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentAdapter(androidx.leanback.app.RowsFragment!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsFragment!);
   }
 
@@ -902,11 +902,11 @@
     method public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
-  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
@@ -1211,7 +1211,7 @@
     field @Deprecated protected final androidx.leanback.widget.PlaybackControlsRow.ThumbsUpAction! mThumbsUpAction;
   }
 
-  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], int[], T!);
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], T!);
     method public int[] getFastForwardSpeeds();
@@ -1398,7 +1398,7 @@
     method public void onVideoSizeChanged(int, int);
   }
 
-  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackTransportControlGlue(android.content.Context!, T!);
     method public final androidx.leanback.widget.PlaybackSeekDataProvider! getSeekProvider();
     method public final boolean isSeekEnabled();
@@ -2002,7 +2002,7 @@
     field public static final int NO_CHECK_SET = 0; // 0x0
   }
 
-  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder> {
+  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder!> {
     ctor @Deprecated public GuidedAction.Builder();
     ctor public GuidedAction.Builder(android.content.Context?);
     method public androidx.leanback.widget.GuidedAction build();
@@ -2106,7 +2106,7 @@
     method public void onAutofill(android.view.View!);
   }
 
-  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction> {
+  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction!> {
     ctor public GuidedActionDiffCallback();
     method public boolean areContentsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
     method public boolean areItemsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
@@ -2201,12 +2201,12 @@
     method public void setDate(long);
   }
 
-  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder> {
+  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder!> {
     ctor public GuidedDatePickerAction.Builder(android.content.Context);
     method public androidx.leanback.widget.GuidedDatePickerAction build();
   }
 
-  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B> {
+  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B!> {
     ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
     method protected final void applyDatePickerValues(androidx.leanback.widget.GuidedDatePickerAction);
     method public B! date(long);
@@ -2484,10 +2484,10 @@
     method public void onActionClicked(androidx.leanback.widget.Action);
   }
 
-  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row!> {
   }
 
-  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row!> {
   }
 
   public class PageRow extends androidx.leanback.widget.Row {
@@ -2530,7 +2530,7 @@
     method @CallSuper public void updateValues();
   }
 
-  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Float> {
+  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Float!> {
     ctor public Parallax.FloatProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(float, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(float);
@@ -2546,7 +2546,7 @@
     field public static final float UNKNOWN_BEFORE = -3.4028235E38f;
   }
 
-  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Integer> {
+  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Integer!> {
     ctor public Parallax.IntProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(int, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(int);
@@ -2870,7 +2870,7 @@
     method public void unselect();
   }
 
-  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty> {
+  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty!> {
     ctor public RecyclerViewParallax();
     method public androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty! createProperty(String!, int);
     method public float getMaxValue();
diff --git a/leanback/leanback/api/restricted_current.txt b/leanback/leanback/api/restricted_current.txt
index 8bdf344..daf1928 100644
--- a/leanback/leanback/api/restricted_current.txt
+++ b/leanback/leanback/api/restricted_current.txt
@@ -171,7 +171,7 @@
     method @Deprecated public void showTitleView(boolean);
   }
 
-  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class BrowseFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseFragment.FragmentFactory<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public BrowseFragment.ListRowFragmentFactory();
     method @Deprecated public androidx.leanback.app.RowsFragment! createFragment(Object!);
   }
@@ -271,7 +271,7 @@
     method public void showTitleView(boolean);
   }
 
-  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment> {
+  public static class BrowseSupportFragment.ListRowFragmentFactory extends androidx.leanback.app.BrowseSupportFragment.FragmentFactory<androidx.leanback.app.RowsSupportFragment!> {
     ctor public BrowseSupportFragment.ListRowFragmentFactory();
     method public androidx.leanback.app.RowsSupportFragment! createFragment(Object!);
   }
@@ -863,11 +863,11 @@
     method @Deprecated public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentAdapter(androidx.leanback.app.RowsFragment!);
   }
 
-  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment> {
+  @Deprecated public static class RowsFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsFragment!> {
     ctor @Deprecated public RowsFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsFragment!);
   }
 
@@ -902,11 +902,11 @@
     method public void setSelectedPosition(int, boolean, androidx.leanback.widget.Presenter.ViewHolderTask!);
   }
 
-  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
-  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment> {
+  public static class RowsSupportFragment.MainFragmentRowsAdapter extends androidx.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter<androidx.leanback.app.RowsSupportFragment!> {
     ctor public RowsSupportFragment.MainFragmentRowsAdapter(androidx.leanback.app.RowsSupportFragment!);
   }
 
@@ -1211,7 +1211,7 @@
     field @Deprecated protected final androidx.leanback.widget.PlaybackControlsRow.ThumbsUpAction! mThumbsUpAction;
   }
 
-  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackBannerControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], int[], T!);
     ctor public PlaybackBannerControlGlue(android.content.Context, int[], T!);
     method public int[] getFastForwardSpeeds();
@@ -1398,7 +1398,7 @@
     method public void onVideoSizeChanged(int, int);
   }
 
-  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T> {
+  public class PlaybackTransportControlGlue<T extends androidx.leanback.media.PlayerAdapter> extends androidx.leanback.media.PlaybackBaseControlGlue<T!> {
     ctor public PlaybackTransportControlGlue(android.content.Context!, T!);
     method public final androidx.leanback.widget.PlaybackSeekDataProvider! getSeekProvider();
     method public final boolean isSeekEnabled();
@@ -2002,7 +2002,7 @@
     field public static final int NO_CHECK_SET = 0; // 0x0
   }
 
-  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder> {
+  public static class GuidedAction.Builder extends androidx.leanback.widget.GuidedAction.BuilderBase<androidx.leanback.widget.GuidedAction.Builder!> {
     ctor @Deprecated public GuidedAction.Builder();
     ctor public GuidedAction.Builder(android.content.Context?);
     method public androidx.leanback.widget.GuidedAction build();
@@ -2106,7 +2106,7 @@
     method public void onAutofill(android.view.View!);
   }
 
-  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction> {
+  public class GuidedActionDiffCallback extends androidx.leanback.widget.DiffCallback<androidx.leanback.widget.GuidedAction!> {
     ctor public GuidedActionDiffCallback();
     method public boolean areContentsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
     method public boolean areItemsTheSame(androidx.leanback.widget.GuidedAction, androidx.leanback.widget.GuidedAction);
@@ -2201,12 +2201,12 @@
     method public void setDate(long);
   }
 
-  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder> {
+  public static final class GuidedDatePickerAction.Builder extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase<androidx.leanback.widget.GuidedDatePickerAction.Builder!> {
     ctor public GuidedDatePickerAction.Builder(android.content.Context);
     method public androidx.leanback.widget.GuidedDatePickerAction build();
   }
 
-  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B> {
+  public abstract static class GuidedDatePickerAction.BuilderBase<B extends androidx.leanback.widget.GuidedDatePickerAction.BuilderBase> extends androidx.leanback.widget.GuidedAction.BuilderBase<B!> {
     ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
     method protected final void applyDatePickerValues(androidx.leanback.widget.GuidedDatePickerAction);
     method public B! date(long);
@@ -2484,10 +2484,10 @@
     method public void onActionClicked(androidx.leanback.widget.Action);
   }
 
-  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewClickedListener extends androidx.leanback.widget.BaseOnItemViewClickedListener<androidx.leanback.widget.Row!> {
   }
 
-  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row> {
+  public interface OnItemViewSelectedListener extends androidx.leanback.widget.BaseOnItemViewSelectedListener<androidx.leanback.widget.Row!> {
   }
 
   public class PageRow extends androidx.leanback.widget.Row {
@@ -2530,7 +2530,7 @@
     method @CallSuper public void updateValues();
   }
 
-  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Float> {
+  public static class Parallax.FloatProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Float!> {
     ctor public Parallax.FloatProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(float, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(float);
@@ -2546,7 +2546,7 @@
     field public static final float UNKNOWN_BEFORE = -3.4028235E38f;
   }
 
-  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax,java.lang.Integer> {
+  public static class Parallax.IntProperty extends android.util.Property<androidx.leanback.widget.Parallax!,java.lang.Integer!> {
     ctor public Parallax.IntProperty(String!, int);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! at(int, float);
     method public final androidx.leanback.widget.Parallax.PropertyMarkerValue! atAbsolute(int);
@@ -2870,7 +2870,7 @@
     method public void unselect();
   }
 
-  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty> {
+  public class RecyclerViewParallax extends androidx.leanback.widget.Parallax<androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty!> {
     ctor public RecyclerViewParallax();
     method public androidx.leanback.widget.RecyclerViewParallax.ChildPositionProperty! createProperty(String!, int);
     method public float getMaxValue();
diff --git a/libraryversions.toml b/libraryversions.toml
index 560faf2..01af990 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,6 +1,6 @@
 [versions]
-ACTIVITY = "1.9.0-beta01"
-ANNOTATION = "1.8.0-alpha02"
+ACTIVITY = "1.9.0-rc01"
+ANNOTATION = "1.8.0-beta01"
 ANNOTATION_EXPERIMENTAL = "1.4.0-rc01"
 APPACTIONS_BUILTINTYPES = "1.0.0-alpha01"
 APPACTIONS_INTERACTION = "1.0.0-alpha01"
@@ -14,18 +14,18 @@
 BLUETOOTH = "1.0.0-alpha02"
 BROWSER = "1.9.0-alpha01"
 BUILDSRC_TESTS = "1.0.0-alpha01"
-CAMERA = "1.4.0-alpha04"
+CAMERA = "1.4.0-beta01"
 CAMERA_PIPE = "1.0.0-alpha01"
 CAMERA_TESTING = "1.0.0-alpha01"
-CAMERA_VIEWFINDER = "1.4.0-alpha04"
+CAMERA_VIEWFINDER = "1.4.0-alpha05"
 CAMERA_VIEWFINDER_COMPOSE = "1.0.0-alpha01"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-alpha01"
 COLLECTION = "1.5.0-alpha01"
-COMPOSE = "1.7.0-alpha05"
+COMPOSE = "1.7.0-alpha06"
 COMPOSE_COMPILER = "1.5.11"  # Update when preparing for a release
-COMPOSE_MATERIAL3 = "1.3.0-alpha03"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha09"
+COMPOSE_MATERIAL3 = "1.3.0-alpha04"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha10"
 COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha05"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
 COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
@@ -34,7 +34,7 @@
 CONSTRAINTLAYOUT_CORE = "1.1.0-alpha13"
 CONTENTPAGER = "1.1.0-alpha01"
 COORDINATORLAYOUT = "1.3.0-alpha02"
-CORE = "1.13.0-beta01"
+CORE = "1.13.0-rc01"
 CORE_ANIMATION = "1.0.0-rc01"
 CORE_ANIMATION_TESTING = "1.0.0-rc01"
 CORE_APPDIGEST = "1.0.0-alpha01"
@@ -43,12 +43,13 @@
 CORE_I18N = "1.0.0-alpha02"
 CORE_LOCATION_ALTITUDE = "1.0.0-alpha02"
 CORE_PERFORMANCE = "1.0.0"
-CORE_REMOTEVIEWS = "1.1.0-alpha01"
+CORE_REMOTEVIEWS = "1.1.0-beta01"
 CORE_ROLE = "1.2.0-alpha01"
 CORE_SPLASHSCREEN = "1.1.0-alpha02"
 CORE_TELECOM = "1.0.0-alpha02"
 CORE_UWB = "1.0.0-alpha08"
 CREDENTIALS = "1.3.0-alpha02"
+CREDENTIALS_E2EE_QUARANTINE = "1.0.0-alpha01"
 CREDENTIALS_FIDO_QUARANTINE = "1.0.0-alpha02"
 CURSORADAPTER = "1.1.0-alpha01"
 CUSTOMVIEW = "1.2.0-alpha03"
@@ -63,9 +64,9 @@
 EMOJI2 = "1.5.0-alpha01"
 ENTERPRISE = "1.1.0-rc01"
 EXIFINTERFACE = "1.4.0-alpha01"
-FRAGMENT = "1.7.0-beta01"
+FRAGMENT = "1.7.0-rc01"
 FUTURES = "1.2.0-alpha03"
-GLANCE = "1.1.0-alpha01"
+GLANCE = "1.1.0-beta01"
 GLANCE_PREVIEW = "1.0.0-alpha06"
 GLANCE_TEMPLATE = "1.0.0-alpha06"
 GLANCE_WEAR_TILES = "1.0.0-alpha06"
@@ -75,7 +76,7 @@
 GRAPHICS_SHAPES = "1.0.0-beta01"
 GRIDLAYOUT = "1.1.0-beta02"
 HEALTH_CONNECT = "1.1.0-alpha08"
-HEALTH_SERVICES_CLIENT = "1.1.0-alpha02"
+HEALTH_SERVICES_CLIENT = "1.1.0-alpha03"
 HEIFWRITER = "1.1.0-alpha03"
 HILT = "1.2.0-rc01"
 HILT_NAVIGATION = "1.2.0-rc01"
@@ -92,15 +93,15 @@
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
-LIFECYCLE = "2.8.0-alpha03"
+LIFECYCLE = "2.8.0-alpha04"
 LIFECYCLE_EXTENSIONS = "2.2.0"
 LINT = "1.0.0-alpha01"
 LOADER = "1.2.0-alpha01"
 MEDIA = "1.7.0-rc01"
 MEDIAROUTER = "1.7.0-rc01"
 METRICS = "1.0.0-beta02"
-NAVIGATION = "2.8.0-alpha05"
-PAGING = "3.3.0-alpha05"
+NAVIGATION = "2.8.0-alpha06"
+PAGING = "3.3.0-beta01"
 PALETTE = "1.1.0-alpha01"
 PDF = "1.0.0-alpha01"
 PERCENTLAYOUT = "1.1.0-alpha01"
@@ -127,6 +128,7 @@
 SECURITY_BIOMETRIC = "1.0.0-alpha01"
 SECURITY_IDENTITY_CREDENTIAL = "1.0.0-alpha04"
 SECURITY_MLS = "1.0.0-alpha01"
+SECURITY_STATE = "1.0.0-alpha01"
 SHARETARGET = "1.3.0-alpha01"
 SLICE = "1.1.0-alpha03"
 SLICE_BENCHMARK = "1.1.0-alpha03"
@@ -144,7 +146,7 @@
 TEXT = "1.0.0-alpha01"
 TRACING = "1.3.0-alpha02"
 TRACING_PERFETTO = "1.0.0"
-TRANSITION = "1.5.0-beta01"
+TRANSITION = "1.5.0-rc01"
 TV = "1.0.0-alpha11"
 TVPROVIDER = "1.1.0-alpha02"
 VECTORDRAWABLE = "1.2.0-rc01"
@@ -154,7 +156,7 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.1.0-beta03"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.4.0-alpha05"
+WEAR_COMPOSE = "1.4.0-alpha06"
 WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha20"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
@@ -165,13 +167,13 @@
 WEAR_TILES = "1.4.0-alpha01"
 WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
 WEAR_WATCHFACE = "1.3.0-alpha02"
-WEBKIT = "1.11.0-beta01"
+WEBKIT = "1.12.0-alpha01"
 # Adding a comment to prevent merge conflicts for Window artifact
 WINDOW = "1.3.0-beta01"
 WINDOW_EXTENSIONS = "1.3.0-alpha01"
 WINDOW_EXTENSIONS_CORE = "1.1.0-alpha01"
 WINDOW_SIDECAR = "1.0.0-rc01"
-WORK = "2.10.0-alpha01"
+WORK = "2.10.0-alpha02"
 XR = "1.0.0-alpha01"
 
 [groups]
@@ -202,7 +204,7 @@
 COMPOSE_COMPILER = { group = "androidx.compose.compiler", atomicGroupVersion = "versions.COMPOSE_COMPILER" }
 COMPOSE_DESKTOP = { group = "androidx.compose.desktop", atomicGroupVersion = "versions.COMPOSE" }
 COMPOSE_FOUNDATION = { group = "androidx.compose.foundation", atomicGroupVersion = "versions.COMPOSE" }
-COMPOSE_MATERIAL = { group = "androidx.compose.material", atomicGroupVersion = "versions.COMPOSE" }
+COMPOSE_MATERIAL = { group = "androidx.compose.material"}
 COMPOSE_MATERIAL3 = { group = "androidx.compose.material3", atomicGroupVersion = "versions.COMPOSE_MATERIAL3" }
 COMPOSE_MATERIAL3_ADAPTIVE = { group = "androidx.compose.material3.adaptive", atomicGroupVersion = "versions.COMPOSE_MATERIAL3_ADAPTIVE" }
 COMPOSE_RUNTIME = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE" }
diff --git a/lifecycle/lifecycle-livedata-core-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt b/lifecycle/lifecycle-livedata-core-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
index ad4d5df..cf14cdc 100644
--- a/lifecycle/lifecycle-livedata-core-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
+++ b/lifecycle/lifecycle-livedata-core-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
@@ -29,10 +29,10 @@
 import com.android.tools.lint.detector.api.UastLintUtils
 import com.android.tools.lint.detector.api.isKotlin
 import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiTypeParameter
 import com.intellij.psi.PsiVariable
 import com.intellij.psi.PsiWhiteSpace
 import com.intellij.psi.impl.source.PsiImmediateClassType
-import org.jetbrains.kotlin.asJava.elements.KtLightTypeParameter
 import org.jetbrains.kotlin.psi.KtCallExpression
 import org.jetbrains.kotlin.psi.KtCallableDeclaration
 import org.jetbrains.kotlin.psi.KtNullableType
@@ -102,7 +102,7 @@
                 // argument: `Boolean`
                 val typeReference = element.sourcePsi
                     ?.children
-                    ?.firstOrNull { it is KtTypeReference } as? KtTypeReference
+                    ?.firstNotNullOfOrNull { it as? KtTypeReference }
                 val typeArgument = typeReference?.typeElement?.typeArgumentsAsTypes?.singleOrNull()
                 if (typeArgument != null) {
                     return typeArgument
@@ -114,12 +114,12 @@
                 // argument: `Boolean`
                 val expression = element.sourcePsi
                     ?.children
-                    ?.firstOrNull { it is KtCallExpression } as? KtCallExpression
+                    ?.firstNotNullOfOrNull { it as? KtCallExpression }
                 return expression?.typeArguments?.singleOrNull()?.typeReference
             }
 
             override fun visitCallExpression(node: UCallExpression) {
-                if (!isKotlin(node.sourcePsi) || !methods.contains(node.methodName) ||
+                if (!isKotlin(node.lang) || !methods.contains(node.methodName) ||
                     !context.evaluator.isMemberInSubClassOf(
                             node.resolve()!!, "androidx.lifecycle.LiveData", false
                         )
@@ -175,6 +175,11 @@
             return null
         }
         val cls = classType.resolve().getUastParentOfType<UClass>()
+        if (cls != null && !isKotlin(cls.lang)) {
+            // If the type argument refers to a Java type,
+            // we won't get KtTypeReference anyway, so bail out early.
+            return null
+        }
         val parentPsiType = cls?.superClassType as PsiClassType
         if (parentPsiType.hasParameters()) {
             val parentTypeReference = cls.uastSuperTypes[0]
@@ -223,7 +228,7 @@
     private fun UCallExpression.isGenericTypeDefinition(): Boolean {
         val classType = typeArguments.singleOrNull() as? PsiImmediateClassType
         val resolveGenerics = classType?.resolveGenerics()
-        return resolveGenerics?.element is KtLightTypeParameter
+        return resolveGenerics?.element is PsiTypeParameter
     }
 
     /**
diff --git a/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/NonNullableMutableLiveDataDetectorTest.kt b/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/NonNullableMutableLiveDataDetectorTest.kt
index 97e2074..03173d3 100644
--- a/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/NonNullableMutableLiveDataDetectorTest.kt
+++ b/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/NonNullableMutableLiveDataDetectorTest.kt
@@ -852,4 +852,166 @@
 1 errors, 0 warnings
         """)
     }
+
+    @Test
+    fun dataClassFromBinary_nonNull() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+                import some.other.pkg.SomeData
+
+                fun foo() {
+                    val liveData = MutableLiveData<SomeData>()
+                    val x = SomeData()
+                    liveData.value = x
+                    liveData.postValue(bar(6))
+                }
+
+                fun bar(x: Int): SomeData {
+                    return SomeData(extras = x)
+                }
+            """
+            ).indented(),
+            DATA_LIB,
+        ).expectClean()
+    }
+
+    @Test
+    fun dataClassFromBinary_nullable() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+                import some.other.pkg.SomeData
+
+                fun foo() {
+                    val liveData = MutableLiveData<SomeData>()
+                    val bar: SomeData? = SomeData()
+                    liveData.value = bar
+                }
+            """
+            ).indented(),
+            DATA_LIB,
+        ).expect(
+            """
+src/com/example/test.kt:9: Error: Expected non-nullable value [NullSafeMutableLiveData]
+    liveData.value = bar
+                     ~~~
+1 errors, 0 warnings
+        """
+        ).expectFixDiffs(
+            """
+Fix for src/com/example/test.kt line 9: Change `LiveData` type to nullable:
+@@ -7 +7
+-     val liveData = MutableLiveData<SomeData>()
++     val liveData = MutableLiveData<SomeData?>()
+Fix for src/com/example/test.kt line 9: Add non-null asserted (!!) call:
+@@ -9 +9
+-     liveData.value = bar
++     liveData.value = bar!!
+        """
+        )
+    }
+
+    @Test
+    fun typeArgumentFromJava() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.LiveData
+                import some.other.pkg.SomeData
+
+                abstract class Test : LiveData<SomeData>() {
+                  abstract val remoteRefreshCounter: RemoteRefreshCounter
+
+                  fun foo() {
+                    val counterValue = remoteRefreshCounter.value
+                    // This will trigger the detector, but the receiver type is from Java.
+                    remoteRefreshCounter.value = (counterValue ?: 1) - 1
+                  }
+                }
+                """
+            ).indented(),
+            DATA_LIB,
+            java(
+                """
+                package com.example;
+
+                import androidx.lifecycle.MutableLiveData;
+
+                public final class RemoteRefreshCounter extends MutableLiveData<Integer> {
+                  public RemoteRefreshCounter() {}
+                }
+                """
+            ).indented(),
+        ).expectClean()
+    }
+
+    private companion object {
+        val DATA_LIB: TestFile =
+            bytecode(
+                "libs/data.jar",
+                kotlin(
+                    """
+                    package some.other.pkg
+
+                    data class SomeData
+                    @JvmOverloads
+                    constructor(val items: List<Boolean> = listOf(), val extras: Int = 42)
+                """
+                ).indented(),
+                0x9ae81803,
+                """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgEuXiTs7P1UutSMwtyEkVYgtJLS7x
+                LlFi0GIAAJY6UNwvAAAA
+                """,
+                """
+                some/other/pkg/SomeData.class:
+                H4sIAAAAAAAA/41X3VMTVxT/3c3XZgmwiSDIh6JoDQkaRFrbgl+g1mBQC4pa
+                bOsSVlhIdnF3Q/Wl45N/gjPtS2f60Cce6kwFp850qL71L+pDp9NzdpcACVJm
+                wr3n3j3nd8/H79w7/PXv738AGMIzgTbHKus5y13Q7dzy0nxuipZXNFeLQQio
+                i9qKlitp5nzu1uyiXnRjCAlERwzTcC8ItKQLnkLFNUq5guG4w/m+aYG+2u2R
+                whbOqGWVdM0cvuDrHiosWW7JMHOLK+Xc+Er51opulyxtzhkW6C1Y9nxuUXdn
+                bc0wnZxmmparuYZF8k3LvVkplUgrYrh62ZGhCBzehmWYrm6bWimXN12brI2i
+                E0NCoLW4oBeXAvPbmq2VdVIUOJku1AY7vG1nikHmh/umE2hCs4JGqAKhNK8j
+                SCkI4wClqzYdCShojUPCQcqa/tS1NUdA5BNoxyHe7hAIuwuGw3n4QB0owrH6
+                NOcLu4V6RX+sVUruGCXItStF17InNHtJt4c50z2BRdEqlSg4L4tjW/INqu0R
+                gbheXnaf8SECqXRffUBHcUxBD3o57qhCMZwQkOd1N89V4DTW2uxefDqJjK4G
+                KaFE5gWUolVetkzddM9sXwxSkorWMnG1fxe+7ZG3y/vl4R4YCT75+JyfWIH7
+                6Q/q7lKjOkJ9+KQETuE0p5OilV3Lpxt3WF89Calpa/dGK0ZpTrdj+ETBOaZi
+                8yZy2muP8zI+Iwpqy8u6OSdwKr0Lteu2AlDybRgjjHu+xrIa1V6WF9nyEtW7
+                v8fvAPJlVKArnd/b7grbXWXFsb0Vv2DF6wm6zj5maZwSuKA5C2PWnJ5AARlu
+                zgmB5BYGXQn6PGfrFlGL/Miz3pcKbmOSkmTrjlfrqP6kopWIna27xfyVwPG9
+                7ie6XbTZkk7FkjVbv8pQ/3vJVLETuI8HfMnQKRGPLdSN9YoCB+o6gls9vT/i
+                +5fXYWbdI4ET++zb5ObVM6G72pzfJVJ5JUQPiuAhzgPomlui/acGrwZImqOe
+                /nvjeY8itUuKpG48V+gnqbRWG3xRViQ5QXOT/O6F3L7xfFAaEKNH5ViqSZZU
+                qUNOhVPSQHgglJJpHemQBqLvfo5Kamy8WU1sfb3+/kVoPK428ndP7lWbSCa4
+                GpjBKgypifEDajPJ4UFZVTvC7WJAXH//koGSvsZLQXKK5AMsTyarDsjkfEdY
+                ltX4ZNt2+K1PitrA8VNjU1YaNvvy9BJRLD5lzJuaW7F1gc7JiukaZT1vrhiO
+                QdS5vEUnoinTmdq6YJj6zUp5VrfvML2YFlZRK01rtsHrYPN4LVb1odsB2jjl
+                asWlCW05MFOmrIpd1K8ZvDgUYEzXeYMzxJgw1VVCih84mpe91Sye0BylMOM0
+                p/iVo7mR1vRWkIVNq3ukx5xozaYa3iCZWUdLJvsabZmu1+h85cE4NDIpo6So
+                kKlL6x7fCF3oZnqRxAcLT+JjNl2IVV2Qaa4wH6XAH36xie60Yj8uEWCE5o7u
+                8Pc/IPaGurk7QlJUzZzPZLvW8ZHvzAqNYUiKXHULZMduJXESxwiF0XJMeP6S
+                +Q1tv1ajiPqbXgSJQPYj8L1N70hYEn3IBO5tB+xc3QdgEtmqN7213oh9e9Bf
+                9aC31oPdQJL8bAWVHaWZi6T6lX0L6YGXxy0H/CKq1SKq1SKqO4qY2+FUTRHj
+                GCAC+j6eDYrY7RVR5nD9KrLYyXVcw1n//KCODQrJ9FLQKQxwDZJ3fvotzj1Y
+                x6epz9dwgWHWcDk15suda7im9q0hv4YbtclMB7Fs91fwoxM4eJHwmTFtPubN
+                kU71yAJjvsHUo5FtaT1IajKacYfSwMBtAbDAXUwHzpaD1hnKZH9BJLya/RPS
+                j4iEVrMbkCb4hH76e4MZb5NPoXXnT7wIr1bJ3IBwTP4HLTFIierR/M/AZlWG
+                8BBf08Gc9xhz4J7nRwTfBIU+RzKbNActnA1FxDq0VzuCoc9VxOagzn51v61r
+                Ue+UeHBKKoj2JMmciXhGhKI78f3cx2tyL3vo33mjhac0z9NukWDmZhDKQ8/j
+                cZ72FkiEkccilmYgHJRQnkGLg24HdLcp3ki/dm+MOuhzkHFw0sExB1kH/Q5O
+                Objr4KGDgoMhUvsPdw4FK1ANAAA=
+                """
+            )
+    }
 }
diff --git a/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/stubs/Stubs.kt b/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/stubs/Stubs.kt
index dbd5ed0..96d15cc 100644
--- a/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/stubs/Stubs.kt
+++ b/lifecycle/lifecycle-livedata-core-lint/src/test/java/androidx/lifecycle/livedata/core/lint/stubs/Stubs.kt
@@ -25,7 +25,7 @@
     public abstract class LiveData<T> {
         protected void postValue(T value) {}
         protected void setValue(T value) {}
-        public T getValue() {}
+        public T getValue() { return null; }
     }
 """
 ).indented()
diff --git a/lifecycle/lifecycle-livedata-core/api/current.txt b/lifecycle/lifecycle-livedata-core/api/current.txt
index 1168cc2..810396e 100644
--- a/lifecycle/lifecycle-livedata-core/api/current.txt
+++ b/lifecycle/lifecycle-livedata-core/api/current.txt
@@ -22,7 +22,7 @@
     method @Deprecated @MainThread public static inline <T> androidx.lifecycle.Observer<T> observe(androidx.lifecycle.LiveData<T>, androidx.lifecycle.LifecycleOwner owner, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onChanged);
   }
 
-  public class MutableLiveData<T> extends androidx.lifecycle.LiveData<T> {
+  public class MutableLiveData<T> extends androidx.lifecycle.LiveData<T!> {
     ctor public MutableLiveData();
     ctor public MutableLiveData(T!);
     method public void postValue(T!);
diff --git a/lifecycle/lifecycle-livedata-core/api/restricted_current.txt b/lifecycle/lifecycle-livedata-core/api/restricted_current.txt
index 1168cc2..810396e 100644
--- a/lifecycle/lifecycle-livedata-core/api/restricted_current.txt
+++ b/lifecycle/lifecycle-livedata-core/api/restricted_current.txt
@@ -22,7 +22,7 @@
     method @Deprecated @MainThread public static inline <T> androidx.lifecycle.Observer<T> observe(androidx.lifecycle.LiveData<T>, androidx.lifecycle.LifecycleOwner owner, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onChanged);
   }
 
-  public class MutableLiveData<T> extends androidx.lifecycle.LiveData<T> {
+  public class MutableLiveData<T> extends androidx.lifecycle.LiveData<T!> {
     ctor public MutableLiveData();
     ctor public MutableLiveData(T!);
     method public void postValue(T!);
diff --git a/lifecycle/lifecycle-livedata/api/current.txt b/lifecycle/lifecycle-livedata/api/current.txt
index 55bd162..3f39eb4 100644
--- a/lifecycle/lifecycle-livedata/api/current.txt
+++ b/lifecycle/lifecycle-livedata/api/current.txt
@@ -24,7 +24,7 @@
     property public abstract T? latestValue;
   }
 
-  public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData<T> {
+  public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData<T!> {
     ctor public MediatorLiveData();
     ctor public MediatorLiveData(T!);
     method @MainThread public <S> void addSource(androidx.lifecycle.LiveData<S!>, androidx.lifecycle.Observer<? super S!>);
diff --git a/lifecycle/lifecycle-livedata/api/restricted_current.txt b/lifecycle/lifecycle-livedata/api/restricted_current.txt
index 6724451..d22b8c4 100644
--- a/lifecycle/lifecycle-livedata/api/restricted_current.txt
+++ b/lifecycle/lifecycle-livedata/api/restricted_current.txt
@@ -33,7 +33,7 @@
     property public abstract T? latestValue;
   }
 
-  public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData<T> {
+  public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData<T!> {
     ctor public MediatorLiveData();
     ctor public MediatorLiveData(T!);
     method @MainThread public <S> void addSource(androidx.lifecycle.LiveData<S!>, androidx.lifecycle.Observer<? super S!>);
diff --git a/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/RepeatOnLifecycleDetector.kt b/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/RepeatOnLifecycleDetector.kt
index 7d42aa1..a2192eb 100644
--- a/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/RepeatOnLifecycleDetector.kt
+++ b/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/RepeatOnLifecycleDetector.kt
@@ -59,7 +59,7 @@
     override fun applicableSuperClasses(): List<String>? = listOf(FRAGMENT_CLASS, ACTIVITY_CLASS)
 
     override fun visitClass(context: JavaContext, declaration: UClass) {
-        if (!isKotlin(context.psiFile)) return // Check only Kotlin files
+        if (!isKotlin(declaration.lang)) return // Check only Kotlin files
 
         val visitedMethods = mutableSetOf<PsiMethod>()
         declaration.methods.forEach { method ->
diff --git a/lifecycle/lifecycle-runtime/build.gradle b/lifecycle/lifecycle-runtime/build.gradle
index f16a328..323d017 100644
--- a/lifecycle/lifecycle-runtime/build.gradle
+++ b/lifecycle/lifecycle-runtime/build.gradle
@@ -61,7 +61,7 @@
             dependencies {
                 api(libs.kotlinCoroutinesAndroid)
                 implementation("androidx.arch.core:core-runtime:2.2.0")
-                implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+                implementation("androidx.profileinstaller:profileinstaller:1.3.1")
             }
         }
 
@@ -136,5 +136,4 @@
     publish = Publish.SNAPSHOT_AND_RELEASE
     inceptionYear "2017"
     description "Android Lifecycle Runtime"
-    metalavaK2UastEnabled = true
 }
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInAppCompatActivityTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInAppCompatActivityTest.kt
index e5ee149..1ead559 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInAppCompatActivityTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInAppCompatActivityTest.kt
@@ -19,8 +19,8 @@
 import androidx.activity.compose.setContent
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import java.util.concurrent.CountDownLatch
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInComponentActivityTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInComponentActivityTest.kt
index 50f06f9..fb0e225 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInComponentActivityTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelInComponentActivityTest.kt
@@ -19,8 +19,8 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
index 3b02e84..4f8159f 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidTest/java/androidx/lifecycle/viewmodel/compose/ViewModelTest.kt
@@ -21,7 +21,6 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -30,6 +29,7 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.viewmodel.CreationExtras
 import androidx.lifecycle.viewmodel.MutableCreationExtras
diff --git a/lifecycle/lifecycle-viewmodel/api/current.txt b/lifecycle/lifecycle-viewmodel/api/current.txt
index 417a0af..558185d 100644
--- a/lifecycle/lifecycle-viewmodel/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/current.txt
@@ -43,8 +43,8 @@
     ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras defaultCreationExtras);
     ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner);
     ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory);
-    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
-    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
     method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(Class<T> modelClass);
     method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(String key, Class<T> modelClass);
     method @MainThread public final operator <T extends androidx.lifecycle.ViewModel> T get(String key, kotlin.reflect.KClass<T> modelClass);
@@ -66,8 +66,8 @@
   }
 
   public static final class ViewModelProvider.Companion {
-    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
-    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
   }
 
   public static interface ViewModelProvider.Factory {
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
index 417a0af..558185d 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
@@ -43,8 +43,8 @@
     ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras defaultCreationExtras);
     ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner);
     ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory);
-    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
-    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public static final androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
     method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(Class<T> modelClass);
     method @MainThread public operator <T extends androidx.lifecycle.ViewModel> T get(String key, Class<T> modelClass);
     method @MainThread public final operator <T extends androidx.lifecycle.ViewModel> T get(String key, kotlin.reflect.KClass<T> modelClass);
@@ -66,8 +66,8 @@
   }
 
   public static final class ViewModelProvider.Companion {
-    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
-    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, androidx.lifecycle.ViewModelProvider.Factory factory, androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStore store, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method public androidx.lifecycle.ViewModelProvider create(androidx.lifecycle.ViewModelStoreOwner owner, optional androidx.lifecycle.ViewModelProvider.Factory factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
   }
 
   public static interface ViewModelProvider.Factory {
diff --git a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt
index 5e5f45f..6909499 100644
--- a/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt
+++ b/lifecycle/lifecycle-viewmodel/src/androidMain/kotlin/androidx/lifecycle/ViewModelProvider.android.kt
@@ -344,6 +344,7 @@
 
     public actual companion object {
         @JvmStatic
+        @Suppress("MissingJvmstatic")
         public actual fun create(
             owner: ViewModelStoreOwner,
             factory: Factory,
@@ -351,6 +352,7 @@
         ): ViewModelProvider = ViewModelProvider(owner.viewModelStore, factory, extras)
 
         @JvmStatic
+        @Suppress("MissingJvmstatic")
         public actual fun create(
             store: ViewModelStore,
             factory: Factory,
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
index e0a9e70..b638571 100644
--- a/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/GradleIssueRegistry.kt
@@ -28,7 +28,8 @@
 
     override val issues = listOf(
         EagerConfigurationDetector.ISSUE,
-        InternalApiUsageDetector.ISSUE,
+        InternalApiUsageDetector.INTERNAL_GRADLE_ISSUE,
+        InternalApiUsageDetector.INTERNAL_AGP_ISSUE,
         WithPluginClasspathUsageDetector.ISSUE,
     )
 
diff --git a/lint/lint-gradle/src/main/java/androidx/lint/gradle/InternalApiUsageDetector.kt b/lint/lint-gradle/src/main/java/androidx/lint/gradle/InternalApiUsageDetector.kt
index 5e1b881..5b1d9cf 100644
--- a/lint/lint-gradle/src/main/java/androidx/lint/gradle/InternalApiUsageDetector.kt
+++ b/lint/lint-gradle/src/main/java/androidx/lint/gradle/InternalApiUsageDetector.kt
@@ -49,9 +49,14 @@
 
                 if (resolved is PsiClass) {
                     if (resolved.isInternalGradleApi()) {
-                        reportIncidentForNode(node, "Avoid using internal Gradle APIs")
+                        reportIncidentForNode(
+                            INTERNAL_GRADLE_ISSUE,
+                            node,
+                            "Avoid using internal Gradle APIs"
+                        )
                     } else if (resolved.isInternalAgpApi()) {
                         reportIncidentForNode(
+                            INTERNAL_AGP_ISSUE,
                             node,
                             "Avoid using internal Android Gradle Plugin APIs"
                         )
@@ -60,9 +65,9 @@
             }
         }
 
-        private fun reportIncidentForNode(node: UElement, message: String) {
+        private fun reportIncidentForNode(issue: Issue, node: UElement, message: String) {
             val incident = Incident(context)
-                .issue(ISSUE)
+                .issue(issue)
                 .location(context.getLocation(node))
                 .message(message)
                 .scope(node)
@@ -81,7 +86,7 @@
     }
 
     companion object {
-        val ISSUE = Issue.create(
+        val INTERNAL_GRADLE_ISSUE = Issue.create(
             "InternalGradleApiUsage",
             "Avoid using internal Gradle APIs",
             """
@@ -95,5 +100,19 @@
                 Scope.JAVA_FILE_SCOPE
             )
         )
+        val INTERNAL_AGP_ISSUE = Issue.create(
+            "InternalAgpApiUsage",
+            "Avoid using internal Android Gradle Plugin APIs",
+            """
+                Using internal APIs results in fragile plugin behavior as these types have no binary
+                compatibility guarantees. It is best to create a feature request to open up these
+                APIs if you find them useful.
+            """,
+            Category.CORRECTNESS, 5, Severity.ERROR,
+            Implementation(
+                InternalApiUsageDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
+        )
     }
 }
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/InternalApiUsageDetectorTest.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/InternalApiUsageDetectorTest.kt
index 23a8918..8f3a2e5 100644
--- a/lint/lint-gradle/src/test/java/androidx/lint/gradle/InternalApiUsageDetectorTest.kt
+++ b/lint/lint-gradle/src/test/java/androidx/lint/gradle/InternalApiUsageDetectorTest.kt
@@ -24,7 +24,10 @@
 @RunWith(JUnit4::class)
 class InternalApiUsageDetectorTest : GradleLintDetectorTest(
     detector = InternalApiUsageDetector(),
-    issues = listOf(InternalApiUsageDetector.ISSUE)
+    issues = listOf(
+        InternalApiUsageDetector.INTERNAL_GRADLE_ISSUE,
+        InternalApiUsageDetector.INTERNAL_AGP_ISSUE,
+    )
 ) {
     @Test
     fun `Test usage of internal Gradle API`() {
@@ -90,7 +93,7 @@
             .run()
             .expect(
                 """
-                src/test.kt:1: Error: Avoid using internal Android Gradle Plugin APIs [InternalGradleApiUsage]
+                src/test.kt:1: Error: Avoid using internal Android Gradle Plugin APIs [InternalAgpApiUsage]
                 import com.android.build.gradle.internal.lint.VariantInputs
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                 1 errors, 0 warnings
diff --git a/loader/loader/api/current.txt b/loader/loader/api/current.txt
index 9b59aa7..9e811b6 100644
--- a/loader/loader/api/current.txt
+++ b/loader/loader/api/current.txt
@@ -24,7 +24,7 @@
 
 package androidx.loader.content {
 
-  public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D> {
+  public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D!> {
     ctor public AsyncTaskLoader(android.content.Context);
     method public void cancelLoadInBackground();
     method protected java.util.concurrent.Executor getExecutor();
@@ -35,7 +35,7 @@
     method public void setUpdateThrottle(long);
   }
 
-  public class CursorLoader extends androidx.loader.content.AsyncTaskLoader<android.database.Cursor> {
+  public class CursorLoader extends androidx.loader.content.AsyncTaskLoader<android.database.Cursor!> {
     ctor public CursorLoader(android.content.Context);
     ctor public CursorLoader(android.content.Context, android.net.Uri, String![]?, String?, String![]?, String?);
     method public void deliverResult(android.database.Cursor?);
diff --git a/loader/loader/api/restricted_current.txt b/loader/loader/api/restricted_current.txt
index 9b59aa7..9e811b6 100644
--- a/loader/loader/api/restricted_current.txt
+++ b/loader/loader/api/restricted_current.txt
@@ -24,7 +24,7 @@
 
 package androidx.loader.content {
 
-  public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D> {
+  public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D!> {
     ctor public AsyncTaskLoader(android.content.Context);
     method public void cancelLoadInBackground();
     method protected java.util.concurrent.Executor getExecutor();
@@ -35,7 +35,7 @@
     method public void setUpdateThrottle(long);
   }
 
-  public class CursorLoader extends androidx.loader.content.AsyncTaskLoader<android.database.Cursor> {
+  public class CursorLoader extends androidx.loader.content.AsyncTaskLoader<android.database.Cursor!> {
     ctor public CursorLoader(android.content.Context);
     ctor public CursorLoader(android.content.Context, android.net.Uri, String![]?, String?, String![]?, String?);
     method public void deliverResult(android.database.Cursor?);
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index 6e3a990..fd4ea53 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -32,6 +32,9 @@
     method public abstract java.util.List<java.lang.String> serializeAsValues(T value);
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface ExperimentalSafeArgsApi {
+  }
+
   public interface FloatingWindow {
   }
 
@@ -252,6 +255,7 @@
   @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
     ctor @Deprecated public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
     ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
+    ctor @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, kotlin.reflect.KClass<?>? route, java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap);
     method @Deprecated public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
     method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
     method public D build();
@@ -285,16 +289,20 @@
     method public final void addDestinations(androidx.navigation.NavDestination... nodes);
     method public final void addDestinations(java.util.Collection<? extends androidx.navigation.NavDestination?> nodes);
     method public final void clear();
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> androidx.navigation.NavDestination? findNode();
     method public final androidx.navigation.NavDestination? findNode(@IdRes int resId);
     method public final androidx.navigation.NavDestination? findNode(String? route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public final <T> androidx.navigation.NavDestination? findNode(T? route);
     method public static final androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
     method @Deprecated @IdRes public final int getStartDestination();
     method @IdRes public final int getStartDestinationId();
     method public final String? getStartDestinationRoute();
     method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
     method public final void remove(androidx.navigation.NavDestination node);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> void setStartDestination();
     method public final void setStartDestination(int startDestId);
     method public final void setStartDestination(String startDestRoute);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public final <T> void setStartDestination(T startDestRoute);
     property @IdRes public final int startDestinationId;
     property public final String? startDestinationRoute;
     field public static final androidx.navigation.NavGraph.Companion Companion;
@@ -306,7 +314,9 @@
 
   @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
     ctor @Deprecated public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, Object startDestination, kotlin.reflect.KClass<?>? route, java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap);
     ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
+    ctor @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, kotlin.reflect.KClass<?> startDestination, kotlin.reflect.KClass<?>? route, java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap);
     method public final void addDestination(androidx.navigation.NavDestination destination);
     method public androidx.navigation.NavGraph build();
     method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -317,16 +327,24 @@
 
   public final class NavGraphBuilderKt {
     method @Deprecated public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline void navigation(androidx.navigation.NavGraphBuilder, Object startDestination, kotlin.reflect.KClass<?> route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline void navigation(androidx.navigation.NavGraphBuilder, kotlin.reflect.KClass<?> startDestination, kotlin.reflect.KClass<?> route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, Object startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<?> startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavGraphKt {
     method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
     method public static operator boolean contains(androidx.navigation.NavGraph, String route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline operator <reified T> boolean contains(androidx.navigation.NavGraph, kotlin.reflect.KClass<T> route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static operator <T> boolean contains(androidx.navigation.NavGraph, T route);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline operator <reified T> androidx.navigation.NavDestination get(androidx.navigation.NavGraph, kotlin.reflect.KClass<T> route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline operator <T> androidx.navigation.NavDestination get(androidx.navigation.NavGraph, T route);
     method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -437,7 +455,7 @@
     property public String name;
   }
 
-  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]?> {
     ctor public NavType.ParcelableArrayType(Class<D> type);
     method public D[]? get(android.os.Bundle bundle, String key);
     method public D[] parseValue(String value);
@@ -454,7 +472,7 @@
     property public String name;
   }
 
-  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]?> {
     ctor public NavType.SerializableArrayType(Class<D> type);
     method public D[]? get(android.os.Bundle bundle, String key);
     method public D[] parseValue(String value);
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index 6e3a990..fd4ea53 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -32,6 +32,9 @@
     method public abstract java.util.List<java.lang.String> serializeAsValues(T value);
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level.WARNING) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface ExperimentalSafeArgsApi {
+  }
+
   public interface FloatingWindow {
   }
 
@@ -252,6 +255,7 @@
   @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
     ctor @Deprecated public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
     ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, String? route);
+    ctor @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, kotlin.reflect.KClass<?>? route, java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap);
     method @Deprecated public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
     method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
     method public D build();
@@ -285,16 +289,20 @@
     method public final void addDestinations(androidx.navigation.NavDestination... nodes);
     method public final void addDestinations(java.util.Collection<? extends androidx.navigation.NavDestination?> nodes);
     method public final void clear();
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> androidx.navigation.NavDestination? findNode();
     method public final androidx.navigation.NavDestination? findNode(@IdRes int resId);
     method public final androidx.navigation.NavDestination? findNode(String? route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public final <T> androidx.navigation.NavDestination? findNode(T? route);
     method public static final androidx.navigation.NavDestination findStartDestination(androidx.navigation.NavGraph);
     method @Deprecated @IdRes public final int getStartDestination();
     method @IdRes public final int getStartDestinationId();
     method public final String? getStartDestinationRoute();
     method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
     method public final void remove(androidx.navigation.NavDestination node);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> void setStartDestination();
     method public final void setStartDestination(int startDestId);
     method public final void setStartDestination(String startDestRoute);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public final <T> void setStartDestination(T startDestRoute);
     property @IdRes public final int startDestinationId;
     property public final String? startDestinationRoute;
     field public static final androidx.navigation.NavGraph.Companion Companion;
@@ -306,7 +314,9 @@
 
   @androidx.navigation.NavDestinationDsl public class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
     ctor @Deprecated public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+    ctor @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, Object startDestination, kotlin.reflect.KClass<?>? route, java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap);
     ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, String startDestination, String? route);
+    ctor @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, kotlin.reflect.KClass<?> startDestination, kotlin.reflect.KClass<?>? route, java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap);
     method public final void addDestination(androidx.navigation.NavDestination destination);
     method public androidx.navigation.NavGraph build();
     method public final <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
@@ -317,16 +327,24 @@
 
   public final class NavGraphBuilderKt {
     method @Deprecated public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline void navigation(androidx.navigation.NavGraphBuilder, Object startDestination, kotlin.reflect.KClass<?> route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline void navigation(androidx.navigation.NavGraphBuilder, String startDestination, String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline void navigation(androidx.navigation.NavGraphBuilder, kotlin.reflect.KClass<?> startDestination, kotlin.reflect.KClass<?> route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method @Deprecated public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, Object startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<?> startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavGraphKt {
     method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
     method public static operator boolean contains(androidx.navigation.NavGraph, String route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline operator <reified T> boolean contains(androidx.navigation.NavGraph, kotlin.reflect.KClass<T> route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static operator <T> boolean contains(androidx.navigation.NavGraph, T route);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
     method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, String route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline operator <reified T> androidx.navigation.NavDestination get(androidx.navigation.NavGraph, kotlin.reflect.KClass<T> route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline operator <T> androidx.navigation.NavDestination get(androidx.navigation.NavGraph, T route);
     method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
     method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
@@ -437,7 +455,7 @@
     property public String name;
   }
 
-  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+  public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]?> {
     ctor public NavType.ParcelableArrayType(Class<D> type);
     method public D[]? get(android.os.Bundle bundle, String key);
     method public D[] parseValue(String value);
@@ -454,7 +472,7 @@
     property public String name;
   }
 
-  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+  public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]?> {
     ctor public NavType.SerializableArrayType(Class<D> type);
     method public D[]? get(android.os.Bundle bundle, String key);
     method public D[] parseValue(String value);
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index b5e5e8d..c513438 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -49,7 +49,7 @@
     api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2")
     implementation("androidx.core:core-ktx:1.1.0")
     implementation("androidx.collection:collection-ktx:1.1.0")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     implementation(libs.kotlinSerializationCore)
 
     api(libs.kotlinStdlib)
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
index 3c4a03b0..540df8d 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalSafeArgsApi::class)
+
 package androidx.navigation
 
 import androidx.annotation.IdRes
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
index 47f7464..6e3d59b 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
@@ -14,13 +14,18 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalSafeArgsApi::class)
+
 package androidx.navigation
 
 import androidx.annotation.IdRes
 import androidx.navigation.serialization.generateRoutePattern
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.KClass
+import kotlin.test.assertFailsWith
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.serializer
 import org.junit.Assert.fail
@@ -124,7 +129,9 @@
         val graph = provider.navigation(
             startDestination = route
         ) {
-            val builder = NavDestinationBuilder(provider[NoOpNavigator::class], TestClass::class)
+            val builder = NavDestinationBuilder(
+                provider[NoOpNavigator::class], TestClass::class, null
+            )
             addDestination(builder.build())
         }
         assertWithMessage("Destination route should be added to the graph")
@@ -145,7 +152,9 @@
         val graph = provider.navigation(
             startDestination = route
         ) {
-            val builder = NavDestinationBuilder(provider[NoOpNavigator::class], TestClass::class)
+            val builder = NavDestinationBuilder(
+                provider[NoOpNavigator::class], TestClass::class, null
+            )
             addDestination(builder.build())
         }
         assertWithMessage("Destination route should be added to the graph")
@@ -156,6 +165,82 @@
             .isTrue()
     }
 
+    @Test fun navigationStartDestinationKClass() {
+        @Serializable
+        class Graph(val arg: Int)
+
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = provider.navigation(
+            route = Graph::class,
+            startDestination = TestClass::class
+        ) {
+            navDestination(TestClass::class) { }
+        }
+
+        // assert graph info
+        val expectedGraphRoute = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationStartDestinationKClass.Graph/{arg}"
+        assertWithMessage("graph route should be set")
+            .that(graph.route)
+            .isEqualTo(expectedGraphRoute)
+        assertWithMessage("graph id should be set")
+            .that(graph.id)
+            .isEqualTo(serializer<Graph>().hashCode())
+
+        // assert start destination info
+        val expectedStartRoute = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationStartDestinationKClass.TestClass/{arg}"
+        assertWithMessage("Destination route should be added to the graph")
+            .that(expectedStartRoute in graph)
+            .isTrue()
+        assertWithMessage("startDestinationRoute should be set")
+            .that(graph.startDestinationRoute)
+            .isEqualTo(expectedStartRoute)
+        assertWithMessage("startDestinationId should be set")
+            .that(graph.startDestinationId)
+            .isEqualTo(serializer<TestClass>().hashCode())
+    }
+
+    @Test fun navigationStartDestinationObject() {
+        @Serializable
+        class Graph(val arg: Int)
+
+        @Serializable
+        class TestClass(val arg2: Int)
+
+        val graph = provider.navigation(
+            route = Graph::class,
+            startDestination = TestClass(1)
+        ) {
+            navDestination(TestClass::class) { }
+        }
+
+        // assert graph info
+        val expectedGraphRoute = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationStartDestinationObject.Graph/{arg}"
+        assertWithMessage("graph route should be set")
+            .that(graph.route)
+            .isEqualTo(expectedGraphRoute)
+        assertWithMessage("graph id should be set")
+            .that(graph.id)
+            .isEqualTo(serializer<Graph>().hashCode())
+
+        // assert start destination info
+        val expectedStartRoute = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationStartDestinationObject.TestClass/1"
+        assertWithMessage("Destination route should be added to the graph")
+            .that(expectedStartRoute in graph)
+            .isTrue()
+        assertWithMessage("startDestinationRoute should be set")
+            .that(graph.startDestinationRoute)
+            .isEqualTo(expectedStartRoute)
+        assertWithMessage("startDestinationId should be set")
+            .that(graph.startDestinationId)
+            .isEqualTo(serializer<TestClass>().hashCode())
+    }
+
     @Suppress("DEPRECATION")
     @Test(expected = IllegalStateException::class)
     fun navigationMissingStartDestination() {
@@ -173,6 +258,32 @@
         fail("NavGraph should throw IllegalStateException if no startDestinationRoute is set")
     }
 
+    @Test
+    fun navigationMissingStartDestinationKClass() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        assertFailsWith<IllegalStateException> {
+            provider.navigation(startDestination = TestClass::class) {
+                // nav destination must have been added via route from KClass
+                navDestination("route") { }
+            }
+        }
+    }
+
+    @Test
+    fun navigationMissingStartDestinationObject() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        assertFailsWith<IllegalStateException> {
+            provider.navigation(startDestination = TestClass(0)) {
+                // nav destination must have been added via route from KClass
+                navDestination("route") { }
+            }
+        }
+    }
+
     @Suppress("DEPRECATION")
     @Test
     fun navigationNested() {
@@ -197,6 +308,109 @@
             .that(DESTINATION_ROUTE in graph)
             .isTrue()
     }
+
+    @Test
+    fun navigationNestedKClass() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        @Serializable
+        class NestedGraph(val arg: Int)
+
+        val graph = provider.navigation(startDestination = NestedGraph::class) {
+            navigation(startDestination = TestClass::class, route = NestedGraph::class) {
+                navDestination(TestClass::class) {}
+            }
+        }
+        val nestedGraph = graph.findNode(
+            serializer<NestedGraph>().generateRoutePattern()
+        ) as NavGraph
+        // assert graph
+        val expectedNestedGraph = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedKClass.NestedGraph/{arg}"
+        assertThat(nestedGraph.route).isEqualTo(expectedNestedGraph)
+        assertThat(nestedGraph.id).isEqualTo(serializer<NestedGraph>().hashCode())
+        // assert nested startDestination
+        val expectedNestedStart = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedKClass.TestClass/{arg}"
+        assertThat(nestedGraph.startDestinationRoute).isEqualTo(expectedNestedStart)
+        assertThat(nestedGraph.startDestinationId).isEqualTo(serializer<TestClass>().hashCode())
+        assertWithMessage("Destination should be added to the nested graph")
+            .that(expectedNestedStart in nestedGraph)
+            .isTrue()
+    }
+
+    @Test
+    fun navigationNestedObject() {
+        @Serializable
+        class TestClass(val arg2: Int)
+
+        @Serializable
+        class NestedGraph(val arg: Int)
+
+        val graph = provider.navigation(startDestination = NestedGraph::class) {
+            navigation(startDestination = TestClass(15), route = NestedGraph::class) {
+                navDestination(TestClass::class) {}
+            }
+        }
+        val nestedGraph = graph.findNode(
+            serializer<NestedGraph>().generateRoutePattern()
+        ) as NavGraph
+
+        // assert graph
+        val expectedNestedGraph = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedObject.NestedGraph/{arg}"
+        assertThat(nestedGraph.route).isEqualTo(expectedNestedGraph)
+        assertThat(nestedGraph.id).isEqualTo(serializer<NestedGraph>().hashCode())
+
+        // assert nested StartDestination
+        val expectedNestedStart = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedObject.TestClass/15"
+        assertThat(nestedGraph.startDestinationRoute).isEqualTo(expectedNestedStart)
+        assertThat(nestedGraph.startDestinationId).isEqualTo(serializer<TestClass>().hashCode())
+        assertWithMessage("Destination should be added to the nested graph")
+            .that(expectedNestedStart in nestedGraph)
+            .isTrue()
+    }
+
+    @Test
+    fun navigationNestedObjectAndKClass() {
+        @Serializable
+        class TestClass(val arg2: Int)
+
+        @Serializable
+        class NestedGraph(val arg: Int)
+
+        val graph = provider.navigation(startDestination = NestedGraph(0)) {
+            navigation(startDestination = TestClass(15), route = NestedGraph::class) {
+                navDestination(TestClass::class) {}
+            }
+        }
+        val nestedGraph = graph.findNode(
+            serializer<NestedGraph>().generateRoutePattern()
+        ) as NavGraph
+
+        // assert graph
+        val expectedStart = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedObjectAndKClass.NestedGraph/0"
+        assertThat(graph.startDestinationRoute).isEqualTo(expectedStart)
+        assertThat(graph.startDestinationId).isEqualTo(serializer<NestedGraph>().hashCode())
+
+        // assert nested graph
+        val expectedNestedGraph = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedObjectAndKClass.NestedGraph/{arg}"
+        assertThat(nestedGraph.route).isEqualTo(expectedNestedGraph)
+        assertThat(nestedGraph.id).isEqualTo(serializer<NestedGraph>().hashCode())
+
+        // assert nested StartDestination
+        val expectedNestedStart = "androidx.navigation.NavGraphBuilderTest." +
+            "navigationNestedObjectAndKClass.TestClass/15"
+        assertThat(nestedGraph.startDestinationRoute).isEqualTo(expectedNestedStart)
+        assertThat(nestedGraph.startDestinationId).isEqualTo(serializer<TestClass>().hashCode())
+        assertWithMessage("Destination should be added to the nested graph")
+            .that(expectedNestedStart in nestedGraph)
+            .isTrue()
+    }
 }
 
 private const val DESTINATION_ID = 1
@@ -222,3 +436,12 @@
     route: String,
     builder: NavDestinationBuilder<NavDestination>.() -> Unit
 ) = destination(NavDestinationBuilder(provider[NoOpNavigator::class], route).apply(builder))
+
+/**
+ * Create a base NavDestination. Generally, only subtypes of NavDestination should be
+ * added to a NavGraph (hence why this is not in the common-ktx library)
+ */
+fun NavGraphBuilder.navDestination(
+    route: KClass<*>,
+    builder: NavDestinationBuilder<NavDestination>.() -> Unit
+) = destination(NavDestinationBuilder(provider[NoOpNavigator::class], route, null).apply(builder))
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
index 937c4c8..7982c3fc 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
@@ -14,11 +14,18 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalSafeArgsApi::class)
+
 package androidx.navigation
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.assertFailsWith
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.serializer
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
@@ -130,6 +137,152 @@
         val graph = NavGraph(navGraphNavigator)
         graph[DESTINATION_ID]
     }
+
+    @Test
+    fun graphSetStartDestinationKClass() {
+        @Serializable
+        @SerialName("route")
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            setStartDestination(15)
+            addDestination(NavDestinationBuilder(
+                navGraphNavigator, TestClass::class, null
+            ).build())
+        }
+        assertThat(graph.startDestinationId).isEqualTo(15)
+
+        graph.setStartDestination<TestClass>()
+        assertThat(graph.startDestinationRoute).isEqualTo("route/{arg}")
+        assertThat(graph.startDestinationId).isEqualTo(serializer<TestClass>().hashCode())
+    }
+
+    @Test
+    fun graphSetStartDestinationKClassMissingStartDestination() {
+        @Serializable
+        class TestClass
+
+        val graph = NavGraph(navGraphNavigator)
+
+        // start destination not added via KClass, cannot match
+        assertFailsWith<IllegalStateException> {
+            graph.setStartDestination<TestClass>()
+        }
+    }
+
+    @Test
+    fun graphSetStartDestinationObject() {
+        @Serializable
+        @SerialName("route")
+        class TestClass(val arg: Int, val arg2: String? = "test")
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            setStartDestination(15)
+            addDestination(NavDestinationBuilder(navGraphNavigator, TestClass::class, null).build())
+        }
+        assertThat(graph.startDestinationId).isEqualTo(15)
+
+        graph.setStartDestination(TestClass(20))
+        assertThat(graph.startDestinationRoute).isEqualTo("route/20?arg2=test")
+        assertThat(graph.startDestinationId).isEqualTo(serializer<TestClass>().hashCode())
+    }
+
+    @Test
+    fun graphSetStartDestinationObjectMissingStartDestination() {
+        @Serializable
+        class TestClass
+
+        val graph = NavGraph(navGraphNavigator)
+
+        // start destination not added via KClass, cannot match
+        assertFailsWith<IllegalStateException> {
+            graph.setStartDestination(TestClass())
+        }
+    }
+
+    @Test
+    fun findNodeKClass() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            addDestination(NavDestinationBuilder(navGraphNavigator, TestClass::class, null).build())
+        }
+
+        val dest = graph.findNode<TestClass>()
+        assertThat(dest).isNotNull()
+    }
+
+    @Test
+    fun getNodeKClass() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            addDestination(NavDestinationBuilder(
+                navGraphNavigator, TestClass::class, null
+            ).build())
+        }
+
+        assertThat(graph[TestClass::class]).isNotNull()
+    }
+
+    @Test
+    fun containNodeKClass() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            addDestination(NavDestinationBuilder(
+                navGraphNavigator, TestClass::class, null
+            ).build())
+        }
+
+        assertThat(graph.contains(TestClass::class)).isTrue()
+    }
+
+    @Test
+    fun findNodeObject() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            addDestination(NavDestinationBuilder(
+                navGraphNavigator, TestClass::class, null
+            ).build())
+        }
+
+        val dest = graph.findNode(TestClass(15))
+        assertThat(dest).isNotNull()
+    }
+
+    @Test
+    fun getNodeObject() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            addDestination(NavDestinationBuilder(
+                navGraphNavigator, TestClass::class, null
+            ).build())
+        }
+
+        assertThat(graph[TestClass(15)]).isNotNull()
+    }
+
+    @Test
+    fun containNodeObject() {
+        @Serializable
+        class TestClass(val arg: Int)
+
+        val graph = NavGraph(navGraphNavigator).apply {
+            addDestination(NavDestinationBuilder(
+                navGraphNavigator, TestClass::class, null
+            ).build())
+        }
+
+        assertThat(graph.contains(TestClass(15))).isTrue()
+    }
 }
 
 private const val DESTINATION_ID = 1
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt
index f008ff35..553e1a0 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt
@@ -31,7 +31,6 @@
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.serializer
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -47,10 +46,8 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass
 
-        val serializer = serializer<TestClass>()
-
         val clazz = TestClass()
-        assertThatRouteFilledFrom(clazz, serializer).isEqualTo(PATH_SERIAL_NAME)
+        assertThatRouteFilledFrom(clazz).isEqualTo(PATH_SERIAL_NAME)
     }
 
     @Test
@@ -59,12 +56,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String)
 
-        val serializer = serializer<TestClass>()
-
         val clazz = TestClass("test")
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(stringArgument("arg"))
         ).isEqualTo("$PATH_SERIAL_NAME/test")
     }
@@ -75,12 +69,10 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String, val arg2: Int)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test", 0)
 
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(stringArgument("arg"), intArgument("arg2"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/test/0"
@@ -93,11 +85,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String?)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test")
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(nullableStringArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/test"
@@ -110,11 +100,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String?)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(null)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(nullableStringArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/null"
@@ -130,7 +118,6 @@
         val clazz = TestClass("null")
         assertThatRouteFilledFrom(
             clazz,
-            serializer<TestClass>(),
             listOf(nullableStringArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/null"
@@ -143,11 +130,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String?, val arg2: Int?)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test", 0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(nullableStringArgument("arg"), nullableIntArgument("arg2"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/test/0"
@@ -160,11 +145,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String?, val arg2: Int?)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(null, null)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(nullableStringArgument("arg"), nullableIntArgument("arg2"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/null/null"
@@ -177,11 +160,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String = "test")
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(stringArgument("arg", true))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=test"
@@ -194,11 +175,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String = "test")
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("newTest")
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(stringArgument("arg", true))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=newTest"
@@ -211,11 +190,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String? = "test")
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(nullableStringArgument("arg", true))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=test"
@@ -228,11 +205,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String? = null)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(nullableStringArgument("arg", true))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=null"
@@ -248,7 +223,6 @@
         val clazz = TestClass("null")
         assertThatRouteFilledFrom(
             clazz,
-            serializer<TestClass>(),
             listOf(nullableStringArgument("arg", true))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=null"
@@ -261,11 +235,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String? = "test", val arg2: Int? = 0)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 nullableStringArgument("arg", true),
                 nullableIntArgument("arg2", true)
@@ -281,11 +253,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: String? = null, val arg2: Int? = null)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 nullableStringArgument("arg", true),
                 nullableIntArgument("arg2", true)
@@ -301,11 +271,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val pathArg: String, val queryArg: Int = 0)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test")
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 stringArgument("pathArg"),
                 intArgument("queryArg", true)
@@ -321,11 +289,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val queryArg: Int = 0, val pathArg: String)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(1, "test")
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 intArgument("queryArg", true),
                 stringArgument("pathArg")
@@ -341,11 +307,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val pathArg: String?, val queryArg: Int? = 0)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test", 1)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 nullableStringArgument("pathArg"),
                 nullableIntArgument("queryArg", true)
@@ -361,11 +325,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val array: IntArray)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(intArrayOf(0, 1, 2))
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArrayArgument("array"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?array=0&array=1&array=2"
@@ -378,11 +340,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val array: IntArray?)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(intArrayOf(0, 1, 2))
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArrayArgument("array"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?array=0&array=1&array=2"
@@ -395,12 +355,10 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val array: IntArray? = null)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
-            listOf(intArrayArgument("array"),)
+            listOf(intArrayArgument("array"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?array=null"
         )
@@ -412,11 +370,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val string: String, val array: IntArray)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test", intArrayOf(0, 1, 2))
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 stringArgument("string"),
                 intArrayArgument("array")
@@ -432,11 +388,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val array: IntArray, val arg: Int = 0)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(intArrayOf(0, 1, 2), 15)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(
                 intArrayArgument("array"),
                 intArgument("arg")
@@ -454,11 +408,9 @@
             constructor(arg2: Int) : this(arg2.toString())
         }
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(stringArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0"
@@ -467,11 +419,9 @@
 
     @Test
     fun withCompanionObject() {
-        val serializer = serializer<ClassWithCompanionObject>()
         val clazz = ClassWithCompanionObject(0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0"
@@ -480,11 +430,9 @@
 
     @Test
     fun withCompanionParameter() {
-        val serializer = serializer<ClassWithCompanionParam>()
         val clazz = ClassWithCompanionParam(0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0"
@@ -499,11 +447,9 @@
             fun testFun() { }
         }
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass("test")
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(stringArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/test"
@@ -530,11 +476,9 @@
             unknownDefaultValuePresent = false
         }
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(CustomType())
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(customArg)
         ).isEqualTo(
             "$PATH_SERIAL_NAME/customValue"
@@ -564,11 +508,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val custom: CustomType)
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(CustomType(NestedCustomType()))
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(customArg)
         ).isEqualTo(
             "$PATH_SERIAL_NAME/customValue[nestedCustomValue]"
@@ -596,11 +538,9 @@
             nullable = false
             unknownDefaultValuePresent = false
         }
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(0, CustomSerializerClass(1L))
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg"), customArg)
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0/customSerializerClass[1]"
@@ -615,14 +555,12 @@
             val noBackingField: Int
                 get() = 0
         }
-        val serializer = serializer<TestClass>()
         // only members with backing field should appear on route
         val clazz = TestClass()
         assertThatRouteFilledFrom(
-            clazz,
-            serializer
+            clazz
         ).isEqualTo(
-            "$PATH_SERIAL_NAME"
+            PATH_SERIAL_NAME
         )
     }
 
@@ -633,11 +571,9 @@
         class TestClass {
             val arg: Int = 0
         }
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=0"
@@ -651,11 +587,9 @@
         class TestClass {
             lateinit var arg: IntArray
         }
-        val serializer = serializer<TestClass>()
         val clazz = TestClass().also { it.arg = intArrayOf(0) }
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArrayArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME?arg=0"
@@ -669,7 +603,7 @@
 
         assertFailsWith<SerializationException> {
             // the class must be serializable
-            serializer<TestClass>().generateRouteWithArgs(TestClass(), emptyMap())
+            TestClass().generateRouteWithArgs(emptyMap<String, NavType<Any?>>())
         }
     }
 
@@ -682,11 +616,11 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass : TestAbstractClass()
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass()
-        assertThatRouteFilledFrom(clazz, serializer,
+        assertThatRouteFilledFrom(
+            clazz,
         ).isEqualTo(
-            "$PATH_SERIAL_NAME"
+            PATH_SERIAL_NAME
         )
     }
 
@@ -699,12 +633,10 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg2: Int) : TestAbstractClass(arg2)
 
-        val serializer = serializer<TestClass>()
         // args will be duplicated
         val clazz = TestClass(0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg"), intArgument("arg2"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0/0"
@@ -713,12 +645,10 @@
 
     @Test
     fun childClassOfSealed_withArgs() {
-        val serializer = serializer<SealedClass.TestClass>()
         // child class overrides parent variable so only child variable shows up in route pattern
         val clazz = SealedClass.TestClass(0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg2"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0"
@@ -731,11 +661,9 @@
         @SerialName(PATH_SERIAL_NAME)
         class TestClass(val arg: Int) : TestInterface
 
-        val serializer = serializer<TestClass>()
         val clazz = TestClass(0)
         assertThatRouteFilledFrom(
             clazz,
-            serializer,
             listOf(intArgument("arg"))
         ).isEqualTo(
             "$PATH_SERIAL_NAME/0"
@@ -744,30 +672,27 @@
 
     @Test
     fun routeFromObject() {
-        val serializer = serializer<TestObject>()
-        assertThatRouteFilledFrom(TestObject, serializer).isEqualTo(
-            "$PATH_SERIAL_NAME"
+        assertThatRouteFilledFrom(TestObject).isEqualTo(
+            PATH_SERIAL_NAME
         )
     }
 
     @Test
     fun routeFromObject_argsNotSerialized() {
-        val serializer = serializer<TestObjectWithArg>()
         // object variables are not serialized and does not show up on route
-        assertThatRouteFilledFrom(TestObjectWithArg, serializer).isEqualTo(
-            "$PATH_SERIAL_NAME"
+        assertThatRouteFilledFrom(TestObjectWithArg).isEqualTo(
+            PATH_SERIAL_NAME
         )
     }
 }
 
 private fun <T : Any> assertThatRouteFilledFrom(
     obj: T,
-    serializer: KSerializer<T>,
     customArgs: List<NamedNavArgument>? = null
 ): String {
     val typeMap = mutableMapOf<String, NavType<Any?>>()
     customArgs?.forEach { typeMap[it.name] = it.argument.type }
-    return serializer.generateRouteWithArgs(obj, typeMap)
+    return obj.generateRouteWithArgs(typeMap)
 }
 
 internal fun String.isEqualTo(other: String) {
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/ExperimentalSafeArgsApi.kt b/navigation/navigation-common/src/main/java/androidx/navigation/ExperimentalSafeArgsApi.kt
new file mode 100644
index 0000000..be7244c2
--- /dev/null
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/ExperimentalSafeArgsApi.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation
+
+/**
+ * Annotation indicating experimental API for new safe args component that enables navigating
+ * with type safety through serialization.
+ *
+ * The Safe Args API surface is still under development and  incomplete at the moment.
+ * Many specific features require multiple safe args APIs working
+ * together, so individual APIs may not currently work as intended. As such, use of
+ * APIs annotated with `ExperimentalSafeArgsAPI` requires opt-in
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FUNCTION)
+@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
+public annotation class ExperimentalSafeArgsApi
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt
index a8eb63a..2239fdc 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavArgument.kt
@@ -60,7 +60,10 @@
 
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public fun putDefaultValue(name: String, bundle: Bundle) {
-        if (isDefaultValuePresent) {
+        // even if there is defaultValuePresent, the defaultValue itself could be null as in the
+        // case of safe args where we know there is default value present but we are not able to
+        // read the actual default (serializer limitations), so the defaultValue is set to null.
+        if (isDefaultValuePresent && defaultValue != null) {
             type.put(bundle, name, defaultValue)
         }
     }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
index 2c4329e..0a21652 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
@@ -17,7 +17,6 @@
 package androidx.navigation
 
 import androidx.annotation.IdRes
-import androidx.annotation.RestrictTo
 import androidx.core.os.bundleOf
 import androidx.navigation.serialization.generateNavArguments
 import androidx.navigation.serialization.generateRoutePattern
@@ -90,22 +89,23 @@
      *
      * @return the newly constructed [NavDestination]
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @ExperimentalSafeArgsApi
     @OptIn(InternalSerializationApi::class)
     public constructor(
         navigator: Navigator<out D>,
-        route: KClass<*>,
-        typeMap: Map<KType, NavType<*>> = mapOf(),
+        @Suppress("OptionalBuilderConstructorArgument") route: KClass<*>?,
+        @Suppress("OptionalBuilderConstructorArgument")
+        typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>?,
     ) : this(
         navigator,
-        route.serializer().hashCode(),
-        route.serializer().generateRoutePattern(typeMap.ifEmpty { null })
+        route?.serializer()?.hashCode() ?: -1,
+        route?.serializer()?.generateRoutePattern(typeMap)
     ) {
-        route.serializer()
-            .generateNavArguments(typeMap)
-            .forEach {
+        route?.apply {
+            serializer().generateNavArguments(typeMap).forEach {
                 arguments[it.name] = it.argument
             }
+        }
     }
 
     /**
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
index 8883540..1db1e0d 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -25,7 +25,13 @@
 import androidx.collection.valueIterator
 import androidx.core.content.res.use
 import androidx.navigation.common.R
+import androidx.navigation.serialization.generateRouteWithArgs
 import java.lang.StringBuilder
+import kotlin.reflect.KClass
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.serializer
 
 /**
  * NavGraph is a collection of [NavDestination] nodes fetchable by ID.
@@ -178,6 +184,33 @@
         return if (!route.isNullOrBlank()) findNode(route, true) else null
     }
 
+    /**
+     * Finds a destination in the collection by route from [KClass]. This will recursively check the
+     * [parent][parent] of this navigation graph if node is not found in this navigation graph.
+     *
+     * @param T Route from a [KClass] to locate
+     * @return the node with route - the node must have been created with a route from [KClass]
+     */
+    @OptIn(InternalSerializationApi::class)
+    @ExperimentalSafeArgsApi
+    public inline fun <reified T> findNode(): NavDestination? {
+        return findNode(serializer<T>().hashCode())
+    }
+
+    /**
+     * Finds a destination in the collection by route from Object. This will recursively check the
+     * [parent][parent] of this navigation graph if node is not found in this navigation graph.
+     *
+     * @param route Route to locate
+     * @return the node with route - the node must have been created with a route from [KClass]
+     */
+    @OptIn(InternalSerializationApi::class)
+    @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
+    @ExperimentalSafeArgsApi
+    public fun <T> findNode(route: T?): NavDestination? {
+        return if (route != null) findNode(route!!::class.serializer().hashCode()) else null
+    }
+
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public fun findNode(@IdRes resId: Int, searchParents: Boolean): NavDestination? {
         val destination = nodes[resId]
@@ -329,6 +362,60 @@
     }
 
     /**
+     * Sets the starting destination for this NavGraph.
+     *
+     * This will override any previously set [startDestinationId]
+     *
+     * @param T The route of the destination as a [KClass] to be shown when navigating
+     * to this NavGraph.
+     */
+    @ExperimentalSafeArgsApi
+    public inline fun <reified T> setStartDestination() {
+        setStartDestination(serializer<T>()) { startDestination ->
+            startDestination.route!!
+        }
+    }
+
+    /**
+     * Sets the starting destination for this NavGraph.
+     *
+     * This will override any previously set [startDestinationId]
+     *
+     * @param startDestRoute The route of the destination as an object to be shown when navigating
+     * to this NavGraph.
+     */
+    @OptIn(InternalSerializationApi::class)
+    @ExperimentalSafeArgsApi
+    public fun <T : Any> setStartDestination(startDestRoute: T) {
+        setStartDestination(startDestRoute::class.serializer()) { startDestination ->
+            val args = startDestination.arguments.mapValues {
+                it.value.type
+            }
+            startDestRoute.generateRouteWithArgs(args)
+        }
+    }
+
+    // unfortunately needs to be public so reified setStartDestination can access this
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @OptIn(ExperimentalSerializationApi::class)
+    public fun <T> setStartDestination(
+        serializer: KSerializer<T>,
+        parseRoute: (NavDestination) -> String,
+    ) {
+        val id = serializer.hashCode()
+        val startDest = findNode(id)
+        checkNotNull(startDest) {
+            "Cannot find startDestination ${serializer.descriptor.serialName} from NavGraph. " +
+                "Ensure the starting NavDestination was added with route from KClass."
+        }
+        // when dest id is based on serializer, we expect the dest route to have been generated
+        // and set
+        startDestinationRoute = parseRoute(startDest)
+        // bypass startDestinationId setter so we don't set route back to null
+        this.startDestId = id
+    }
+
+    /**
      * The route for the starting destination for this NavGraph. When navigating to the
      * NavGraph, the destination represented by this route is the one the user will initially see.
      */
@@ -433,12 +520,45 @@
     findNode(route)
         ?: throw IllegalArgumentException("No destination for $route was found in $this")
 
+/**
+ * Returns the destination with `route` from [KClass].
+ *
+ * @throws IllegalArgumentException if no destination is found with that route.
+ */
+@Suppress("NOTHING_TO_INLINE")
+@ExperimentalSafeArgsApi
+
+public inline operator fun <reified T : Any> NavGraph.get(route: KClass<T>): NavDestination =
+    findNode<T>()
+        ?: throw IllegalArgumentException("No destination for $route was found in $this")
+
+/**
+ * Returns the destination with `route` from an Object.
+ *
+ * @throws IllegalArgumentException if no destination is found with that route.
+ */
+@Suppress("NOTHING_TO_INLINE")
+@ExperimentalSafeArgsApi
+public inline operator fun <T> NavGraph.get(route: T): NavDestination =
+    findNode(route)
+        ?: throw IllegalArgumentException("No destination for $route was found in $this")
+
 /** Returns `true` if a destination with `id` is found in this navigation graph. */
 public operator fun NavGraph.contains(@IdRes id: Int): Boolean = findNode(id) != null
 
 /** Returns `true` if a destination with `route` is found in this navigation graph. */
 public operator fun NavGraph.contains(route: String): Boolean = findNode(route) != null
 
+/** Returns `true` if a destination with `route` is found in this navigation graph. */
+@Suppress("UNUSED_PARAMETER")
+@ExperimentalSafeArgsApi
+public inline operator fun <reified T : Any> NavGraph.contains(route: KClass<T>): Boolean =
+    findNode<T>() != null
+
+/** Returns `true` if a destination with `route` is found in this navigation graph. */
+@ExperimentalSafeArgsApi
+public operator fun <T> NavGraph.contains(route: T): Boolean = findNode(route) != null
+
 /**
  * Adds a destination to this NavGraph. The destination must have an
  * [id][NavDestination.id] set.
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
index d69f5d2..9219ad7 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
@@ -17,6 +17,10 @@
 package androidx.navigation
 
 import androidx.annotation.IdRes
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.serializer
 
 /**
  * Construct a new [NavGraph]
@@ -58,6 +62,48 @@
     .build()
 
 /**
+ * Construct a new [NavGraph]
+ *
+ * @param startDestination the starting destination's route from a [KClass] for this NavGraph. The
+ * respective NavDestination must be added with route from a [KClass] in order to match.
+ * @param route the graph's unique route as a [KClass]
+ * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+ * if [route] uses custom NavTypes.
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed NavGraph
+ */
+@ExperimentalSafeArgsApi
+public inline fun NavigatorProvider.navigation(
+    startDestination: KClass<*>,
+    route: KClass<*>? = null,
+    typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = NavGraphBuilder(this, startDestination, route, typeMap).apply(builder)
+    .build()
+
+/**
+ * Construct a new [NavGraph]
+ *
+ * @param startDestination the starting destination's route from an Object for this NavGraph. The
+ * respective NavDestination must be added with route from a [KClass] in order to match.
+ * @param route the graph's unique route as a [KClass]
+ * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+ * if [route] uses custom NavTypes.
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed NavGraph
+ */
+@ExperimentalSafeArgsApi
+public inline fun NavigatorProvider.navigation(
+    startDestination: Any,
+    route: KClass<*>? = null,
+    typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = NavGraphBuilder(this, startDestination, route, typeMap).apply(builder)
+    .build()
+
+/**
  * Construct a nested [NavGraph]
  *
  * @param id the destination's unique id
@@ -96,6 +142,46 @@
 ): Unit = destination(NavGraphBuilder(provider, startDestination, route).apply(builder))
 
 /**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route from a [KClass] for this NavGraph. The
+ * respective NavDestination must be added with route from a [KClass] in order to match.
+ * @param route the graph's unique route from a [KClass]
+ * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+ * if [route] uses custom NavTypes.
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+@ExperimentalSafeArgsApi
+public inline fun NavGraphBuilder.navigation(
+    startDestination: KClass<*>,
+    route: KClass<*>,
+    typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): Unit = destination(NavGraphBuilder(provider, startDestination, route, typeMap).apply(builder))
+
+/**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route from an Object for this NavGraph. The
+ * respective NavDestination must be added with route from a [KClass] in order to match.
+ * @param route the graph's unique route from a [KClass]
+ * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+ * if [route] uses custom NavTypes.
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+@ExperimentalSafeArgsApi
+public inline fun NavGraphBuilder.navigation(
+    startDestination: Any,
+    route: KClass<*>,
+    typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): Unit = destination(NavGraphBuilder(provider, startDestination, route, typeMap).apply(builder))
+
+/**
  * DSL for constructing a new [NavGraph]
  */
 @NavDestinationDsl
@@ -106,6 +192,8 @@
     public val provider: NavigatorProvider
     @IdRes private var startDestinationId: Int = 0
     private var startDestinationRoute: String? = null
+    private var startDestinationClass: KClass<*>? = null
+    private var startDestinationObject: Any? = null
 
     /**
      * DSL for constructing a new [NavGraph]
@@ -151,6 +239,52 @@
         this.startDestinationRoute = startDestination
     }
 
+    /**
+     * DSL for constructing a new [NavGraph]
+     *
+     * @param provider navigator used to create the destination
+     * @param startDestination the starting destination's route as a [KClass] for this NavGraph. The
+     * respective NavDestination must be added with route from a [KClass] in order to match.
+     * @param route the graph's unique route as a [KClass]
+     * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+     * if [route] uses custom NavTypes.
+     *
+     * @return the newly created NavGraph
+     */
+    @ExperimentalSafeArgsApi
+    public constructor(
+        provider: NavigatorProvider,
+        startDestination: KClass<*>,
+        route: KClass<*>?,
+        typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>?
+    ) : super(provider[NavGraphNavigator::class], route, typeMap) {
+        this.provider = provider
+        this.startDestinationClass = startDestination
+    }
+
+    /**
+     * DSL for constructing a new [NavGraph]
+     *
+     * @param provider navigator used to create the destination
+     * @param startDestination the starting destination's route as an Object for this NavGraph. The
+     * respective NavDestination must be added with route from a [KClass] in order to match.
+     * @param route the graph's unique route as a [KClass]
+     * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+     * if [route] uses custom NavTypes.
+     *
+     * @return the newly created NavGraph
+     */
+    @ExperimentalSafeArgsApi
+    public constructor(
+        provider: NavigatorProvider,
+        startDestination: Any,
+        route: KClass<*>?,
+        typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>?
+    ) : super(provider[NavGraphNavigator::class], route, typeMap) {
+            this.provider = provider
+            this.startDestinationObject = startDestination
+        }
+
     private val destinations = mutableListOf<NavDestination>()
 
     /**
@@ -174,9 +308,11 @@
         destinations += destination
     }
 
+    @OptIn(InternalSerializationApi::class, ExperimentalSafeArgsApi::class)
     override fun build(): NavGraph = super.build().also { navGraph ->
         navGraph.addDestinations(destinations)
-        if (startDestinationId == 0 && startDestinationRoute == null) {
+        if (startDestinationId == 0 && startDestinationRoute == null &&
+            startDestinationClass == null && startDestinationObject == null) {
             if (route != null) {
                 throw IllegalStateException("You must set a start destination route")
             } else {
@@ -185,6 +321,10 @@
         }
         if (startDestinationRoute != null) {
             navGraph.setStartDestination(startDestinationRoute!!)
+        } else if (startDestinationClass != null) {
+            navGraph.setStartDestination(startDestinationClass!!.serializer()) { it.route!! }
+        } else if (startDestinationObject != null) {
+            navGraph.setStartDestination(startDestinationObject!!)
         } else {
             navGraph.setStartDestination(startDestinationId)
         }
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
index 2a9256d..719dab4 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteEncoder.kt
@@ -48,8 +48,9 @@
      * default implementation which further serializes nested non-primitive values). So we
      * delegate to the default entry by directly calling [super.encodeSerializableValue].
      */
-    fun encodeRouteWithArgs(value: T): String {
-        super.encodeSerializableValue(serializer, value)
+    @Suppress("UNCHECKED_CAST")
+    fun encodeRouteWithArgs(value: Any): String {
+        super.encodeSerializableValue(serializer, value as T)
         return builder.build()
     }
 
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt
index c99b628..b186ad0 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/serialization/RouteSerializer.kt
@@ -18,14 +18,17 @@
 
 package androidx.navigation.serialization
 
+import androidx.annotation.RestrictTo
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
 import kotlin.reflect.KType
+import kotlinx.serialization.InternalSerializationApi
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.PolymorphicSerializer
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.descriptors.capturedKClass
+import kotlinx.serialization.serializer
 
 /**
  * Generates a route pattern for use in Navigation functions such as [::navigate] from
@@ -126,11 +129,15 @@
  * The generated route pattern contains the path, path args, and query args.
  * See [RouteBuilder.Builder.computeParamType] for logic on how parameter type (path or query)
  * is computed.
+ *
+ * [T] as receiver to allow secondary constructors for nav builders (i.e. NavGraphBuilder)
+ * to take object <T : Any> as parameter
  */
-internal fun <T : Any> KSerializer<T>.generateRouteWithArgs(
-    destination: T,
+@OptIn(InternalSerializationApi::class)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public fun <T : Any> T.generateRouteWithArgs(
     typeMap: Map<String, NavType<Any?>>
-): String = RouteEncoder(this, typeMap).encodeRouteWithArgs(destination)
+): String = RouteEncoder(this::class.serializer(), typeMap).encodeRouteWithArgs(this)
 
 private fun <T> KSerializer<T>.assertNotAbstractClass(handler: () -> Unit) {
     // abstract class
diff --git a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
index ae36260..d2b50f3e 100644
--- a/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
+++ b/navigation/navigation-compose/samples/src/main/java/androidx/navigation/compose/samples/NavigationSamples.kt
@@ -42,12 +42,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Color.Companion.LightGray
 import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.navigation.NavController
 import androidx.navigation.NavHostController
 import androidx.navigation.NavType
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
index 20edc30..1e36d9d 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavBackStackEntryProviderTest.kt
@@ -19,12 +19,12 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.saveable.rememberSaveableStateHolder
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.testing.TestNavigatorState
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt
index 33c9de8..b2ee212 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostScreenShotTest.kt
@@ -107,7 +107,10 @@
 
         composeTestRule.waitForIdle()
         // the image should show third destination covering half the screen (covering half of
-        // second destination) as its slideIn animation starts at half screen
+        // second destination) as its slideIn animation starts at half screen.
+        // We need to advance by 2 frames to ensure we actually make to the expected half screen
+        // state.
+        composeTestRule.mainClock.advanceTimeByFrame()
         composeTestRule.mainClock.advanceTimeByFrame()
 
         composeTestRule.onNodeWithText(THIRD).onParent()
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
index bfbf4ee..32980f7 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
@@ -43,7 +43,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -54,6 +53,7 @@
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
index 4957694..2ca5a6f 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavBackStackEntryProvider.kt
@@ -19,10 +19,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.saveable.SaveableStateHolder
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavBackStackEntry
diff --git a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
index 1b41906..8993ac0 100644
--- a/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
+++ b/navigation/navigation-compose/src/main/java/androidx/navigation/compose/NavHost.kt
@@ -27,7 +27,6 @@
 import androidx.compose.animation.core.SeekableTransitionState
 import androidx.compose.animation.core.rememberTransition
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
@@ -44,7 +43,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavDestination
@@ -422,22 +421,30 @@
             }
         }
 
-        val transition = if (inPredictiveBack) {
-            val transitionState = remember(backStackEntry) {
-                // The state returned here cannot be nullable cause it produces the input of the
-                // transitionSpec passed into the AnimatedContent and that must match the non-nullable
-                // scope exposed by the transitions on the NavHost and composable APIs.
-                SeekableTransitionState(backStackEntry)
-            }
+        val transitionState = remember {
+            // The state returned here cannot be nullable cause it produces the input of the
+            // transitionSpec passed into the AnimatedContent and that must match the non-nullable
+            // scope exposed by the transitions on the NavHost and composable APIs.
+            SeekableTransitionState(backStackEntry)
+        }
+
+        if (inPredictiveBack) {
             LaunchedEffect(progress) {
                 val previousEntry = currentBackStack[currentBackStack.size - 2]
                 transitionState.seekTo(progress, previousEntry)
             }
-            rememberTransition(transitionState, label = "entry")
         } else {
-            updateTransition(backStackEntry, label = "entry")
+            LaunchedEffect(backStackEntry) {
+                // This ensures we don't animate after the back gesture is cancelled and we
+                // are already on the current state
+                if (transitionState.currentState != backStackEntry) {
+                    transitionState.animateTo(backStackEntry)
+                }
+            }
         }
 
+        val transition = rememberTransition(transitionState, label = "entry")
+
         transition.AnimatedContent(
             modifier,
             transitionSpec = {
diff --git a/navigation/navigation-runtime/api/current.txt b/navigation/navigation-runtime/api/current.txt
index 5de0d5e..ffc549c 100644
--- a/navigation/navigation-runtime/api/current.txt
+++ b/navigation/navigation-runtime/api/current.txt
@@ -91,12 +91,16 @@
   public class NavController {
     ctor public NavController(android.content.Context context);
     method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> boolean clearBackStack();
     method @MainThread public final boolean clearBackStack(@IdRes int destinationId);
     method @MainThread public final boolean clearBackStack(String route);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> boolean clearBackStack(T route);
     method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
     method @SuppressCompatibility @androidx.navigation.NavDeepLinkSaveStateControl public static final void enableDeepLinkSaveState(boolean saveState);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> androidx.navigation.NavBackStackEntry getBackStackEntry();
     method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
     method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public final <T> androidx.navigation.NavBackStackEntry getBackStackEntry(T route);
     method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
     method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
     method public androidx.navigation.NavDestination? getCurrentDestination();
@@ -124,12 +128,19 @@
     method @MainThread public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
     method @MainThread public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
     method @MainThread public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route, optional androidx.navigation.NavOptions? navOptions);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
     method @MainThread public boolean navigateUp();
     method @MainThread public boolean popBackStack();
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> boolean popBackStack(boolean inclusive, optional boolean saveState);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
     method @MainThread public final boolean popBackStack(String route, boolean inclusive);
     method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> boolean popBackStack(T route, boolean inclusive);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> boolean popBackStack(T route, boolean inclusive, optional boolean saveState);
     method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method @CallSuper public void restoreState(android.os.Bundle? navState);
     method @CallSuper public android.os.Bundle? saveState();
@@ -159,7 +170,9 @@
 
   public final class NavControllerKt {
     method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, Object startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, kotlin.reflect.KClass<?> startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavDeepLinkBuilder {
diff --git a/navigation/navigation-runtime/api/restricted_current.txt b/navigation/navigation-runtime/api/restricted_current.txt
index 5de0d5e..ffc549c 100644
--- a/navigation/navigation-runtime/api/restricted_current.txt
+++ b/navigation/navigation-runtime/api/restricted_current.txt
@@ -91,12 +91,16 @@
   public class NavController {
     ctor public NavController(android.content.Context context);
     method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> boolean clearBackStack();
     method @MainThread public final boolean clearBackStack(@IdRes int destinationId);
     method @MainThread public final boolean clearBackStack(String route);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> boolean clearBackStack(T route);
     method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
     method @SuppressCompatibility @androidx.navigation.NavDeepLinkSaveStateControl public static final void enableDeepLinkSaveState(boolean saveState);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> androidx.navigation.NavBackStackEntry getBackStackEntry();
     method public androidx.navigation.NavBackStackEntry getBackStackEntry(@IdRes int destinationId);
     method public final androidx.navigation.NavBackStackEntry getBackStackEntry(String route);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public final <T> androidx.navigation.NavBackStackEntry getBackStackEntry(T route);
     method public androidx.navigation.NavBackStackEntry? getCurrentBackStackEntry();
     method public final kotlinx.coroutines.flow.Flow<androidx.navigation.NavBackStackEntry> getCurrentBackStackEntryFlow();
     method public androidx.navigation.NavDestination? getCurrentDestination();
@@ -124,12 +128,19 @@
     method @MainThread public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions);
     method @MainThread public final void navigate(String route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
     method @MainThread public final void navigate(String route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route, optional androidx.navigation.NavOptions? navOptions);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route, optional androidx.navigation.NavOptions? navOptions, optional androidx.navigation.Navigator.Extras? navigatorExtras);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> void navigate(T route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> builder);
     method @MainThread public boolean navigateUp();
     method @MainThread public boolean popBackStack();
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public inline <reified T> boolean popBackStack(boolean inclusive, optional boolean saveState);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive);
     method @MainThread public boolean popBackStack(@IdRes int destinationId, boolean inclusive, boolean saveState);
     method @MainThread public final boolean popBackStack(String route, boolean inclusive);
     method @MainThread public final boolean popBackStack(String route, boolean inclusive, optional boolean saveState);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> boolean popBackStack(T route, boolean inclusive);
+    method @SuppressCompatibility @MainThread @androidx.navigation.ExperimentalSafeArgsApi public final <T> boolean popBackStack(T route, boolean inclusive, optional boolean saveState);
     method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener listener);
     method @CallSuper public void restoreState(android.os.Bundle? navState);
     method @CallSuper public android.os.Bundle? saveState();
@@ -159,7 +170,9 @@
 
   public final class NavControllerKt {
     method @Deprecated public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, optional @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, Object startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
     method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, String startDestination, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+    method @SuppressCompatibility @androidx.navigation.ExperimentalSafeArgsApi public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, kotlin.reflect.KClass<?> startDestination, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,androidx.navigation.NavType<?>>? typeMap, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
   }
 
   public final class NavDeepLinkBuilder {
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index d83b630..6e626be 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -28,6 +28,7 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("kotlin-android")
+    alias(libs.plugins.kotlinSerialization)
 }
 
 dependencies {
@@ -37,6 +38,7 @@
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
     api("androidx.annotation:annotation-experimental:1.4.0")
     implementation('androidx.collection:collection:1.1.0')
+    implementation(libs.kotlinSerializationCore)
 
     api(libs.kotlinStdlib)
     androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing"))
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index b95635f..2d2965c0 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalSafeArgsApi::class)
+
 package androidx.navigation
 
 import android.content.Context
@@ -53,6 +55,9 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlin.test.assertFailsWith
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.serializer
 import org.hamcrest.CoreMatchers.allOf
 import org.hamcrest.CoreMatchers.not
 import org.hamcrest.Matchers
@@ -200,12 +205,26 @@
             }
     }
 
+    @Serializable
+    class TestClass
+
+    @Serializable
+    class TestClassPathArg(val arg: Int)
+
+    @Serializable
+    class TestGraph
+
     companion object {
         private const val UNKNOWN_DESTINATION_ID = -1
         private const val TEST_ARG = "test"
         private const val TEST_ARG_VALUE = "value"
         private const val TEST_OVERRIDDEN_VALUE_ARG = "test_overridden_value"
         private const val TEST_OVERRIDDEN_VALUE_ARG_VALUE = "override"
+        private const val TEST_CLASS_ROUTE = "androidx.navigation.NavControllerRouteTest.TestClass"
+        private const val TEST_CLASS_PATH_ARG_ROUTE = "androidx.navigation." +
+            "NavControllerRouteTest.TestClassPathArg/{arg}"
+        private const val TEST_GRAPH_ROUTE = "androidx.navigation." +
+            "NavControllerRouteTest.TestGraph"
     }
 
     @UiThreadTest
@@ -235,6 +254,125 @@
 
     @UiThreadTest
     @Test
+    fun testStartDestinationKClass() {
+        @Serializable
+        @SerialName("test")
+        class TestClass
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = TestClass::class) {
+            test(route = TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("test")
+        assertThat(navController.currentDestination?.id).isEqualTo(
+            serializer<TestClass>().hashCode()
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationKClassWithArgs() {
+        @Serializable
+        @SerialName("test")
+        class TestClass(val arg: Int)
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = TestClass::class) {
+            test(route = TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("test/{arg}")
+        assertThat(navController.currentDestination?.id).isEqualTo(
+            serializer<TestClass>().hashCode()
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationObject() {
+        @Serializable
+        @SerialName("test")
+        class TestClass
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = TestClass()) {
+            test(route = TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("test")
+        assertThat(navController.currentDestination?.id).isEqualTo(
+            serializer<TestClass>().hashCode()
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationObjectWithPathArg() {
+        @Serializable
+        @SerialName("test")
+        class TestClass(val arg: Int)
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(
+            startDestination = TestClass(0)
+        ) {
+            test(route = TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo(
+            "test/{arg}"
+        )
+        assertThat(navController.currentDestination?.id).isEqualTo(
+            serializer<TestClass>().hashCode()
+        )
+        val arg = navController.currentBackStackEntry?.arguments?.getInt("arg")
+        assertThat(arg).isNotNull()
+        assertThat(arg).isEqualTo(0)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationObjectWithQueryArg() {
+        @Serializable
+        @SerialName("test")
+        class TestClass(val arg: Boolean = false)
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(
+            startDestination = TestClass(false)
+        ) {
+            test(route = TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo(
+            "test?arg={arg}"
+        )
+        assertThat(navController.currentDestination?.id).isEqualTo(
+            serializer<TestClass>().hashCode()
+        )
+        val arg = navController.currentBackStackEntry?.arguments?.getBoolean("arg")
+        assertThat(arg).isNotNull()
+        assertThat(arg).isEqualTo(false)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testStartDestinationObjectNoMatch() {
+        @Serializable
+        @SerialName("test")
+        class TestClass(val arg: Boolean = false)
+
+        val navController = createNavController()
+
+        // Even though route string matches, startDestinations are matched based on id which
+        // is based on serializer. So this won't match. StartDestination must be added via KClass
+        assertFailsWith<IllegalStateException> {
+            navController.graph = navController.createGraph(
+                startDestination = TestClass(false)
+            ) {
+                test(route = "test?arg={arg}")
+            }
+        }
+    }
+
+    @UiThreadTest
+    @Test
     fun testSetGraphTwice() {
         val navController = createNavController()
         navController.graph = nav_start_destination_route_graph
@@ -509,6 +647,195 @@
 
     @UiThreadTest
     @Test
+    fun testNavigateWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        navController.navigate(TestClass())
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectPathArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        navController.navigate(TestClassPathArg(0))
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+        assertThat(navController.currentBackStackEntry?.arguments?.getInt("arg"))
+            .isEqualTo(0)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectQueryArg() {
+        @Serializable
+        class TestClass(val arg: IntArray)
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        navController.navigate(TestClass(intArrayOf(0, 1, 2)))
+        assertThat(navController.currentDestination?.route).isEqualTo(
+            "androidx.navigation.NavControllerRouteTest." +
+                "testNavigateWithObjectQueryArg.TestClass?arg={arg}"
+        )
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+        assertThat(navController.currentBackStackEntry?.arguments?.getIntArray("arg"))
+            .isEqualTo(intArrayOf(0, 1, 2))
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectPathQueryArg() {
+        @Serializable
+        class TestClass(val arg: IntArray, val arg2: Boolean)
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        navController.navigate(TestClass(intArrayOf(0, 1, 2), true))
+        assertThat(navController.currentDestination?.route).isEqualTo(
+            "androidx.navigation.NavControllerRouteTest." +
+                "testNavigateWithObjectPathQueryArg.TestClass/{arg2}?arg={arg}"
+        )
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+        assertThat(navController.currentBackStackEntry?.arguments?.getIntArray("arg"))
+            .isEqualTo(intArrayOf(0, 1, 2))
+        assertThat(navController.currentBackStackEntry?.arguments?.getBoolean("arg2"))
+            .isEqualTo(true)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectInvalidObject() {
+        @Serializable
+        class WrongTestClass
+
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        assertFailsWith<IllegalArgumentException> {
+            navController.navigate(WrongTestClass())
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectWithPopUpTo() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        navController.navigate(TestClass(), navOptions {
+            popUpTo("start") { inclusive = true }
+        })
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectArgsSavedAndRestored() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        // first nav
+        val dest = TestClassPathArg(0)
+        navController.navigate(dest)
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+        assertThat(navController.currentBackStackEntry?.arguments?.getInt("arg"))
+            .isEqualTo(0)
+
+        // pop + save
+        val popped = navController.popBackStack(dest, true, true)
+        assertThat(popped).isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+
+        // second nav with restore
+        val dest2 = TestClassPathArg(1)
+        navController.navigate(dest2, navOptions { restoreState = true })
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+        assertThat(navController.currentBackStackEntry?.arguments?.getInt("arg"))
+            .isEqualTo(0)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testNavigateWithObjectArgsSavedNotRestored() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        // first nav
+        val dest = TestClassPathArg(0)
+        navController.navigate(dest)
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+        assertThat(navController.currentBackStackEntry?.arguments?.getInt("arg"))
+            .isEqualTo(0)
+
+        // pop + save
+        val popped = navController.popBackStack(dest, true, true)
+        assertThat(popped).isTrue()
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+
+        // second nav without restore
+        val dest2 = TestClassPathArg(1)
+        navController.navigate(dest2, navOptions { restoreState = false })
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+        assertThat(navController.currentBackStackEntry?.arguments?.getInt("arg"))
+            .isEqualTo(1)
+
+        // now we restore
+        navController.navigate(dest2, navOptions { restoreState = true })
+        assertThat(navController.currentDestination?.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+        assertThat(navController.currentBackStackEntry?.arguments?.getInt("arg"))
+            .isEqualTo(0)
+    }
+
+    @UiThreadTest
+    @Test
     fun testNavigateWithPopUpToFurthestRoute() {
         val navController = createNavController()
         navController.graph = nav_singleArg_graph
@@ -1013,6 +1340,119 @@
 
     @UiThreadTest
     @Test
+    fun testGetBackStackEntryWithKClass() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        navController.navigate(TEST_CLASS_ROUTE)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val entry = navController.getBackStackEntry<TestClass>()
+        assertThat(entry.destination.route).isEqualTo(TEST_CLASS_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithKClassArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val entry = navController.getBackStackEntry<TestClassPathArg>()
+        assertThat(entry.destination.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        navController.navigate(TEST_CLASS_ROUTE)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val entry = navController.getBackStackEntry(TestClass())
+        assertThat(entry.destination.route).isEqualTo(TEST_CLASS_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryGraphWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(
+            route = TestGraph::class,
+            startDestination = TestClass::class
+        ) {
+            test(TestClass::class)
+        }
+
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+        assertThat(navController.currentBackStackEntry?.destination?.route)
+            .isEqualTo(TEST_CLASS_ROUTE)
+
+        navController.navigate(TEST_GRAPH_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(4)
+
+        val entry = navController.getBackStackEntry(TestGraph())
+        assertThat(entry.destination.route).isEqualTo(TEST_GRAPH_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithObjectArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val entry = navController.getBackStackEntry(TestClassPathArg(0))
+        assertThat(entry.destination.route).isEqualTo(TEST_CLASS_PATH_ARG_ROUTE)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithObjectIncorrectArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(TestClassPathArg(1))
+        }
+    }
+
+    @UiThreadTest
+    @Test
     fun testPopBackStack() {
         val navController = createNavController()
         navController.graph = nav_singleArg_graph
@@ -1224,6 +1664,167 @@
 
     @UiThreadTest
     @Test
+    fun testPopBackStackWithKClass() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        // first nav
+        navController.navigate("start")
+
+        // second nav
+        navController.navigate(TEST_CLASS_ROUTE)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        val popped = navController.popBackStack<TestClass>(true)
+        assertThat(popped).isTrue()
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopBackStackGraphWithKClass() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(
+            route = TestGraph::class,
+            startDestination = TestClass::class
+        ) {
+            test(TestClass::class)
+        }
+
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+        assertThat(navController.currentBackStackEntry?.destination?.route)
+            .isEqualTo(TEST_CLASS_ROUTE)
+
+        navController.navigate(TEST_GRAPH_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(4)
+
+        val popped = navController.popBackStack<TestGraph>(true)
+        assertThat(popped).isTrue()
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopBackStackWithKClassArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        // first nav
+        navController.navigate("start")
+
+        // second nav
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        val popped = navController.popBackStack<TestClassPathArg>(true)
+        assertThat(popped).isTrue()
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopBackStackWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        // first nav
+        navController.navigate("start")
+
+        // second nav
+        navController.navigate(TEST_CLASS_ROUTE)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        val popped = navController.popBackStack(TestClass(), true)
+        assertThat(popped).isTrue()
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopBackStackGraphWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(
+            route = TestGraph::class,
+            startDestination = TestClass::class
+        ) {
+            test(TestClass::class)
+        }
+
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+        assertThat(navController.currentBackStackEntry?.destination?.route)
+            .isEqualTo(TEST_CLASS_ROUTE)
+
+        navController.navigate(TEST_GRAPH_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(4)
+
+        val popped = navController.popBackStack(TestGraph(), true)
+        assertThat(popped).isTrue()
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopBackStackWithObjectArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        // first nav
+        navController.navigate("start")
+
+        // second nav
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        val popped = navController.popBackStack(TestClassPathArg(0), true)
+        assertThat(popped).isTrue()
+        assertThat(navigator.backStack.size).isEqualTo(2)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPopBackStackWithObjectIncorrectArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        // first nav
+        navController.navigate("start")
+
+        // second nav
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        assertThat(navigator.backStack.size).isEqualTo(3)
+        // pop with a route that has a different arg value
+        val popped = navController.popBackStack(TestClassPathArg(1), true)
+        assertThat(popped).isFalse()
+        assertThat(navigator.backStack.size).isEqualTo(3)
+    }
+
+    @UiThreadTest
+    @Test
     fun testFindDestinationWithRoute() {
         val navController = createNavController()
         navController.graph = nav_singleArg_graph
@@ -1553,6 +2154,124 @@
 
     @UiThreadTest
     @Test
+    fun testClearBackStackWithKClass() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        navController.navigate(TEST_CLASS_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+
+        val popped = navController.popBackStack<TestClass>(true, true)
+        assertThat(popped).isTrue()
+
+        val cleared = navController.clearBackStack<TestClass>()
+        assertThat(cleared).isTrue()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testClearBackStackWithKClassPoppedWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        navController.navigate(TEST_CLASS_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+
+        val popped = navController.popBackStack(TestClass(), true, true)
+        assertThat(popped).isTrue()
+
+        val cleared = navController.clearBackStack<TestClass>()
+        assertThat(cleared).isTrue()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testClearBackStackWithKClassArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+
+        val popped = navController.popBackStack<TestClassPathArg>(true, true)
+        assertThat(popped).isTrue()
+
+        val cleared = navController.clearBackStack<TestClassPathArg>()
+        assertThat(cleared).isTrue()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testClearBackStackWithObject() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        navController.navigate(TEST_CLASS_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+
+        val popped = navController.popBackStack(TestClass(), true, true)
+        assertThat(popped).isTrue()
+
+        val cleared = navController.clearBackStack(TestClass())
+        assertThat(cleared).isTrue()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testClearBackStackWithObjectPoppedWithKClass() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClass::class)
+        }
+
+        navController.navigate(TEST_CLASS_ROUTE)
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+
+        val popped = navController.popBackStack<TestClass>(true, true)
+        assertThat(popped).isTrue()
+
+        val cleared = navController.clearBackStack(TestClass())
+        assertThat(cleared).isTrue()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testClearBackStackWithObjectIncorrectArg() {
+        val navController = createNavController()
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test(TestClassPathArg::class)
+        }
+
+        // second nav
+        navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+
+        val popped = navController.popBackStack(
+            TestClassPathArg(0), true, true
+        )
+        assertThat(popped).isTrue()
+
+        // pass in different arg value
+        val cleared = navController.clearBackStack(TestClassPathArg(1))
+        assertThat(cleared).isFalse()
+    }
+
+    @UiThreadTest
+    @Test
     fun testNavigateViaDeepLinkDefaultArgs() {
         val navController = createNavController()
         navController.graph = nav_simple_route_graph
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 31a937b..0bc6506 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -13,8 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+@file:SuppressLint("NullAnnotationGroup")
+
 package androidx.navigation
 
+import android.annotation.SuppressLint
 import android.app.Activity
 import android.content.Context
 import android.content.ContextWrapper
@@ -42,8 +46,11 @@
 import androidx.navigation.NavDestination.Companion.createRoute
 import androidx.navigation.NavDestination.Companion.hierarchy
 import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.serialization.generateRouteWithArgs
 import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.atomic.AtomicInteger
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -51,6 +58,8 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.serializer
 
 /**
  * NavController manages app navigation within a [NavHost].
@@ -522,6 +531,58 @@
     }
 
     /**
+     * Attempts to pop the controller's back stack back to a specific destination.
+     *
+     * @param T The topmost destination to retain with route from a [KClass]. The
+     * target NavDestination must have been created with route from [KClass].
+     * @param inclusive Whether the given destination should also be popped.
+     * @param saveState Whether the back stack and the state of all destinations between the
+     * current destination and [T] should be saved for later
+     * restoration via [NavOptions.Builder.setRestoreState] or the `restoreState` attribute using
+     * the same [T] (note: this matching ID is true whether
+     * [inclusive] is true or false).
+     *
+     * @return true if the stack was popped at least once and the user has been navigated to
+     * another destination, false otherwise
+     */
+    @MainThread
+    @JvmOverloads
+    @ExperimentalSafeArgsApi
+    public inline fun <reified T> popBackStack(
+        inclusive: Boolean,
+        saveState: Boolean = false
+    ): Boolean = popBackStack(serializer<T>().hashCode(), inclusive, saveState)
+
+    /**
+     * Attempts to pop the controller's back stack back to a specific destination.
+     *
+     * @param route The topmost destination to retain with route from an Object. The
+     * target NavDestination must have been created with route from [KClass].
+     * @param inclusive Whether the given destination should also be popped.
+     * @param saveState Whether the back stack and the state of all destinations between the
+     * current destination and the [route] should be saved for later
+     * restoration via [NavOptions.Builder.setRestoreState] or the `restoreState` attribute using
+     * the same [route] (note: this matching ID is true whether
+     * [inclusive] is true or false).
+     *
+     * @return true if the stack was popped at least once and the user has been navigated to
+     * another destination, false otherwise
+     */
+    @MainThread
+    @JvmOverloads
+    @ExperimentalSafeArgsApi
+    public fun <T : Any> popBackStack(
+        route: T,
+        inclusive: Boolean,
+        saveState: Boolean = false
+    ): Boolean {
+        // route contains arguments so we need to generate and pop with the populated route
+        // rather than popping based on route pattern
+        val finalRoute = generateRouteFilled(route, fromBackStack = true) ?: return false
+        return popBackStack(finalRoute, inclusive, saveState)
+    }
+
+    /**
      * Attempts to pop the controller's back stack back to a specific destination. This does
      * **not** handle calling [dispatchOnDestinationChanged]
      *
@@ -804,6 +865,44 @@
         return cleared && dispatchOnDestinationChanged()
     }
 
+    /**
+     * Clears any saved state associated with KClass [T] that was previously saved
+     * via [popBackStack] when using a `saveState` value of `true`.
+     *
+     * @param T The route from the [KClass] of the destination previously used with [popBackStack]
+     * with a `saveState`value of `true`. The target NavDestination must have been created
+     * with route from [KClass].
+     *
+     * @return true if the saved state of the stack associated with [T] was cleared.
+     */
+    @MainThread
+    @ExperimentalSafeArgsApi
+    public inline fun <reified T> clearBackStack(): Boolean =
+        clearBackStack(serializer<T>().hashCode())
+
+    /**
+     * Clears any saved state associated with KClass [T] that was previously saved
+     * via [popBackStack] when using a `saveState` value of `true`.
+     *
+     * @param route The route from an Object of the destination previously used with
+     * [popBackStack] with a `saveState`value of `true`. The target NavDestination must
+     * have been created with route from [KClass].
+     *
+     * @return true if the saved state of the stack associated with [T] was cleared.
+     */
+    @OptIn(InternalSerializationApi::class)
+    @MainThread
+    @ExperimentalSafeArgsApi
+    public fun <T : Any> clearBackStack(route: T): Boolean {
+        // route contains arguments so we need to generate and clear with the populated route
+        // rather than clearing based on route pattern
+        val finalRoute = generateRouteFilled(route) ?: return false
+        val cleared = clearBackStackInternal(finalRoute)
+        // Only return true if the clear succeeded and we've dispatched
+        // the change to a new destination
+        return cleared && dispatchOnDestinationChanged()
+    }
+
     @MainThread
     private fun clearBackStackInternal(@IdRes destinationId: Int): Boolean {
         navigatorState.values.forEach { state ->
@@ -1555,6 +1654,27 @@
         return currentGraph.findNode(route)
     }
 
+    // Finds destination and generates a route filled with args based on the serializable object.
+    // `fromBackStack` is for efficiency - if left false, the worst case scenario is searching
+    // from entire graph when we only care about backstack.
+    @OptIn(InternalSerializationApi::class)
+    private fun <T : Any> generateRouteFilled(route: T, fromBackStack: Boolean = false): String? {
+        val destination = if (fromBackStack) {
+            // limit search within backstack
+            backQueue.lastOrNull {
+                it.destination.id == route::class.serializer().hashCode()
+            }?.destination
+        } else {
+            // search from within root graph
+            findDestination(route::class.serializer().hashCode())
+        }
+        if (destination == null) return null
+        return route.generateRouteWithArgs(
+            // get argument typeMap
+            destination.arguments.mapValues { it.value.type }
+        )
+    }
+
     /**
      * Navigate to a destination from the current navigation graph. This supports both navigating
      * via an [action][NavDestination.getAction] and directly navigating to a destination.
@@ -2238,6 +2358,56 @@
     }
 
     /**
+     * Navigate to a route from an Object in the current NavGraph. If an invalid route is given, an
+     * [IllegalArgumentException] will be thrown.
+     *
+     * The target NavDestination must have been created with route from a [KClass]
+     *
+     * If given [NavOptions] pass in [NavOptions.restoreState] `true`, any args passed here as part
+     * of the route will be overridden by the restored args.
+     *
+     * @param route route from an Object for the destination
+     * @param builder DSL for constructing a new [NavOptions]
+     *
+     * @throws IllegalArgumentException if the given route is invalid
+     */
+    @MainThread
+    @ExperimentalSafeArgsApi
+    public fun <T : Any> navigate(route: T, builder: NavOptionsBuilder.() -> Unit) {
+        navigate(route, navOptions(builder))
+    }
+
+    /**
+     * Navigate to a route from an Object in the current NavGraph. If an invalid route is given, an
+     * [IllegalArgumentException] will be thrown.
+     *
+     * The target NavDestination must have been created with route from a [KClass]
+     *
+     * If given [NavOptions] pass in [NavOptions.restoreState] `true`, any args passed here as part
+     * of the route will be overridden by the restored args.
+     *
+     * @param route route from an Object for the destination
+     * @param navOptions special options for this navigation operation
+     * @param navigatorExtras extras to pass to the [Navigator]
+     *
+     * @throws IllegalArgumentException if the given route is invalid
+     */
+    @MainThread
+    @JvmOverloads
+    @ExperimentalSafeArgsApi
+    public fun <T : Any> navigate(
+        route: T,
+        navOptions: NavOptions? = null,
+        navigatorExtras: Navigator.Extras? = null
+    ) {
+        val finalRoute = generateRouteFilled(route)
+        navigate(
+            NavDeepLinkRequest.Builder.fromUri(createRoute(finalRoute).toUri()).build(), navOptions,
+            navigatorExtras
+        )
+    }
+
+    /**
      * Create a deep link to a destination within this NavController.
      *
      * @return a [NavDeepLinkBuilder] suitable for constructing a deep link
@@ -2482,6 +2652,44 @@
     }
 
     /**
+     * Gets the topmost [NavBackStackEntry] for a route from [KClass].
+     *
+     * This is always safe to use with [the current destination][currentDestination] or
+     * [its parent][NavDestination.parent] or grandparent navigation graphs as these
+     * destinations are guaranteed to be on the back stack.
+     *
+     * @param T route from the [KClass] of a destination that exists on the back stack. The
+     * target NavBackStackEntry's [NavDestination] must have been created with route from [KClass].
+     * @throws IllegalArgumentException if the destination is not on the back stack
+     */
+    @ExperimentalSafeArgsApi
+    public inline fun <reified T : Any> getBackStackEntry(): NavBackStackEntry =
+        getBackStackEntry(serializer<T>().hashCode())
+
+    /**
+     * Gets the topmost [NavBackStackEntry] for a route from an Object.
+     *
+     * This is always safe to use with [the current destination][currentDestination] or
+     * [its parent][NavDestination.parent] or grandparent navigation graphs as these
+     * destinations are guaranteed to be on the back stack.
+     *
+     * @param route route from an Object of a destination that exists on the back stack. The
+     * target NavBackStackEntry's [NavDestination] must have been created with route from [KClass].
+     * @throws IllegalArgumentException if the destination is not on the back stack
+     */
+    @ExperimentalSafeArgsApi
+    public fun <T : Any> getBackStackEntry(route: T): NavBackStackEntry {
+        // route contains arguments so we need to generate the populated route
+        // rather than getting entry based on route pattern
+        val finalRoute = generateRouteFilled(route, fromBackStack = true)
+        requireNotNull(finalRoute) {
+            "No destination with route $finalRoute is on the NavController's back stack. The " +
+                "current destination is $currentDestination"
+        }
+        return getBackStackEntry(finalRoute)
+    }
+
+    /**
      * The topmost [NavBackStackEntry].
      *
      * @return the topmost entry on the back stack or null if the back stack is empty
@@ -2602,3 +2810,39 @@
     route: String? = null,
     builder: NavGraphBuilder.() -> Unit
 ): NavGraph = navigatorProvider.navigation(startDestination, route, builder)
+
+/**
+ * Construct a new [NavGraph]
+ *
+ * @param startDestination the starting destination's route from a [KClass] for this NavGraph. The
+ * respective NavDestination must be added as a [KClass] in order to match.
+ * @param route the graph's unique route from a [KClass]
+ * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+ * if [route] uses custom NavTypes.
+ * @param builder the builder used to construct the graph
+ */
+@ExperimentalSafeArgsApi
+public inline fun NavController.createGraph(
+    startDestination: KClass<*>,
+    route: KClass<*>? = null,
+    typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = navigatorProvider.navigation(startDestination, route, typeMap, builder)
+
+/**
+ * Construct a new [NavGraph]
+ *
+ * @param startDestination the starting destination's route from an Object for this NavGraph. The
+ * respective NavDestination must be added as a [KClass] in order to match.
+ * @param route the graph's unique route from a [KClass]
+ * @param typeMap A mapping of KType to custom NavType<*> in the [route]. Only necessary
+ * if [route] uses custom NavTypes.
+ * @param builder the builder used to construct the graph
+ */
+@ExperimentalSafeArgsApi
+public inline fun NavController.createGraph(
+    startDestination: Any,
+    route: KClass<*>? = null,
+    typeMap: Map<KType, @JvmSuppressWildcards NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = navigatorProvider.navigation(startDestination, route, typeMap, builder)
diff --git a/paging/paging-common-ktx/api/3.3.0-beta01.txt b/paging/paging-common-ktx/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-common-ktx/api/3.3.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-common-ktx/api/restricted_3.3.0-beta01.txt b/paging/paging-common-ktx/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-common-ktx/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-common/api/3.3.0-beta01.txt b/paging/paging-common/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..e372b42
--- /dev/null
+++ b/paging/paging-common/api/3.3.0-beta01.txt
@@ -0,0 +1,609 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    method public boolean hasError();
+    method public boolean isIdle();
+    property public final androidx.paging.LoadState append;
+    property public final boolean hasError;
+    property public final boolean isIdle;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public final class CombinedLoadStatesKt {
+    method public static suspend Object? awaitNotLoading(kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates>, kotlin.coroutines.Continuation<androidx.paging.CombinedLoadStates?>);
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalPagingApi {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements androidx.paging.PagingSourceFactory<Key,Value> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T?> {
+    ctor public ItemSnapshotList(@IntRange(from=0L) int placeholdersBefore, @IntRange(from=0L) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public boolean hasError();
+    method public boolean isIdle();
+    property public final androidx.paging.LoadState append;
+    property public final boolean hasError;
+    property public final boolean isIdle;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  @Deprecated public interface Logger {
+    method @Deprecated public boolean isLoggable(int level);
+    method @Deprecated public void log(int level, String message, optional Throwable? tr);
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property @Deprecated public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property @Deprecated public abstract boolean isDetached;
+    property @Deprecated public boolean isImmutable;
+    property @Deprecated public abstract Object? lastKey;
+    property @Deprecated public final int loadedCount;
+    property @Deprecated public final int positionOffset;
+    property @Deprecated public int size;
+    field @Deprecated public static final androidx.paging.PagedList.Companion Companion;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Companion {
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1L) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2L) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1L) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0L) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+    method @kotlin.jvm.JvmSynthetic public static androidx.paging.PagedList.Config Config(int pageSize, optional int prefetchDistance, optional boolean enablePlaceholders, optional int initialLoadSizeHint, optional int maxSize);
+  }
+
+  public final class PagedListKt {
+    method @Deprecated @kotlin.jvm.JvmSynthetic public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional Key? initialKey);
+  }
+
+  public final class Pager<Key, Value> {
+    ctor @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1L) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1L) int initialLoadSize, optional @IntRange(from=2L) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1L) int initialLoadSize, optional @IntRange(from=2L) int maxSize, optional int jumpThreshold);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates);
+    method public static <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates);
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates);
+    method public <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates);
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+  }
+
+  public abstract sealed class PagingDataEvent<T> {
+  }
+
+  public static final class PagingDataEvent.Append<T> extends androidx.paging.PagingDataEvent<T> {
+    method public java.util.List<T> getInserted();
+    method public int getNewPlaceholdersAfter();
+    method public int getOldPlaceholdersAfter();
+    method public int getStartIndex();
+    property public final java.util.List<T> inserted;
+    property public final int newPlaceholdersAfter;
+    property public final int oldPlaceholdersAfter;
+    property public final int startIndex;
+  }
+
+  public static final class PagingDataEvent.DropAppend<T> extends androidx.paging.PagingDataEvent<T> {
+    method public int getDropCount();
+    method public int getNewPlaceholdersAfter();
+    method public int getOldPlaceholdersAfter();
+    method public int getStartIndex();
+    property public final int dropCount;
+    property public final int newPlaceholdersAfter;
+    property public final int oldPlaceholdersAfter;
+    property public final int startIndex;
+  }
+
+  public static final class PagingDataEvent.DropPrepend<T> extends androidx.paging.PagingDataEvent<T> {
+    method public int getDropCount();
+    method public int getNewPlaceholdersBefore();
+    method public int getOldPlaceholdersBefore();
+    property public final int dropCount;
+    property public final int newPlaceholdersBefore;
+    property public final int oldPlaceholdersBefore;
+  }
+
+  public static final class PagingDataEvent.Prepend<T> extends androidx.paging.PagingDataEvent<T> {
+    method public java.util.List<T> getInserted();
+    method public int getNewPlaceholdersBefore();
+    method public int getOldPlaceholdersBefore();
+    property public final java.util.List<T> inserted;
+    property public final int newPlaceholdersBefore;
+    property public final int oldPlaceholdersBefore;
+  }
+
+  public static final class PagingDataEvent.Refresh<T> extends androidx.paging.PagingDataEvent<T> {
+    method public androidx.paging.PlaceholderPaddedList<T> getNewList();
+    method public androidx.paging.PlaceholderPaddedList<T> getPreviousList();
+    property public final androidx.paging.PlaceholderPaddedList<T> newList;
+    property public final androidx.paging.PlaceholderPaddedList<T> previousList;
+  }
+
+  public abstract class PagingDataPresenter<T> {
+    ctor public PagingDataPresenter(optional kotlin.coroutines.CoroutineContext mainContext, optional androidx.paging.PagingData<T>? cachedPagingData);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final suspend Object? collectFrom(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method @MainThread public final operator T? get(@IntRange(from=0L) int index);
+    method public final kotlinx.coroutines.flow.StateFlow<androidx.paging.CombinedLoadStates?> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public final int getSize();
+    method @MainThread public final T? peek(@IntRange(from=0L) int index);
+    method public abstract suspend Object? presentPagingDataEvent(androidx.paging.PagingDataEvent<T> event, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    property public final kotlinx.coroutines.flow.StateFlow<androidx.paging.CombinedLoadStates?> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+    property public final int size;
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Iterable<? extends R>>,?> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, kotlin.jvm.functions.Function3<? super T?,? super T?,? super kotlin.coroutines.Continuation<? super R?>,?> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> implements java.lang.Iterable<Value> kotlin.jvm.internal.markers.KMappedMarker {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=androidx.paging.PagingSource.LoadResult.Page.COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=androidx.paging.PagingSource.LoadResult.Page.COUNT_UNDEFINED.toLong()) int itemsAfter);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, @IntRange(from=-2147483648L) int itemsBefore, @IntRange(from=-2147483648L) int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    method public java.util.Iterator<Value> iterator();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public fun interface PagingSourceFactory<Key, Value> extends kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    method public operator androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0L) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  public interface PlaceholderPaddedList<T> {
+    method public int getDataCount();
+    method public T getItem(int index);
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public abstract int dataCount;
+    property public abstract int placeholdersAfter;
+    property public abstract int placeholdersBefore;
+    property public abstract int size;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+    field @Deprecated public static final androidx.paging.PositionalDataSource.Companion Companion;
+  }
+
+  @Deprecated public static final class PositionalDataSource.Companion {
+    method @Deprecated public int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class RemoteMediator<Key, Value> {
+    ctor public RemoteMediator();
+    method public suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public abstract suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+  }
+
+  public enum RemoteMediator.InitializeAction {
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction LAUNCH_INITIAL_REFRESH;
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction SKIP_INITIAL_REFRESH;
+  }
+
+  public abstract static sealed class RemoteMediator.MediatorResult {
+  }
+
+  public static final class RemoteMediator.MediatorResult.Error extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Error(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class RemoteMediator.MediatorResult.Success extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Success(boolean endOfPaginationReached);
+    method public boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
diff --git a/paging/paging-common/api/current.txt b/paging/paging-common/api/current.txt
index b356f96..e372b42 100644
--- a/paging/paging-common/api/current.txt
+++ b/paging/paging-common/api/current.txt
@@ -98,7 +98,7 @@
     field @Deprecated public final int requestedLoadSize;
   }
 
-  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T?> {
     ctor public ItemSnapshotList(@IntRange(from=0L) int placeholdersBefore, @IntRange(from=0L) int placeholdersAfter, java.util.List<? extends T> items);
     method public T? get(int index);
     method public java.util.List<T> getItems();
diff --git a/paging/paging-common/api/res-3.3.0-beta01.txt b/paging/paging-common/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-common/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-common/api/restricted_3.3.0-beta01.txt b/paging/paging-common/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..e372b42
--- /dev/null
+++ b/paging/paging-common/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,609 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    method public boolean hasError();
+    method public boolean isIdle();
+    property public final androidx.paging.LoadState append;
+    property public final boolean hasError;
+    property public final boolean isIdle;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public final class CombinedLoadStatesKt {
+    method public static suspend Object? awaitNotLoading(kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates>, kotlin.coroutines.Continuation<androidx.paging.CombinedLoadStates?>);
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @kotlin.jvm.JvmSynthetic public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalPagingApi {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements androidx.paging.PagingSourceFactory<Key,Value> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T?> {
+    ctor public ItemSnapshotList(@IntRange(from=0L) int placeholdersBefore, @IntRange(from=0L) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public boolean hasError();
+    method public boolean isIdle();
+    property public final androidx.paging.LoadState append;
+    property public final boolean hasError;
+    property public final boolean isIdle;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  @Deprecated public interface Logger {
+    method @Deprecated public boolean isLoggable(int level);
+    method @Deprecated public void log(int level, String message, optional Throwable? tr);
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property @Deprecated public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property @Deprecated public abstract boolean isDetached;
+    property @Deprecated public boolean isImmutable;
+    property @Deprecated public abstract Object? lastKey;
+    property @Deprecated public final int loadedCount;
+    property @Deprecated public final int positionOffset;
+    property @Deprecated public int size;
+    field @Deprecated public static final androidx.paging.PagedList.Companion Companion;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Companion {
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1L) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2L) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1L) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0L) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+    method @kotlin.jvm.JvmSynthetic public static androidx.paging.PagedList.Config Config(int pageSize, optional int prefetchDistance, optional boolean enablePlaceholders, optional int initialLoadSizeHint, optional int maxSize);
+  }
+
+  public final class PagedListKt {
+    method @Deprecated @kotlin.jvm.JvmSynthetic public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional Key? initialKey);
+  }
+
+  public final class Pager<Key, Value> {
+    ctor @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1L) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1L) int initialLoadSize, optional @IntRange(from=2L) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0L) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1L) int initialLoadSize, optional @IntRange(from=2L) int maxSize, optional int jumpThreshold);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates);
+    method public static <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates);
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates);
+    method public <T> androidx.paging.PagingData<T> empty(androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates);
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data, androidx.paging.LoadStates sourceLoadStates, optional androidx.paging.LoadStates? mediatorLoadStates);
+  }
+
+  public abstract sealed class PagingDataEvent<T> {
+  }
+
+  public static final class PagingDataEvent.Append<T> extends androidx.paging.PagingDataEvent<T> {
+    method public java.util.List<T> getInserted();
+    method public int getNewPlaceholdersAfter();
+    method public int getOldPlaceholdersAfter();
+    method public int getStartIndex();
+    property public final java.util.List<T> inserted;
+    property public final int newPlaceholdersAfter;
+    property public final int oldPlaceholdersAfter;
+    property public final int startIndex;
+  }
+
+  public static final class PagingDataEvent.DropAppend<T> extends androidx.paging.PagingDataEvent<T> {
+    method public int getDropCount();
+    method public int getNewPlaceholdersAfter();
+    method public int getOldPlaceholdersAfter();
+    method public int getStartIndex();
+    property public final int dropCount;
+    property public final int newPlaceholdersAfter;
+    property public final int oldPlaceholdersAfter;
+    property public final int startIndex;
+  }
+
+  public static final class PagingDataEvent.DropPrepend<T> extends androidx.paging.PagingDataEvent<T> {
+    method public int getDropCount();
+    method public int getNewPlaceholdersBefore();
+    method public int getOldPlaceholdersBefore();
+    property public final int dropCount;
+    property public final int newPlaceholdersBefore;
+    property public final int oldPlaceholdersBefore;
+  }
+
+  public static final class PagingDataEvent.Prepend<T> extends androidx.paging.PagingDataEvent<T> {
+    method public java.util.List<T> getInserted();
+    method public int getNewPlaceholdersBefore();
+    method public int getOldPlaceholdersBefore();
+    property public final java.util.List<T> inserted;
+    property public final int newPlaceholdersBefore;
+    property public final int oldPlaceholdersBefore;
+  }
+
+  public static final class PagingDataEvent.Refresh<T> extends androidx.paging.PagingDataEvent<T> {
+    method public androidx.paging.PlaceholderPaddedList<T> getNewList();
+    method public androidx.paging.PlaceholderPaddedList<T> getPreviousList();
+    property public final androidx.paging.PlaceholderPaddedList<T> newList;
+    property public final androidx.paging.PlaceholderPaddedList<T> previousList;
+  }
+
+  public abstract class PagingDataPresenter<T> {
+    ctor public PagingDataPresenter(optional kotlin.coroutines.CoroutineContext mainContext, optional androidx.paging.PagingData<T>? cachedPagingData);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final suspend Object? collectFrom(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method @MainThread public final operator T? get(@IntRange(from=0L) int index);
+    method public final kotlinx.coroutines.flow.StateFlow<androidx.paging.CombinedLoadStates?> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method public final int getSize();
+    method @MainThread public final T? peek(@IntRange(from=0L) int index);
+    method public abstract suspend Object? presentPagingDataEvent(androidx.paging.PagingDataEvent<T> event, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    property public final kotlinx.coroutines.flow.StateFlow<androidx.paging.CombinedLoadStates?> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+    property public final int size;
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Iterable<? extends R>>,?> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, kotlin.jvm.functions.Function3<? super T?,? super T?,? super kotlin.coroutines.Continuation<? super R?>,?> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends R?> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+    method @CheckResult @kotlin.jvm.JvmSynthetic public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> implements java.lang.Iterable<Value> kotlin.jvm.internal.markers.KMappedMarker {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=androidx.paging.PagingSource.LoadResult.Page.COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=androidx.paging.PagingSource.LoadResult.Page.COUNT_UNDEFINED.toLong()) int itemsAfter);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, @IntRange(from=-2147483648L) int itemsBefore, @IntRange(from=-2147483648L) int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    method public java.util.Iterator<Value> iterator();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public fun interface PagingSourceFactory<Key, Value> extends kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    method public operator androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0L) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  public interface PlaceholderPaddedList<T> {
+    method public int getDataCount();
+    method public T getItem(int index);
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public abstract int dataCount;
+    property public abstract int placeholdersAfter;
+    property public abstract int placeholdersBefore;
+    property public abstract int size;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+    field @Deprecated public static final androidx.paging.PositionalDataSource.Companion Companion;
+  }
+
+  @Deprecated public static final class PositionalDataSource.Companion {
+    method @Deprecated public int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class RemoteMediator<Key, Value> {
+    ctor public RemoteMediator();
+    method public suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public abstract suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+  }
+
+  public enum RemoteMediator.InitializeAction {
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction LAUNCH_INITIAL_REFRESH;
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction SKIP_INITIAL_REFRESH;
+  }
+
+  public abstract static sealed class RemoteMediator.MediatorResult {
+  }
+
+  public static final class RemoteMediator.MediatorResult.Error extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Error(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class RemoteMediator.MediatorResult.Success extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Success(boolean endOfPaginationReached);
+    method public boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
diff --git a/paging/paging-common/api/restricted_current.txt b/paging/paging-common/api/restricted_current.txt
index b356f96..e372b42 100644
--- a/paging/paging-common/api/restricted_current.txt
+++ b/paging/paging-common/api/restricted_current.txt
@@ -98,7 +98,7 @@
     field @Deprecated public final int requestedLoadSize;
   }
 
-  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T?> {
     ctor public ItemSnapshotList(@IntRange(from=0L) int placeholdersBefore, @IntRange(from=0L) int placeholdersAfter, java.util.List<? extends T> items);
     method public T? get(int index);
     method public java.util.List<T> getItems();
diff --git a/paging/paging-compose/api/3.3.0-beta01.txt b/paging/paging-compose/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..cebed3d
--- /dev/null
+++ b/paging/paging-compose/api/3.3.0-beta01.txt
@@ -0,0 +1,27 @@
+// Signature format: 4.0
+package androidx.paging.compose {
+
+  public final class LazyFoundationExtensionsKt {
+    method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object?> itemContentType(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? contentType);
+    method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemKey(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? key);
+  }
+
+  public final class LazyPagingItems<T> {
+    method public operator T? get(int index);
+    method public int getItemCount();
+    method public androidx.paging.ItemSnapshotList<T> getItemSnapshotList();
+    method public androidx.paging.CombinedLoadStates getLoadState();
+    method public T? peek(int index);
+    method public void refresh();
+    method public void retry();
+    property public final int itemCount;
+    property public final androidx.paging.ItemSnapshotList<T> itemSnapshotList;
+    property public final androidx.paging.CombinedLoadStates loadState;
+  }
+
+  public final class LazyPagingItemsKt {
+    method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, optional kotlin.coroutines.CoroutineContext context);
+  }
+
+}
+
diff --git a/paging/paging-compose/api/res-3.3.0-beta01.txt b/paging/paging-compose/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-compose/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-compose/api/restricted_3.3.0-beta01.txt b/paging/paging-compose/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..cebed3d
--- /dev/null
+++ b/paging/paging-compose/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,27 @@
+// Signature format: 4.0
+package androidx.paging.compose {
+
+  public final class LazyFoundationExtensionsKt {
+    method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object?> itemContentType(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? contentType);
+    method public static <T> kotlin.jvm.functions.Function1<java.lang.Integer,java.lang.Object> itemKey(androidx.paging.compose.LazyPagingItems<T>, optional kotlin.jvm.functions.Function1<T,?>? key);
+  }
+
+  public final class LazyPagingItems<T> {
+    method public operator T? get(int index);
+    method public int getItemCount();
+    method public androidx.paging.ItemSnapshotList<T> getItemSnapshotList();
+    method public androidx.paging.CombinedLoadStates getLoadState();
+    method public T? peek(int index);
+    method public void refresh();
+    method public void retry();
+    property public final int itemCount;
+    property public final androidx.paging.ItemSnapshotList<T> itemSnapshotList;
+    property public final androidx.paging.CombinedLoadStates loadState;
+  }
+
+  public final class LazyPagingItemsKt {
+    method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, optional kotlin.coroutines.CoroutineContext context);
+  }
+
+}
+
diff --git a/paging/paging-guava/api/3.3.0-beta01.txt b/paging/paging-guava/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..c5b2656
--- /dev/null
+++ b/paging/paging-guava/api/3.3.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class ListenableFutureRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public ListenableFutureRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.InitializeAction> initializeFuture();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.MediatorResult> loadFuture(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R?> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/paging-guava/api/res-3.3.0-beta01.txt b/paging/paging-guava/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-guava/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-guava/api/restricted_3.3.0-beta01.txt b/paging/paging-guava/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..c5b2656
--- /dev/null
+++ b/paging/paging-guava/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,37 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class ListenableFutureRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public ListenableFutureRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.InitializeAction> initializeFuture();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.MediatorResult> loadFuture(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R?> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/paging-runtime-ktx/api/3.3.0-beta01.txt b/paging/paging-runtime-ktx/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/3.3.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-runtime-ktx/api/res-3.3.0-beta01.txt b/paging/paging-runtime-ktx/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-runtime-ktx/api/restricted_3.3.0-beta01.txt b/paging/paging-runtime-ktx/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-runtime-ktx/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-runtime/api/3.3.0-beta01.txt b/paging/paging-runtime/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..2392a1c
--- /dev/null
+++ b/paging/paging-runtime/api/3.3.0-beta01.txt
@@ -0,0 +1,140 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>?,? super androidx.paging.PagedList<T>?,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>?,? super androidx.paging.PagedList<T>?,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property @Deprecated public androidx.paging.PagedList<T>? currentList;
+    property @Deprecated public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher, optional kotlin.coroutines.CoroutineContext workerDispatcher);
+    ctor @Deprecated public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor @Deprecated public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method @MainThread public T? getItem(@IntRange(from=0L) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method @MainThread public T? peek(@IntRange(from=0L) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public final void setLoadState(androidx.paging.LoadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property @Deprecated public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher, optional kotlin.coroutines.CoroutineContext workerDispatcher);
+    ctor @Deprecated public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor @Deprecated public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method @MainThread protected final T? getItem(@IntRange(from=0L) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method @MainThread public final T? peek(@IntRange(from=0L) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/paging-runtime/api/res-3.3.0-beta01.txt b/paging/paging-runtime/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-runtime/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-runtime/api/restricted_3.3.0-beta01.txt b/paging/paging-runtime/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..2392a1c
--- /dev/null
+++ b/paging/paging-runtime/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,140 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>?,? super androidx.paging.PagedList<T>?,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>?,? super androidx.paging.PagedList<T>?,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property @Deprecated public androidx.paging.PagedList<T>? currentList;
+    property @Deprecated public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher, optional kotlin.coroutines.CoroutineContext workerDispatcher);
+    ctor @Deprecated public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor @Deprecated public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method @MainThread public T? getItem(@IntRange(from=0L) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method @MainThread public T? peek(@IntRange(from=0L) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public final void setLoadState(androidx.paging.LoadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property @Deprecated public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlin.coroutines.CoroutineContext mainDispatcher, optional kotlin.coroutines.CoroutineContext workerDispatcher);
+    ctor @Deprecated public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor @Deprecated public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void addOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method @MainThread protected final T? getItem(@IntRange(from=0L) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final kotlinx.coroutines.flow.Flow<kotlin.Unit> getOnPagesUpdatedFlow();
+    method @MainThread public final T? peek(@IntRange(from=0L) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void removeOnPagesUpdatedListener(kotlin.jvm.functions.Function0<kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+    property public final kotlinx.coroutines.flow.Flow<kotlin.Unit> onPagesUpdatedFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/paging-rxjava2-ktx/api/3.3.0-beta01.txt b/paging/paging-rxjava2-ktx/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/3.3.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-rxjava2-ktx/api/res-3.3.0-beta01.txt b/paging/paging-rxjava2-ktx/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-rxjava2-ktx/api/restricted_3.3.0-beta01.txt b/paging/paging-rxjava2-ktx/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/paging-rxjava2-ktx/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/paging-rxjava2/api/3.3.0-beta01.txt b/paging/paging-rxjava2/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..e0f5936
--- /dev/null
+++ b/paging/paging-rxjava2/api/3.3.0-beta01.txt
@@ -0,0 +1,58 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public io.reactivex.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+    method public abstract io.reactivex.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/paging-rxjava2/api/res-3.3.0-beta01.txt b/paging/paging-rxjava2/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-rxjava2/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-rxjava2/api/restricted_3.3.0-beta01.txt b/paging/paging-rxjava2/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..e0f5936
--- /dev/null
+++ b/paging/paging-rxjava2/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,58 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public io.reactivex.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+    method public abstract io.reactivex.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/paging-rxjava3/api/3.3.0-beta01.txt b/paging/paging-rxjava3/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..e39687f
--- /dev/null
+++ b/paging/paging-rxjava3/api/3.3.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/paging-rxjava3/api/res-3.3.0-beta01.txt b/paging/paging-rxjava3/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-rxjava3/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-rxjava3/api/restricted_3.3.0-beta01.txt b/paging/paging-rxjava3/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..e39687f
--- /dev/null
+++ b/paging/paging-rxjava3/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,54 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @SuppressCompatibility @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T?,? super T?,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.rxjava3.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.rxjava3.core.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler, optional io.reactivex.rxjava3.core.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.rxjava3.core.Scheduler? fetchScheduler, optional io.reactivex.rxjava3.core.Scheduler? notifyScheduler);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @SuppressCompatibility @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction>);
+    method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult>);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/paging-testing/api/3.3.0-beta01.txt b/paging/paging-testing/api/3.3.0-beta01.txt
new file mode 100644
index 0000000..026cb0a
--- /dev/null
+++ b/paging/paging-testing/api/3.3.0-beta01.txt
@@ -0,0 +1,43 @@
+// Signature format: 4.0
+package androidx.paging.testing {
+
+  @VisibleForTesting public enum ErrorRecovery {
+    enum_constant public static final androidx.paging.testing.ErrorRecovery RETRY;
+    enum_constant public static final androidx.paging.testing.ErrorRecovery RETURN_CURRENT_SNAPSHOT;
+    enum_constant public static final androidx.paging.testing.ErrorRecovery THROW;
+  }
+
+  @VisibleForTesting public fun interface LoadErrorHandler {
+    method public androidx.paging.testing.ErrorRecovery onError(androidx.paging.CombinedLoadStates combinedLoadStates);
+  }
+
+  public final class PagerFlowSnapshotKt {
+    method @VisibleForTesting public static suspend <Value> Object? asSnapshot(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>>, optional androidx.paging.testing.LoadErrorHandler onError, optional kotlin.jvm.functions.Function2<? super androidx.paging.testing.SnapshotLoader<Value>,? super kotlin.coroutines.Continuation<kotlin.Unit>,?> loadOperations, kotlin.coroutines.Continuation<java.util.List<Value>>);
+  }
+
+  @VisibleForTesting public final class SnapshotLoader<Value> {
+    method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? flingTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? scrollTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
+  }
+
+  public final class StaticListPagingSourceFactoryKt {
+    method @VisibleForTesting public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(java.util.List<? extends Value>);
+    method @VisibleForTesting public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+  }
+
+  @VisibleForTesting public final class TestPager<Key, Value> {
+    ctor public TestPager(androidx.paging.PagingConfig config, androidx.paging.PagingSource<Key,Value> pagingSource);
+    method public suspend Object? append(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult.Page<Key,Value>?>);
+    method public suspend Object? getPages(kotlin.coroutines.Continuation<java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
+    method public suspend Object? getPagingState(int anchorPosition, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
+    method public suspend Object? getPagingState(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> anchorPositionLookup, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
+    method public suspend Object? prepend(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
+    method public suspend Object? refresh(optional Key? initialKey, kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
+  }
+
+}
+
diff --git a/paging/paging-testing/api/res-3.3.0-beta01.txt b/paging/paging-testing/api/res-3.3.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-testing/api/res-3.3.0-beta01.txt
diff --git a/paging/paging-testing/api/restricted_3.3.0-beta01.txt b/paging/paging-testing/api/restricted_3.3.0-beta01.txt
new file mode 100644
index 0000000..026cb0a
--- /dev/null
+++ b/paging/paging-testing/api/restricted_3.3.0-beta01.txt
@@ -0,0 +1,43 @@
+// Signature format: 4.0
+package androidx.paging.testing {
+
+  @VisibleForTesting public enum ErrorRecovery {
+    enum_constant public static final androidx.paging.testing.ErrorRecovery RETRY;
+    enum_constant public static final androidx.paging.testing.ErrorRecovery RETURN_CURRENT_SNAPSHOT;
+    enum_constant public static final androidx.paging.testing.ErrorRecovery THROW;
+  }
+
+  @VisibleForTesting public fun interface LoadErrorHandler {
+    method public androidx.paging.testing.ErrorRecovery onError(androidx.paging.CombinedLoadStates combinedLoadStates);
+  }
+
+  public final class PagerFlowSnapshotKt {
+    method @VisibleForTesting public static suspend <Value> Object? asSnapshot(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>>, optional androidx.paging.testing.LoadErrorHandler onError, optional kotlin.jvm.functions.Function2<? super androidx.paging.testing.SnapshotLoader<Value>,? super kotlin.coroutines.Continuation<kotlin.Unit>,?> loadOperations, kotlin.coroutines.Continuation<java.util.List<Value>>);
+  }
+
+  @VisibleForTesting public final class SnapshotLoader<Value> {
+    method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? flingTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? scrollTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
+  }
+
+  public final class StaticListPagingSourceFactoryKt {
+    method @VisibleForTesting public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(java.util.List<? extends Value>);
+    method @VisibleForTesting public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+  }
+
+  @VisibleForTesting public final class TestPager<Key, Value> {
+    ctor public TestPager(androidx.paging.PagingConfig config, androidx.paging.PagingSource<Key,Value> pagingSource);
+    method public suspend Object? append(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult.Page<Key,Value>?>);
+    method public suspend Object? getPages(kotlin.coroutines.Continuation<java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
+    method public suspend Object? getPagingState(int anchorPosition, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
+    method public suspend Object? getPagingState(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> anchorPositionLookup, kotlin.coroutines.Continuation<androidx.paging.PagingState<Key,Value>>);
+    method public suspend Object? prepend(kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>?>);
+    method public suspend Object? refresh(optional Key? initialKey, kotlin.coroutines.Continuation<androidx.paging.PagingSource.LoadResult<Key,Value>>);
+  }
+
+}
+
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index cd7c06c..72a0cf7 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
 # Disable docs
 androidx.enableDocumentation=false
 androidx.playground.snapshotBuildId=11349412
-androidx.playground.metalavaBuildId=11569610
+androidx.playground.metalavaBuildId=11595754
 androidx.studio.type=playground
\ No newline at end of file
diff --git a/preference/preference/api/current.txt b/preference/preference/api/current.txt
index 3bbbb79..863b7b2 100644
--- a/preference/preference/api/current.txt
+++ b/preference/preference/api/current.txt
@@ -58,7 +58,7 @@
     method public void onBindEditText(android.widget.EditText);
   }
 
-  public static final class EditTextPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.EditTextPreference> {
+  public static final class EditTextPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.EditTextPreference!> {
     method public static androidx.preference.EditTextPreference.SimpleSummaryProvider getInstance();
     method public CharSequence? provideSummary(androidx.preference.EditTextPreference);
   }
@@ -94,7 +94,7 @@
     method public void setValueIndex(int);
   }
 
-  public static final class ListPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.ListPreference> {
+  public static final class ListPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.ListPreference!> {
     method public static androidx.preference.ListPreference.SimpleSummaryProvider getInstance();
     method public CharSequence? provideSummary(androidx.preference.ListPreference);
   }
@@ -142,7 +142,7 @@
     method public void onDialogClosed(boolean);
   }
 
-  public class Preference implements java.lang.Comparable<androidx.preference.Preference> {
+  public class Preference implements java.lang.Comparable<androidx.preference.Preference!> {
     ctor public Preference(android.content.Context);
     ctor public Preference(android.content.Context, android.util.AttributeSet?);
     ctor public Preference(android.content.Context, android.util.AttributeSet?, int);
diff --git a/preference/preference/api/restricted_current.txt b/preference/preference/api/restricted_current.txt
index 133fb62..c7a186b 100644
--- a/preference/preference/api/restricted_current.txt
+++ b/preference/preference/api/restricted_current.txt
@@ -58,7 +58,7 @@
     method public void onBindEditText(android.widget.EditText);
   }
 
-  public static final class EditTextPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.EditTextPreference> {
+  public static final class EditTextPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.EditTextPreference!> {
     method public static androidx.preference.EditTextPreference.SimpleSummaryProvider getInstance();
     method public CharSequence? provideSummary(androidx.preference.EditTextPreference);
   }
@@ -94,7 +94,7 @@
     method public void setValueIndex(int);
   }
 
-  public static final class ListPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.ListPreference> {
+  public static final class ListPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.ListPreference!> {
     method public static androidx.preference.ListPreference.SimpleSummaryProvider getInstance();
     method public CharSequence? provideSummary(androidx.preference.ListPreference);
   }
@@ -142,7 +142,7 @@
     method public void onDialogClosed(boolean);
   }
 
-  public class Preference implements java.lang.Comparable<androidx.preference.Preference> {
+  public class Preference implements java.lang.Comparable<androidx.preference.Preference!> {
     ctor public Preference(android.content.Context);
     ctor public Preference(android.content.Context, android.util.AttributeSet?);
     ctor public Preference(android.content.Context, android.util.AttributeSet?, int);
@@ -426,7 +426,7 @@
     method public int getPreferenceAdapterPosition(String);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PreferenceGroupAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.preference.PreferenceViewHolder> implements androidx.preference.PreferenceGroup.PreferencePositionCallback {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PreferenceGroupAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.preference.PreferenceViewHolder!> implements androidx.preference.PreferenceGroup.PreferencePositionCallback {
     ctor public PreferenceGroupAdapter(androidx.preference.PreferenceGroup);
     method public androidx.preference.Preference? getItem(int);
     method public int getItemCount();
diff --git a/preference/preference/res/values-sk/strings.xml b/preference/preference/res/values-sk/strings.xml
index 5b535d01..6c7dff3 100644
--- a/preference/preference/res/values-sk/strings.xml
+++ b/preference/preference/res/values-sk/strings.xml
@@ -5,7 +5,7 @@
     <string name="v7_preference_off" msgid="3140233346420563315">"Vypnuté"</string>
     <string name="expand_button_title" msgid="2427401033573778270">"Rozšírené"</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">"KopírovaÅ¥"</string>
+    <string name="copy" msgid="6083905920877235314">"SkopírovaÅ¥"</string>
     <string name="preference_copied" msgid="6685851473431805375">"Položka <xliff:g id="SUMMARY">%1$s</xliff:g> bola skopírovaná do schránky."</string>
     <string name="not_set" msgid="6573031135582639649">"Nenastavené"</string>
 </resources>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/current.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/current.xml
index 8c9a6a1..a85bbad 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/current.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/current.xml
@@ -14,6 +14,6 @@
   limitations under the License.
   -->
 <compat-config>
-    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.current.CompatProvider</compat-entrypoint>
+    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.CompatProvider</compat-entrypoint>
     <dex-path>test-sdks/current/classes.dex</dex-path>
 </compat-config>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/currentWithResources.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/currentWithResources.xml
index cb99939..8e07975 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/currentWithResources.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/currentWithResources.xml
@@ -14,7 +14,7 @@
   limitations under the License.
   -->
 <compat-config>
-    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.current.CompatProvider</compat-entrypoint>
+    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.CompatProvider</compat-entrypoint>
     <dex-path>test-sdks/current/classes.dex</dex-path>
     <dex-path>RuntimeEnabledSdks/RPackage.dex</dex-path>
     <java-resources-root-path>RuntimeEnabledSdks/javaresources</java-resources-root-path>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v1.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v1.xml
index 2bf4d37..38e0309 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v1.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v1.xml
@@ -14,6 +14,6 @@
   limitations under the License.
   -->
 <compat-config>
-    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.v1.CompatProvider</compat-entrypoint>
+    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.CompatProvider</compat-entrypoint>
     <dex-path>test-sdks/v1/classes.dex</dex-path>
 </compat-config>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v2.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v2.xml
index ed4f3707..2caa00d 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v2.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v2.xml
@@ -14,6 +14,6 @@
   limitations under the License.
   -->
 <compat-config>
-    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.v2.CompatProvider</compat-entrypoint>
+    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.CompatProvider</compat-entrypoint>
     <dex-path>test-sdks/v2/classes.dex</dex-path>
 </compat-config>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v4.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v4.xml
index ea3c856..d1e82e8 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v4.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v4.xml
@@ -14,6 +14,6 @@
   limitations under the License.
   -->
 <compat-config>
-    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.v4.CompatProvider</compat-entrypoint>
+    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.CompatProvider</compat-entrypoint>
     <dex-path>test-sdks/v4/classes.dex</dex-path>
 </compat-config>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml
index 8d21c64..b8a7dc1 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/assets/RuntimeEnabledSdks/v5.xml
@@ -14,6 +14,6 @@
   limitations under the License.
   -->
 <compat-config>
-    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.v5.CompatProvider</compat-entrypoint>
+    <compat-entrypoint>androidx.privacysandbox.sdkruntime.testsdk.CompatProvider</compat-entrypoint>
     <dex-path>test-sdks/v5/classes.dex</dex-path>
 </compat-config>
\ No newline at end of file
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
index 3eecfe3..9ddb9e0 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompatTest.kt
@@ -24,7 +24,10 @@
 import androidx.privacysandbox.sdkruntime.client.loader.asTestSdk
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.LOAD_SDK_SDK_DEFINED_ERROR
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkInfo
+import androidx.privacysandbox.sdkruntime.core.internal.ClientApiVersion
+import androidx.privacysandbox.sdkruntime.core.internal.ClientFeature
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -215,12 +218,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val managerCompat = SdkSandboxManagerCompat.from(context)
 
-        val localSdk = runBlocking {
-            managerCompat.loadSdk(
-                TestSdkConfigs.forSdkName("v4").packageName,
-                Bundle()
-            )
-        }
+        val localSdk = managerCompat.loadSdkWithFeature(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val handler = CatchingSdkActivityHandler()
 
@@ -242,12 +240,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val managerCompat = SdkSandboxManagerCompat.from(context)
 
-        val localSdk = runBlocking {
-            managerCompat.loadSdk(
-                TestSdkConfigs.CURRENT.packageName,
-                Bundle()
-            )
-        }
+        val localSdk = managerCompat.loadSdkWithFeature(ClientFeature.LOAD_SDK)
 
         val anotherLocalSdkName = TestSdkConfigs.forSdkName("v5").packageName
         val anotherLocalSdk = localSdk.asTestSdk().loadSdk(
@@ -268,12 +261,7 @@
         val context = ApplicationProvider.getApplicationContext<Context>()
         val managerCompat = SdkSandboxManagerCompat.from(context)
 
-        val localSdk = runBlocking {
-            managerCompat.loadSdk(
-                TestSdkConfigs.CURRENT.packageName,
-                Bundle()
-            )
-        }
+        val localSdk = managerCompat.loadSdkWithFeature(ClientFeature.LOAD_SDK)
 
         val params = Bundle()
         params.putBoolean("needFail", true)
@@ -325,4 +313,23 @@
             anotherLocalSdk.getInterface(),
         )
     }
+
+    private fun SdkSandboxManagerCompat.loadSdkWithFeature(
+        clientFeature: ClientFeature
+    ): SandboxedSdkCompat {
+        return if (clientFeature.availableFrom <= ClientApiVersion.CURRENT_VERSION) {
+            runBlocking {
+                loadSdk(
+                    TestSdkConfigs.CURRENT.packageName,
+                    Bundle()
+                )
+            }
+        } else {
+            loadLocalSdkWithVersionOverride(
+                TestSdkConfigs.CURRENT.packageName,
+                Bundle(),
+                ClientApiVersion.FUTURE_VERSION.apiLevel
+            )
+        }
+    }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/config/LocalSdkConfigsHolderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/config/LocalSdkConfigsHolderTest.kt
index e054456..de29be9 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/config/LocalSdkConfigsHolderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/config/LocalSdkConfigsHolderTest.kt
@@ -51,7 +51,7 @@
             packageName = "androidx.privacysandbox.sdkruntime.testsdk.current",
             versionMajor = 42,
             dexPaths = listOf("test-sdks/current/classes.dex"),
-            entryPoint = "androidx.privacysandbox.sdkruntime.testsdk.current.CompatProvider",
+            entryPoint = "androidx.privacysandbox.sdkruntime.testsdk.CompatProvider",
         )
 
         assertThat(result).isEqualTo(expectedConfig)
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt
index a2f9bbe..77f6564 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/LocalSdkProviderTest.kt
@@ -31,10 +31,11 @@
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkInfo
-import androidx.privacysandbox.sdkruntime.core.Versions
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.LoadSdkCallback
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import androidx.privacysandbox.sdkruntime.core.internal.ClientApiVersion
+import androidx.privacysandbox.sdkruntime.core.internal.ClientFeature
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
@@ -53,8 +54,10 @@
 @LargeTest
 @RunWith(Parameterized::class)
 internal class LocalSdkProviderTest(
+    @Suppress("unused") private val label: String, // Added to test names by JUnit
     private val sdkName: String,
-    private val sdkVersion: Int
+    private val originalSdkVersion: Int,
+    private val forcedSdkVersion: Int,
 ) {
 
     private lateinit var controller: TestStubController
@@ -65,9 +68,15 @@
         val sdkConfig = TestSdkConfigs.forSdkName(sdkName)
 
         controller = TestStubController()
-        loadedSdk = loadTestSdkFromAssets(sdkConfig, controller)
+
+        val overrideVersionHandshake = if (originalSdkVersion != forcedSdkVersion) {
+            VersionHandshake(forcedSdkVersion)
+        } else {
+            null
+        }
+        loadedSdk = loadTestSdkFromAssets(sdkConfig, controller, overrideVersionHandshake)
         assertThat(loadedSdk.extractApiVersion())
-            .isEqualTo(sdkVersion)
+            .isEqualTo(originalSdkVersion)
     }
 
     @Test
@@ -119,10 +128,7 @@
 
     @Test
     fun getSandboxedSdks_delegateToSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 2",
-            sdkVersion >= 2
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_SANDBOX_CONTROLLER)
 
         val expectedResult = SandboxedSdkCompat(
             sdkInterface = Binder(),
@@ -147,10 +153,7 @@
 
     @Test
     fun getAppOwnedSdkSandboxInterfaces_delegateToSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 4",
-            sdkVersion >= 4
-        )
+        assumeFeatureAvailable(ClientFeature.APP_OWNED_INTERFACES)
 
         val expectedResult = AppOwnedSdkSandboxInterfaceCompat(
             name = "TestAppOwnedSdk",
@@ -173,10 +176,7 @@
 
     @Test
     fun registerSdkSandboxActivityHandler_delegateToSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 3",
-            sdkVersion >= 3
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val catchingHandler = CatchingSdkActivityHandler()
 
@@ -198,10 +198,7 @@
 
     @Test
     fun sdkSandboxActivityHandler_ReceivesLifecycleEventsFromOriginalActivityHolder() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 3",
-            sdkVersion >= 3
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val catchingHandler = CatchingSdkActivityHandler()
 
@@ -226,10 +223,7 @@
 
     @Test
     fun unregisterSdkSandboxActivityHandler_delegateToSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 3",
-            sdkVersion >= 3
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val handler = CatchingSdkActivityHandler()
 
@@ -242,10 +236,7 @@
 
     @Test
     fun loadSdk_returnsResultFromSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 5",
-            sdkVersion >= 5
-        )
+        assumeFeatureAvailable(ClientFeature.LOAD_SDK)
 
         val sdkName = "SDK"
         val sdkParams = Bundle()
@@ -265,10 +256,7 @@
 
     @Test
     fun loadSdk_rethrowsExceptionFromSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 5",
-            sdkVersion >= 5
-        )
+        assumeFeatureAvailable(ClientFeature.LOAD_SDK)
 
         val expectedError = LoadSdkCompatException(
             LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
@@ -311,40 +299,61 @@
         }
     }
 
+    private fun assumeFeatureAvailable(clientFeature: ClientFeature) {
+        assumeTrue(
+            "Requires $clientFeature available (API >= ${clientFeature.availableFrom})",
+            clientFeature.isAvailable(forcedSdkVersion)
+        )
+    }
+
     companion object {
 
         /**
-         * Create test params for each previously released [Versions.API_VERSION] + current one.
+         * Create test params for each supported [ClientApiVersion] + current and future.
          * Each released version must have test-sdk named as "vX" (where X is version to test).
          * These TestSDKs should be registered in RuntimeEnabledSdkTable.xml and be compatible with
          * [TestSdkWrapper].
          */
-        @Parameterized.Parameters(name = "sdk: {0}, version: {1}")
+        @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun params(): List<Array<Any>> = buildList {
-            for (apiVersion in 1..Versions.API_VERSION) {
-                if (apiVersion == 3) {
-                    continue // V3 was released as V4 (original release postponed)
-                }
-                add(
-                    arrayOf(
-                        "v$apiVersion",
-                        apiVersion,
+            ClientApiVersion.values().forEach { version ->
+                // FUTURE_VERSION tested separately
+                if (version != ClientApiVersion.FUTURE_VERSION) {
+                    add(
+                        arrayOf(
+                            "v${version.apiLevel}",
+                            "v${version.apiLevel}",
+                            version.apiLevel,
+                            version.apiLevel,
+                        )
                     )
-                )
+                }
             }
 
             add(
                 arrayOf(
+                    "current_version",
                     "current",
-                    Versions.API_VERSION
+                    ClientApiVersion.CURRENT_VERSION.apiLevel,
+                    ClientApiVersion.CURRENT_VERSION.apiLevel
+                )
+            )
+
+            add(
+                arrayOf(
+                    "future_version",
+                    "current",
+                    ClientApiVersion.CURRENT_VERSION.apiLevel,
+                    ClientApiVersion.FUTURE_VERSION.apiLevel
                 )
             )
         }
 
         private fun loadTestSdkFromAssets(
             sdkConfig: LocalSdkConfig,
-            controller: TestStubController
+            controller: TestStubController,
+            overrideVersionHandshake: VersionHandshake?
         ): LocalSdkProvider {
             val context = ApplicationProvider.getApplicationContext<Context>()
             val testStorage = TestLocalSdkStorage(
@@ -358,7 +367,7 @@
                     override fun createControllerFor(sdkConfig: LocalSdkConfig) = controller
                 }
             )
-            return sdkLoader.loadSdk(sdkConfig)
+            return sdkLoader.loadSdk(sdkConfig, overrideVersionHandshake)
         }
     }
 
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
index 83c3fbe..87fccaf 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoaderTest.kt
@@ -17,16 +17,10 @@
 
 import android.content.Context
 import android.os.Build
-import android.os.Bundle
-import android.os.IBinder
 import androidx.privacysandbox.sdkruntime.client.TestSdkConfigs
 import androidx.privacysandbox.sdkruntime.client.config.LocalSdkConfig
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
-import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import androidx.privacysandbox.sdkruntime.core.Versions
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.LoadSdkCallback
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,7 +29,7 @@
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import java.io.File
-import java.util.concurrent.Executor
+import java.lang.reflect.Proxy
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,6 +65,17 @@
     }
 
     @Test
+    fun loadSdk_withCustomVersionHandshake_performsCustomHandShake() {
+        val customVersionHandshake = VersionHandshake(
+            overrideApiVersion = Int.MAX_VALUE
+        )
+        val loadedSdk = sdkLoader.loadSdk(testSdkConfig, customVersionHandshake)
+
+        assertThat(loadedSdk.extractClientVersion())
+            .isEqualTo(Int.MAX_VALUE)
+    }
+
+    @Test
     fun testContextClassloader() {
         val loadedSdk = sdkLoader.loadSdk(testSdkConfig)
 
@@ -157,45 +162,18 @@
     }
 
     private object NoOpFactory : SdkLoader.ControllerFactory {
-        override fun createControllerFor(sdkConfig: LocalSdkConfig) = NoOpImpl()
-    }
 
-    private class NoOpImpl : SdkSandboxControllerCompat.SandboxControllerImpl {
+        val controllerImplClass = SdkSandboxControllerCompat.SandboxControllerImpl::class.java
 
-        override fun loadSdk(
-            sdkName: String,
-            params: Bundle,
-            executor: Executor,
-            callback: LoadSdkCallback
-        ) {
-            executor.execute {
-                callback.onError(
-                    LoadSdkCompatException(
-                        LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
-                        "NoOp"
-                    )
-                )
-            }
-        }
+        val noOpProxy = Proxy.newProxyInstance(
+            controllerImplClass.classLoader,
+            arrayOf(controllerImplClass)
+        ) { proxy, method, args ->
+            throw UnsupportedOperationException(
+                "Unexpected method call (NoOp) object:$proxy, method: $method, args: $args"
+            )
+        } as SdkSandboxControllerCompat.SandboxControllerImpl
 
-        override fun getSandboxedSdks(): List<SandboxedSdkCompat> {
-            throw UnsupportedOperationException("NoOp")
-        }
-
-        override fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> {
-            throw UnsupportedOperationException("NoOp")
-        }
-
-        override fun registerSdkSandboxActivityHandler(
-            handlerCompat: SdkSandboxActivityHandlerCompat
-        ): IBinder {
-            throw UnsupportedOperationException("NoOp")
-        }
-
-        override fun unregisterSdkSandboxActivityHandler(
-            handlerCompat: SdkSandboxActivityHandlerCompat
-        ) {
-            throw UnsupportedOperationException("NoOp")
-        }
+        override fun createControllerFor(sdkConfig: LocalSdkConfig) = noOpProxy
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
index 1de3a50..47590a4 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
@@ -30,10 +30,10 @@
 import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.sdkruntime.client.activity.LocalSdkActivityStarter
 import androidx.privacysandbox.sdkruntime.client.controller.AppOwnedSdkRegistry
-import androidx.privacysandbox.sdkruntime.client.controller.SdkRegistry
 import androidx.privacysandbox.sdkruntime.client.controller.impl.LocalAppOwnedSdkRegistry
 import androidx.privacysandbox.sdkruntime.client.controller.impl.LocalSdkRegistry
 import androidx.privacysandbox.sdkruntime.client.controller.impl.PlatformAppOwnedSdkRegistry
+import androidx.privacysandbox.sdkruntime.client.loader.VersionHandshake
 import androidx.privacysandbox.sdkruntime.core.AdServicesInfo
 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
@@ -90,7 +90,7 @@
  */
 class SdkSandboxManagerCompat private constructor(
     private val platformApi: PlatformApi,
-    private val localSdkRegistry: SdkRegistry,
+    private val localSdkRegistry: LocalSdkRegistry,
     private val appOwnedSdkRegistry: AppOwnedSdkRegistry
 ) {
     /**
@@ -130,6 +130,23 @@
     }
 
     /**
+     * Load local SDK using different client-core protocol version [apiVersion].
+     *
+     * Could be used for:
+     * 1) Testing features in development (force future version for SDK binary with feature impl)
+     * 2) Testing loading newest SDK versions via old protocol version.
+     */
+    @TestOnly
+    internal fun loadLocalSdkWithVersionOverride(
+        sdkName: String,
+        params: Bundle,
+        apiVersion: Int
+    ): SandboxedSdkCompat {
+        val customHandshake = VersionHandshake(overrideApiVersion = apiVersion)
+        return localSdkRegistry.loadSdk(sdkName, params, customHandshake)
+    }
+
+    /**
      * Unloads an SDK that has been previously loaded by the caller.
      *
      * It is not guaranteed that the memory allocated for this SDK will be freed immediately.
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt
index 58ee51d..79a1e72 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/controller/impl/LocalSdkRegistry.kt
@@ -26,6 +26,7 @@
 import androidx.privacysandbox.sdkruntime.client.controller.SdkRegistry
 import androidx.privacysandbox.sdkruntime.client.loader.LocalSdkProvider
 import androidx.privacysandbox.sdkruntime.client.loader.SdkLoader
+import androidx.privacysandbox.sdkruntime.client.loader.VersionHandshake
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
 import org.jetbrains.annotations.TestOnly
@@ -47,14 +48,19 @@
         return configHolder.getSdkConfig(sdkName) != null
     }
 
-    override fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat {
+    override fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat =
+        loadSdk(sdkName, params, overrideVersionHandshake = null)
+
+    fun loadSdk(
+        sdkName: String,
+        params: Bundle,
+        overrideVersionHandshake: VersionHandshake?
+    ): SandboxedSdkCompat {
         val sdkConfig = configHolder.getSdkConfig(sdkName)
-        if (sdkConfig == null) {
-            throw LoadSdkCompatException(
+            ?: throw LoadSdkCompatException(
                 LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
                 "$sdkName not bundled with app"
             )
-        }
 
         synchronized(sdks) {
             if (sdks.containsKey(sdkName)) {
@@ -64,7 +70,7 @@
                 )
             }
 
-            val sdkProvider = sdkLoader.loadSdk(sdkConfig)
+            val sdkProvider = sdkLoader.loadSdk(sdkConfig, overrideVersionHandshake)
             val sandboxedSdkCompat = sdkProvider.onLoadSdk(params)
             sdks.put(
                 sdkName, Entry(
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoader.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoader.kt
index adf7a7ee..e37f665 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoader.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/SdkLoader.kt
@@ -22,6 +22,7 @@
 import androidx.privacysandbox.sdkruntime.client.loader.storage.CachedLocalSdkStorage
 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import androidx.privacysandbox.sdkruntime.core.internal.ClientFeature
 
 /**
  * Load SDK bundled with App.
@@ -50,30 +51,36 @@
      *  4. Select [LocalSdkProvider] implementation that could work with that api version.
      *
      * @param sdkConfig sdk to load
+     * @param overrideVersionHandshake (optional) override internal api level handshake
      * @return LocalSdk implementation for loaded SDK
      */
-    fun loadSdk(sdkConfig: LocalSdkConfig): LocalSdkProvider {
+    fun loadSdk(
+        sdkConfig: LocalSdkConfig,
+        overrideVersionHandshake: VersionHandshake? = null
+    ): LocalSdkProvider {
         val classLoader = classLoaderFactory.createClassLoaderFor(
             sdkConfig,
             getParentClassLoader()
         )
-        return createLocalSdk(classLoader, sdkConfig)
+        val versionHandshake = overrideVersionHandshake ?: VersionHandshake.DEFAULT
+        return createLocalSdk(classLoader, sdkConfig, versionHandshake)
     }
 
     private fun getParentClassLoader(): ClassLoader = appContext.classLoader.parent!!
 
     private fun createLocalSdk(
-        classLoader: ClassLoader,
-        sdkConfig: LocalSdkConfig
+        sdkClassLoader: ClassLoader,
+        sdkConfig: LocalSdkConfig,
+        versionHandshake: VersionHandshake
     ): LocalSdkProvider {
         try {
-            val apiVersion = VersionHandshake.perform(classLoader)
-            ResourceRemapping.apply(classLoader, sdkConfig.resourceRemapping)
-            if (apiVersion >= 2) {
-                return createSdkProviderV2(classLoader, apiVersion, sdkConfig)
-            } else if (apiVersion >= 1) {
-                return createSdkProviderV1(classLoader, sdkConfig)
+            val sdkApiVersion = versionHandshake.perform(sdkClassLoader)
+            ResourceRemapping.apply(sdkClassLoader, sdkConfig.resourceRemapping)
+            if (ClientFeature.SDK_SANDBOX_CONTROLLER.isAvailable(sdkApiVersion)) {
+                val controller = controllerFactory.createControllerFor(sdkConfig)
+                SandboxControllerInjector.inject(sdkClassLoader, sdkApiVersion, controller)
             }
+            return SdkProviderV1.create(sdkClassLoader, sdkConfig, appContext)
         } catch (ex: Exception) {
             throw LoadSdkCompatException(
                 LoadSdkCompatException.LOAD_SDK_INTERNAL_ERROR,
@@ -81,28 +88,6 @@
                 ex
             )
         }
-
-        throw LoadSdkCompatException(
-            LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
-            "Incorrect Api version"
-        )
-    }
-
-    private fun createSdkProviderV1(
-        sdkClassLoader: ClassLoader,
-        sdkConfig: LocalSdkConfig
-    ): LocalSdkProvider {
-        return SdkProviderV1.create(sdkClassLoader, sdkConfig, appContext)
-    }
-
-    private fun createSdkProviderV2(
-        sdkClassLoader: ClassLoader,
-        sdkVersion: Int,
-        sdkConfig: LocalSdkConfig
-    ): LocalSdkProvider {
-        val controller = controllerFactory.createControllerFor(sdkConfig)
-        SandboxControllerInjector.inject(sdkClassLoader, sdkVersion, controller)
-        return SdkProviderV1.create(sdkClassLoader, sdkConfig, appContext)
     }
 
     companion object {
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/VersionHandshake.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/VersionHandshake.kt
index f9e7cca..8563b15 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/VersionHandshake.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/VersionHandshake.kt
@@ -22,7 +22,13 @@
  * Performing version handshake.
  *
  */
-internal object VersionHandshake {
+internal class VersionHandshake(
+    /**
+     * Override version by using [overrideApiVersion] as client and sdk version during handshake.
+     */
+    private val overrideApiVersion: Int? = null
+) {
+
     @SuppressLint("BanUncheckedReflection") // calling method on Versions class
     fun perform(classLoader: ClassLoader?): Int {
         val versionsClass = Class.forName(
@@ -31,6 +37,14 @@
             classLoader
         )
         val handShakeMethod = versionsClass.getMethod("handShake", Int::class.javaPrimitiveType)
-        return handShakeMethod.invoke(null, Versions.API_VERSION) as Int
+
+        val clientVersion = overrideApiVersion ?: Versions.API_VERSION
+        val sdkVersion = handShakeMethod.invoke(null, clientVersion) as Int
+
+        return overrideApiVersion ?: sdkVersion
+    }
+
+    companion object {
+        val DEFAULT = VersionHandshake()
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/impl/SandboxControllerInjector.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/impl/SandboxControllerInjector.kt
index e6441a0..56f29f7 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/impl/SandboxControllerInjector.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/loader/impl/SandboxControllerInjector.kt
@@ -24,6 +24,7 @@
 import androidx.privacysandbox.sdkruntime.client.loader.impl.injector.SdkActivityHandlerWrapper
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import androidx.privacysandbox.sdkruntime.core.internal.ClientFeature
 import java.lang.reflect.InvocationHandler
 import java.lang.reflect.Method
 import java.lang.reflect.Proxy
@@ -65,20 +66,23 @@
         val sandboxedSdkCompatProxyFactory =
             SandboxedSdkCompatProxyFactory.createFor(sdkClassLoader)
 
-        val sdkActivityHandlerWrapper = if (sdkVersion >= 3)
-            SdkActivityHandlerWrapper.createFor(sdkClassLoader)
-        else
-            null
+        val sdkActivityHandlerWrapper =
+            if (ClientFeature.SDK_ACTIVITY_HANDLER.isAvailable(sdkVersion))
+                SdkActivityHandlerWrapper.createFor(sdkClassLoader)
+            else
+                null
 
-        val appOwnedSdkInterfaceProxyFactory = if (sdkVersion >= 4)
-            AppOwnedSdkInterfaceProxyFactory.createFor(sdkClassLoader)
-        else
-            null
+        val appOwnedSdkInterfaceProxyFactory =
+            if (ClientFeature.APP_OWNED_INTERFACES.isAvailable(sdkVersion))
+                AppOwnedSdkInterfaceProxyFactory.createFor(sdkClassLoader)
+            else
+                null
 
-        val loadSdkCallbackWrapper = if (sdkVersion >= 5)
-            LoadSdkCallbackWrapper.createFor(sdkClassLoader)
-        else
-            null
+        val loadSdkCallbackWrapper =
+            if (ClientFeature.LOAD_SDK.isAvailable(sdkVersion))
+                LoadSdkCallbackWrapper.createFor(sdkClassLoader)
+            else
+                null
 
         val proxy = Proxy.newProxyInstance(
             sdkClassLoader,
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/api/api_lint.ignore b/privacysandbox/sdkruntime/sdkruntime-core/api/api_lint.ignore
new file mode 100644
index 0000000..368b5a7
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/api/api_lint.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+DocumentExceptions: androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat#from(android.content.Context):
+    Method SdkSandboxControllerCompat.from appears to be throwing java.lang.UnsupportedOperationException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
+DocumentExceptions: androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat.Companion#from(android.content.Context):
+    Method Companion.from appears to be throwing java.lang.UnsupportedOperationException; this should be listed in the documentation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
index b20baed..2995cca 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompatLocalTest.kt
@@ -26,6 +26,8 @@
 import androidx.privacysandbox.sdkruntime.core.Versions
 import androidx.privacysandbox.sdkruntime.core.activity.ActivityHolder
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
+import androidx.privacysandbox.sdkruntime.core.internal.ClientApiVersion
+import androidx.privacysandbox.sdkruntime.core.internal.ClientFeature
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -46,8 +48,8 @@
 
     @Before
     fun setUp() {
-        // Emulate loading via client lib
-        Versions.handShake(Versions.API_VERSION)
+        // Emulate loading via client lib with only base features available
+        clientHandShakeForMinSupportedVersion()
 
         context = ApplicationProvider.getApplicationContext()
     }
@@ -60,23 +62,14 @@
     }
 
     @Test
-    fun loadSdk_withoutLocalImpl_throwsLoadSdkNotFoundException() {
-        val controllerCompat = SdkSandboxControllerCompat.from(context)
-
-        val exception = Assert.assertThrows(LoadSdkCompatException::class.java) {
-            runBlocking {
-                controllerCompat.loadSdk("SDK", Bundle())
-            }
+    fun from_withoutLocalImpl_throwsUnsupportedOperationException() {
+        Assert.assertThrows(UnsupportedOperationException::class.java) {
+            SdkSandboxControllerCompat.from(context)
         }
-
-        assertThat(exception.loadSdkErrorCode).isEqualTo(LoadSdkCompatException.LOAD_SDK_NOT_FOUND)
     }
 
     @Test
-    fun loadSdk_clientApiBelow5_throwsLoadSdkNotFoundException() {
-        // Emulate loading via client lib with version below 5
-        Versions.handShake(4)
-
+    fun loadSdk_whenNotAvailable_throwsLoadSdkNotFoundException() {
         SdkSandboxControllerCompat.injectLocalImpl(TestStubImpl())
         val controllerCompat = SdkSandboxControllerCompat.from(context)
 
@@ -90,9 +83,8 @@
     }
 
     @Test
-    fun loadSdk_withLocalImpl_returnsLoadedSdkFromLocalImpl() {
-        // Emulate loading via client lib with version 5
-        Versions.handShake(5)
+    fun loadSdk_returnsLoadedSdkFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.LOAD_SDK)
 
         val expectedResult = SandboxedSdkCompat(Binder())
         val stubLocalImpl = TestStubImpl(
@@ -113,9 +105,8 @@
     }
 
     @Test
-    fun loadSdk_withLocalImpl_rethrowsExceptionFromLocalImpl() {
-        // Emulate loading via client lib with version 5
-        Versions.handShake(5)
+    fun loadSdk_rethrowsExceptionFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.LOAD_SDK)
 
         val expectedError = LoadSdkCompatException(RuntimeException(), Bundle())
         SdkSandboxControllerCompat.injectLocalImpl(
@@ -135,14 +126,7 @@
     }
 
     @Test
-    fun getSandboxedSdks_withoutLocalImpl_returnsEmptyList() {
-        val controllerCompat = SdkSandboxControllerCompat.from(context)
-        val sandboxedSdks = controllerCompat.getSandboxedSdks()
-        assertThat(sandboxedSdks).isEmpty()
-    }
-
-    @Test
-    fun getSandboxedSdks_withLocalImpl_returnsListFromLocalImpl() {
+    fun getSandboxedSdks_returnsListFromLocalImpl() {
         val expectedResult = listOf(SandboxedSdkCompat(Binder()))
         SdkSandboxControllerCompat.injectLocalImpl(
             TestStubImpl(
@@ -156,17 +140,7 @@
     }
 
     @Test
-    fun getAppOwnedSdkSandboxInterfaces_withoutLocalImpl_returnsEmptyList() {
-        val controllerCompat = SdkSandboxControllerCompat.from(context)
-        val appOwnedInterfaces = controllerCompat.getAppOwnedSdkSandboxInterfaces()
-        assertThat(appOwnedInterfaces).isEmpty()
-    }
-
-    @Test
-    fun getAppOwnedSdkSandboxInterfaces_clientApiBelow4_returnsEmptyList() {
-        // Emulate loading via client lib with version below 4
-        Versions.handShake(3)
-
+    fun getAppOwnedSdkSandboxInterfaces_whenNotAvailable_returnsEmptyList() {
         SdkSandboxControllerCompat.injectLocalImpl(
             TestStubImpl(
                 appOwnedSdks = listOf(
@@ -185,7 +159,9 @@
     }
 
     @Test
-    fun getAppOwnedSdkSandboxInterfaces_withLocalImpl_returnsListFromLocalImpl() {
+    fun getAppOwnedSdkSandboxInterfaces_returnsListFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.APP_OWNED_INTERFACES)
+
         val expectedResult = listOf(
             AppOwnedSdkSandboxInterfaceCompat(
                 name = "TestSdk",
@@ -205,7 +181,9 @@
     }
 
     @Test
-    fun registerSdkSandboxActivityHandler_withLocalImpl_registerItInLocalImpl() {
+    fun registerSdkSandboxActivityHandler_registerItInLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.SDK_ACTIVITY_HANDLER)
+
         val localImpl = TestStubImpl()
         SdkSandboxControllerCompat.injectLocalImpl(localImpl)
 
@@ -218,7 +196,9 @@
     }
 
     @Test
-    fun unregisterSdkSandboxActivityHandler_withLocalImpl_unregisterItFromLocalImpl() {
+    fun unregisterSdkSandboxActivityHandler_unregisterItFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.SDK_ACTIVITY_HANDLER)
+
         val localImpl = TestStubImpl()
         SdkSandboxControllerCompat.injectLocalImpl(localImpl)
 
@@ -234,10 +214,7 @@
     }
 
     @Test
-    fun registerSdkSandboxActivityHandler_clientApiBelow3_throwsUnsupportedOperationException() {
-        // Emulate loading via client lib with version below 3
-        Versions.handShake(2)
-
+    fun registerSdkSandboxActivityHandler_whenNotAvailable_throwsUnsupportedOperationException() {
         SdkSandboxControllerCompat.injectLocalImpl(TestStubImpl())
         val controllerCompat = SdkSandboxControllerCompat.from(context)
 
@@ -251,10 +228,7 @@
     }
 
     @Test
-    fun unregisterSdkSandboxActivityHandler_clientApiBelow3_throwsUnsupportedOperationException() {
-        // Emulate loading via client lib with version below 3
-        Versions.handShake(2)
-
+    fun unregisterSdkSandboxActivityHandler_whenNotAvailable_throwsUnsupportedOperationException() {
         SdkSandboxControllerCompat.injectLocalImpl(TestStubImpl())
         val controllerCompat = SdkSandboxControllerCompat.from(context)
 
@@ -267,6 +241,22 @@
         }
     }
 
+    /**
+     * Call [Versions.handShake] to emulate loading via client lib.
+     * Using version where [clientFeature] available.
+     */
+    private fun clientHandShakeForVersionIncluding(clientFeature: ClientFeature) {
+        Versions.handShake(clientFeature.availableFrom.apiLevel)
+    }
+
+    /**
+     * Call [Versions.handShake] to emulate loading via client lib.
+     * Using [ClientApiVersion.MIN_SUPPORTED] - to check features available in all client versions.
+     */
+    private fun clientHandShakeForMinSupportedVersion() {
+        Versions.handShake(ClientApiVersion.MIN_SUPPORTED.apiLevel)
+    }
+
     internal class TestStubImpl(
         private val sandboxedSdks: List<SandboxedSdkCompat> = emptyList(),
         private val appOwnedSdks: List<AppOwnedSdkSandboxInterfaceCompat> = emptyList(),
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/Versions.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/Versions.kt
index 69d5126..6d50d2c 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/Versions.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/Versions.kt
@@ -18,19 +18,22 @@
 
 import androidx.annotation.Keep
 import androidx.annotation.RestrictTo
+import androidx.privacysandbox.sdkruntime.core.internal.ClientApiVersion
 import org.jetbrains.annotations.TestOnly
 
 /**
  * Store internal API version (for Client-Core communication).
- * Methods invoked via reflection.
  *
+ * DO NOT CHANGE THIS CLASS.
+ * Methods invoked via reflection from previously released versions of sdkruntime-client.
  */
 @Suppress("unused")
 @Keep
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 object Versions {
 
-    const val API_VERSION = 5
+    @JvmField
+    val API_VERSION = ClientApiVersion.CURRENT_VERSION.apiLevel
 
     @JvmField
     var CLIENT_VERSION: Int? = null
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt
index 07cc5b4..a2ad3e9 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/SdkSandboxControllerCompat.kt
@@ -32,7 +32,6 @@
 import androidx.privacysandbox.sdkruntime.core.Versions
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.impl.LocalImpl
-import androidx.privacysandbox.sdkruntime.core.controller.impl.NoOpImpl
 import androidx.privacysandbox.sdkruntime.core.controller.impl.PlatformUDCImpl
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicBoolean
@@ -160,11 +159,10 @@
         fun from(context: Context): SdkSandboxControllerCompat {
             val clientVersion = Versions.CLIENT_VERSION
             if (clientVersion != null) {
-                val implFromClient = localImpl
-                if (implFromClient != null) {
-                    return SdkSandboxControllerCompat(LocalImpl(implFromClient, clientVersion))
-                }
-                return SdkSandboxControllerCompat(NoOpImpl())
+                val implFromClient = localImpl ?: throw UnsupportedOperationException(
+                    "Shouldn't happen: No controller implementation available"
+                )
+                return SdkSandboxControllerCompat(LocalImpl(implFromClient, clientVersion))
             }
             val platformImpl = PlatformImplFactory.create(context)
             return SdkSandboxControllerCompat(platformImpl)
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
index e07d010..b542c62 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/LocalImpl.kt
@@ -25,6 +25,7 @@
 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
 import androidx.privacysandbox.sdkruntime.core.controller.LoadSdkCallback
 import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+import androidx.privacysandbox.sdkruntime.core.internal.ClientFeature
 import java.util.concurrent.Executor
 
 /**
@@ -42,7 +43,7 @@
         executor: Executor,
         callback: LoadSdkCallback
     ) {
-        if (clientVersion >= 5) {
+        if (ClientFeature.LOAD_SDK.isAvailable(clientVersion)) {
             implFromClient.loadSdk(sdkName, params, executor, callback)
         } else {
             executor.execute {
@@ -61,7 +62,7 @@
     }
 
     override fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> {
-        return if (clientVersion >= 4) {
+        return if (ClientFeature.APP_OWNED_INTERFACES.isAvailable(clientVersion)) {
             implFromClient.getAppOwnedSdkSandboxInterfaces()
         } else {
             emptyList()
@@ -71,22 +72,24 @@
     override fun registerSdkSandboxActivityHandler(
         handlerCompat: SdkSandboxActivityHandlerCompat
     ): IBinder {
-        if (clientVersion < 3) {
+        if (ClientFeature.SDK_ACTIVITY_HANDLER.isAvailable(clientVersion)) {
+            return implFromClient.registerSdkSandboxActivityHandler(handlerCompat)
+        } else {
             throw UnsupportedOperationException(
                 "Client library version doesn't support SdkActivities"
             )
         }
-        return implFromClient.registerSdkSandboxActivityHandler(handlerCompat)
     }
 
     override fun unregisterSdkSandboxActivityHandler(
         handlerCompat: SdkSandboxActivityHandlerCompat
     ) {
-        if (clientVersion < 3) {
+        if (ClientFeature.SDK_ACTIVITY_HANDLER.isAvailable(clientVersion)) {
+            implFromClient.unregisterSdkSandboxActivityHandler(handlerCompat)
+        } else {
             throw UnsupportedOperationException(
                 "Client library version doesn't support SdkActivities"
             )
         }
-        implFromClient.unregisterSdkSandboxActivityHandler(handlerCompat)
     }
 }
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt
deleted file mode 100644
index fdb388f..0000000
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/NoOpImpl.kt
+++ /dev/null
@@ -1,67 +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.privacysandbox.sdkruntime.core.controller.impl
-
-import android.os.Bundle
-import android.os.IBinder
-import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat
-import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException
-import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
-import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat
-import androidx.privacysandbox.sdkruntime.core.controller.LoadSdkCallback
-import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
-import java.util.concurrent.Executor
-
-/**
- * NoOp implementation for cases when [SdkSandboxControllerCompat] not supported.
- */
-internal class NoOpImpl : SdkSandboxControllerCompat.SandboxControllerImpl {
-
-    override fun loadSdk(
-        sdkName: String,
-        params: Bundle,
-        executor: Executor,
-        callback: LoadSdkCallback
-    ) {
-        executor.execute {
-            callback.onError(
-                LoadSdkCompatException(
-                    LoadSdkCompatException.LOAD_SDK_NOT_FOUND,
-                    "Loading SDK not supported on this device"
-                )
-            )
-        }
-    }
-
-    override fun getSandboxedSdks(): List<SandboxedSdkCompat> = emptyList()
-
-    override fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> =
-        emptyList()
-
-    override fun registerSdkSandboxActivityHandler(
-        handlerCompat: SdkSandboxActivityHandlerCompat
-    ):
-        IBinder {
-        throw UnsupportedOperationException("Not supported")
-    }
-
-    override fun unregisterSdkSandboxActivityHandler(
-        handlerCompat: SdkSandboxActivityHandlerCompat
-    ) {
-        throw UnsupportedOperationException("Not supported")
-    }
-}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientApiVersion.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientApiVersion.kt
new file mode 100644
index 0000000..dbbdeda
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientApiVersion.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.sdkruntime.core.internal
+
+import androidx.annotation.RestrictTo
+
+/**
+ * List of all supported internal API versions (Client-Core communication).
+ *
+ * NEVER REMOVE / MODIFY RELEASED VERSIONS:
+ * That could break loading of SDKs built with previous/future library version.
+ *
+ * Adding new version here bumps internal API version for next library release:
+ * [androidx.privacysandbox.sdkruntime.core.Versions.API_VERSION]
+ * When adding a new version, ALL new features from this version should be specified
+ * (NO FUTURE CHANGES SUPPORTED).
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+enum class ClientApiVersion(
+    val apiLevel: Int,
+    private val newFeatures: Set<ClientFeature> = emptySet()
+) {
+    V1__1_0_ALPHA01(apiLevel = 1),
+
+    V2__1_0_ALPHA02(
+        apiLevel = 2,
+        newFeatures = setOf(
+            ClientFeature.SDK_SANDBOX_CONTROLLER
+        )
+    ),
+
+    // V3 was released as V4 (original release postponed)
+    V4__1_0_ALPHA05(
+        apiLevel = 4,
+        newFeatures = setOf(
+            ClientFeature.SDK_ACTIVITY_HANDLER,
+            ClientFeature.APP_OWNED_INTERFACES,
+        )
+    ),
+
+    V5__1_0_ALPHA13(
+        apiLevel = 5,
+        newFeatures = setOf(
+            ClientFeature.LOAD_SDK
+        )
+    ),
+
+    /**
+     * Unreleased API version.
+     * Features not added to other versions will be automatically added here (to allow testing).
+     */
+    FUTURE_VERSION(apiLevel = Int.MAX_VALUE);
+
+    companion object {
+        val MIN_SUPPORTED = values().minBy { v -> v.apiLevel }
+        val CURRENT_VERSION = values().filter { v -> v != FUTURE_VERSION }.maxBy { v -> v.apiLevel }
+
+        private val FEATURE_TO_VERSION_MAP = buildFeatureMap()
+
+        fun minAvailableVersionFor(clientFeature: ClientFeature): ClientApiVersion {
+            return FEATURE_TO_VERSION_MAP[clientFeature] ?: FUTURE_VERSION
+        }
+
+        /**
+         * Build mapping between [ClientFeature] and version where it first became available.
+         */
+        private fun buildFeatureMap(): Map<ClientFeature, ClientApiVersion> {
+            if (FUTURE_VERSION.newFeatures.isNotEmpty()) {
+                throw IllegalStateException("FUTURE_VERSION MUST NOT define any features")
+            }
+            return buildMap {
+                values().forEach { version ->
+                    version.newFeatures.forEach { feature ->
+                        val oldVersion = put(feature, version)
+                        if (oldVersion != null) {
+                            throw IllegalStateException(
+                                "$feature duplicated in $version and $oldVersion"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientFeature.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientFeature.kt
new file mode 100644
index 0000000..5e24eb9
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientFeature.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.sdkruntime.core.internal
+
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.sdkruntime.core.controller.SdkSandboxControllerCompat
+
+/**
+ * List of features using Client-Core internal API.
+ * Each feature available since particular ([ClientApiVersion]).
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+enum class ClientFeature {
+    /**
+     * Support for retrieving [SdkSandboxControllerCompat] on SDK side.
+     */
+    SDK_SANDBOX_CONTROLLER,
+
+    /**
+     * Support for starting SDK Activity:
+     * [SdkSandboxControllerCompat.registerSdkSandboxActivityHandler]
+     * [SdkSandboxControllerCompat.unregisterSdkSandboxActivityHandler]
+     */
+    SDK_ACTIVITY_HANDLER,
+
+    /**
+     * Support for retrieving App-owned interfaces:
+     * [SdkSandboxControllerCompat.getAppOwnedSdkSandboxInterfaces]
+     */
+    APP_OWNED_INTERFACES,
+
+    /**
+     * Support for loading SDKs by other SDKs:
+     * [SdkSandboxControllerCompat.loadSdk]
+     */
+    LOAD_SDK;
+
+    val availableFrom: ClientApiVersion
+        get() = ClientApiVersion.minAvailableVersionFor(this)
+
+    fun isAvailable(apiLevel: Int): Boolean {
+        return apiLevel >= availableFrom.apiLevel
+    }
+}
diff --git a/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/current/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
similarity index 96%
rename from privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/current/CompatProvider.kt
rename to privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index c6960bd..fc1f0b5 100644
--- a/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/current/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/current/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.privacysandbox.sdkruntime.testsdk.current
+package androidx.privacysandbox.sdkruntime.testsdk
 
 import android.content.Context
 import android.os.Binder
diff --git a/privacysandbox/sdkruntime/test-sdks/v1/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v1/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v1/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
similarity index 97%
rename from privacysandbox/sdkruntime/test-sdks/v1/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v1/CompatProvider.kt
rename to privacysandbox/sdkruntime/test-sdks/v1/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index c2b5312..307363e 100644
--- a/privacysandbox/sdkruntime/test-sdks/v1/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v1/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v1/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.privacysandbox.sdkruntime.testsdk.v1
+package androidx.privacysandbox.sdkruntime.testsdk
 
 import android.content.Context
 import android.os.Binder
diff --git a/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v2/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
similarity index 95%
rename from privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v2/CompatProvider.kt
rename to privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index cf4d8c7..8240d3ca 100644
--- a/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v2/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v2/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.privacysandbox.sdkruntime.testsdk.v2
+package androidx.privacysandbox.sdkruntime.testsdk
 
 import android.content.Context
 import android.os.Binder
diff --git a/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v4/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
similarity index 96%
rename from privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v4/CompatProvider.kt
rename to privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index a97aec2..e24fb65 100644
--- a/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v4/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v4/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.privacysandbox.sdkruntime.testsdk.v4
+package androidx.privacysandbox.sdkruntime.testsdk
 
 import android.content.Context
 import android.os.Binder
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/build.gradle b/privacysandbox/sdkruntime/test-sdks/v5/build.gradle
index cecf9a2..9b98f82 100644
--- a/privacysandbox/sdkruntime/test-sdks/v5/build.gradle
+++ b/privacysandbox/sdkruntime/test-sdks/v5/build.gradle
@@ -35,7 +35,7 @@
 
 dependencies {
     implementation(libs.kotlinCoroutinesCore)
-    implementation(project(":privacysandbox:sdkruntime:sdkruntime-provider"))
+    implementation("androidx.privacysandbox.sdkruntime:sdkruntime-provider:1.0.0-alpha13")
 }
 
 /*
diff --git a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v5/CompatProvider.kt b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
similarity index 98%
rename from privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v5/CompatProvider.kt
rename to privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
index bdc8581..857bdc9 100644
--- a/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/v5/CompatProvider.kt
+++ b/privacysandbox/sdkruntime/test-sdks/v5/src/main/java/androidx/privacysandbox/sdkruntime/testsdk/CompatProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.privacysandbox.sdkruntime.testsdk.v5
+package androidx.privacysandbox.sdkruntime.testsdk
 
 import android.content.Context
 import android.os.Binder
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
index 6f4b716..9c0cec1 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
@@ -62,7 +62,7 @@
         assertThat(ToolMetadata.parseFrom(resourceMap[expectedMetadataRelativePath]))
             .isEqualTo(
                 ToolMetadata.newBuilder()
-                    .setCodeGenerationVersion(3)
+                    .setCodeGenerationVersion(4)
                     .build()
             )
     }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
index 548b00b..5cf9554 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
@@ -365,8 +365,8 @@
     fun parameterWithGenerics_fails() {
         checkSourceFails(serviceMethod("suspend fun foo(x: MutableList<Int>)"))
             .containsExactlyErrors(
-                "Error in com.mysdk.MySdk.foo: only primitives, lists, data classes annotated " +
-                    "with @PrivacySandboxValue, interfaces annotated with " +
+                "Error in com.mysdk.MySdk.foo: only primitives, lists, data/enum classes " +
+                    "annotated with @PrivacySandboxValue, interfaces annotated with " +
                     "@PrivacySandboxCallback or @PrivacySandboxInterface, and " +
                     "SdkActivityLaunchers are supported as parameter types."
             )
@@ -384,8 +384,8 @@
     fun parameterLambda_fails() {
         checkSourceFails(serviceMethod("suspend fun foo(x: (Int) -> Int)"))
             .containsExactlyErrors(
-                "Error in com.mysdk.MySdk.foo: only primitives, lists, data classes annotated " +
-                    "with @PrivacySandboxValue, interfaces annotated with " +
+                "Error in com.mysdk.MySdk.foo: only primitives, lists, data/enum classes " +
+                    "annotated with @PrivacySandboxValue, interfaces annotated with " +
                     "@PrivacySandboxCallback " + "or @PrivacySandboxInterface, and " +
                     "SdkActivityLaunchers are supported as parameter types."
             )
@@ -406,9 +406,9 @@
                 """
         )
         checkSourceFails(source).containsExactlyErrors(
-            "Error in com.mysdk.MySdk.foo: only primitives, lists, data classes annotated with " +
-                "@PrivacySandboxValue, interfaces annotated with @PrivacySandboxInterface, and " +
-                "SdkActivityLaunchers are supported as return types."
+            "Error in com.mysdk.MySdk.foo: only primitives, lists, data/enum classes annotated " +
+                "with @PrivacySandboxValue, interfaces annotated with @PrivacySandboxInterface, " +
+                "and SdkActivityLaunchers are supported as return types."
         )
     }
 
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
index 858ee45..7b9640a 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ValueParserTest.kt
@@ -212,7 +212,7 @@
 
     @Test
     fun nonDataClassValue_fails() {
-        checkSourceFails(annotatedValue("private class MySdkRequest(val id: Int)"))
+        checkSourceFails(annotatedValue("class MySdkRequest(val id: Int)"))
             .containsExactlyErrors(
                 "Only data classes and enum classes can be annotated with" +
                     " @PrivacySandboxValue."
@@ -273,7 +273,7 @@
         )
         checkSourceFails(dataClass)
             .containsExactlyErrors(
-                "Error in com.mysdk.MySdkRequest.foo: only primitives, lists, data classes " +
+                "Error in com.mysdk.MySdkRequest.foo: only primitives, lists, data/enum classes " +
                     "annotated with @PrivacySandboxValue, interfaces annotated with " +
                     "@PrivacySandboxInterface, and SdkActivityLaunchers are supported as " +
                     "properties."
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt
index c905fff..a5aaa9b 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/RequestFlagConverter.kt
@@ -5,8 +5,10 @@
 public class RequestFlagConverter(
     public val context: Context,
 ) {
+    private val enumValues: List<RequestFlag> = RequestFlag.values().toList()
+
     public fun fromParcelable(parcelable: ParcelableRequestFlag): RequestFlag =
-            RequestFlag.entries[parcelable.variant_ordinal]
+            enumValues[parcelable.variant_ordinal]
 
     public fun toParcelable(annotatedValue: RequestFlag): ParcelableRequestFlag {
         val parcelable = ParcelableRequestFlag()
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt
index 9554eaf..9d7fbd8 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/callbacks/output/com/sdkwithcallbacks/MyEnumConverter.kt
@@ -1,8 +1,10 @@
 package com.sdkwithcallbacks
 
 public object MyEnumConverter {
+    private val enumValues: List<MyEnum> = MyEnum.values().toList()
+
     public fun fromParcelable(parcelable: ParcelableMyEnum): MyEnum =
-            MyEnum.entries[parcelable.variant_ordinal]
+            enumValues[parcelable.variant_ordinal]
 
     public fun toParcelable(annotatedValue: MyEnum): ParcelableMyEnum {
         val parcelable = ParcelableMyEnum()
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt
index adcb755cf..06e7bfa 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/values/output/com/sdkwithvalues/RequestFlagConverter.kt
@@ -1,8 +1,10 @@
 package com.sdkwithvalues
 
 public object RequestFlagConverter {
+    private val enumValues: List<RequestFlag> = RequestFlag.values().toList()
+
     public fun fromParcelable(parcelable: ParcelableRequestFlag): RequestFlag =
-            RequestFlag.entries[parcelable.variant_ordinal]
+            enumValues[parcelable.variant_ordinal]
 
     public fun toParcelable(annotatedValue: RequestFlag): ParcelableRequestFlag {
         val parcelable = ParcelableRequestFlag()
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Metadata.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Metadata.kt
index 3fd042f..9101808 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Metadata.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Metadata.kt
@@ -24,7 +24,7 @@
     /** Tool metadata message. It's serialized and stored in every SDK API descriptor. */
     val toolMetadata: ToolMetadata =
         ToolMetadata.newBuilder()
-            .setCodeGenerationVersion(3)
+            .setCodeGenerationVersion(4)
             .build()
 
     /** Relative path to metadata file in SDK API descriptor jar. */
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
index 9bc7b98..8afb3a1 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
@@ -91,7 +91,6 @@
     }
 
     private fun generateAidlContent(): List<AidlFileSpec> {
-        // TODO(b/323369085): Generate AIDL content for enum classes
         val values = api.values.map(::generateValue)
         val service = aidlInterface(api.getOnlyService())
         val customCallbacks = api.callbacks.flatMap(::aidlInterface)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt
index e326d56..1277ce6 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/ValueConverterFileGenerator.kt
@@ -22,6 +22,7 @@
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
 import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedValue
+import androidx.privacysandbox.tools.core.model.Types
 import androidx.privacysandbox.tools.core.model.ValueProperty
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FileSpec
@@ -64,11 +65,15 @@
                 )
                 addFunction(generateFromParcelable(value))
                 addFunction(generateToParcelable(value))
+                if (value is AnnotatedEnumClass)
+                    addProperty(generateEnumValuesProperty(value))
             }
         }
         return TypeSpec.objectBuilder(value.converterNameSpec()).build() {
             addFunction(generateFromParcelable(value))
             addFunction(generateToParcelable(value))
+            if (value is AnnotatedEnumClass)
+                addProperty(generateEnumValuesProperty(value))
         }
     }
 
@@ -120,7 +125,7 @@
                     addParameter("parcelable", value.parcelableNameSpec())
                     returns(value.type.poetTypeName())
                     addStatement(
-                        "return %T.entries[parcelable.variant_ordinal]",
+                        "return enumValues[parcelable.variant_ordinal]",
                         value.type.poetTypeName()
                     )
                 }
@@ -135,4 +140,16 @@
                 )
             )
         }
+
+    private fun generateEnumValuesProperty(value: AnnotatedEnumClass) =
+        PropertySpec.builder(
+            "enumValues",
+            Types.list(value.type).poetTypeName()
+        )
+            .addModifiers(KModifier.PRIVATE)
+            .initializer(
+                CodeBlock.of(
+                    "%T.values().toList()", value.type.poetClassName()
+                )
+        ).build()
 }
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
index 7074ebf..ebf7efb 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/validator/ModelValidator.kt
@@ -17,12 +17,14 @@
 package androidx.privacysandbox.tools.core.validator
 
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.AnnotatedValue
 import androidx.privacysandbox.tools.core.model.ParsedApi
 import androidx.privacysandbox.tools.core.model.Type
 import androidx.privacysandbox.tools.core.model.Types
 import androidx.privacysandbox.tools.core.model.Types.asNonNull
+import javax.lang.model.SourceVersion.isKeyword as isJavaKeyword
 
 class ModelValidator private constructor(val api: ParsedApi) {
     private val values = api.values.map(AnnotatedValue::type)
@@ -42,6 +44,7 @@
         validateServiceAndInterfaceMethods()
         validateValuePropertyTypes()
         validateCallbackMethods()
+        validateNames()
         return ValidationResult(errors)
     }
 
@@ -93,7 +96,7 @@
                 if (method.parameters.any { !(isValidInterfaceParameterType(it.type)) }) {
                     errors.add(
                         "Error in ${annotatedInterface.type.qualifiedName}.${method.name}: " +
-                            "only primitives, lists, data classes annotated with " +
+                            "only primitives, lists, data/enum classes annotated with " +
                             "@PrivacySandboxValue, interfaces annotated with " +
                             "@PrivacySandboxCallback or @PrivacySandboxInterface, and " +
                             "SdkActivityLaunchers are supported as parameter types."
@@ -102,7 +105,7 @@
                 if (!isValidInterfaceReturnType(method.returnType)) {
                     errors.add(
                         "Error in ${annotatedInterface.type.qualifiedName}.${method.name}: " +
-                            "only primitives, lists, data classes annotated with " +
+                            "only primitives, lists, data/enum classes annotated with " +
                             "@PrivacySandboxValue, interfaces annotated with " +
                             "@PrivacySandboxInterface, and SdkActivityLaunchers are supported as " +
                             "return types."
@@ -121,7 +124,7 @@
                 if (!isValidValuePropertyType(property.type)) {
                     errors.add(
                         "Error in ${value.type.qualifiedName}.${property.name}: " +
-                            "only primitives, lists, data classes annotated with " +
+                            "only primitives, lists, data/enum classes annotated with " +
                             "@PrivacySandboxValue, interfaces annotated with " +
                             "@PrivacySandboxInterface, and SdkActivityLaunchers are supported as " +
                             "properties."
@@ -137,7 +140,7 @@
                 if (method.parameters.any { !isValidCallbackParameterType(it.type) }) {
                     errors.add(
                         "Error in ${callback.type.qualifiedName}.${method.name}: " +
-                            "only primitives, lists, data classes annotated with " +
+                            "only primitives, lists, data/enum classes annotated with " +
                             "@PrivacySandboxValue, interfaces annotated with " +
                             "@PrivacySandboxInterface, and SdkActivityLaunchers are supported as " +
                             "callback parameter types."
@@ -147,6 +150,39 @@
         }
     }
 
+    private fun validateNames() {
+        for (value in api.values.filterIsInstance<AnnotatedDataClass>()) {
+            for (property in value.properties) {
+                if (isJavaKeyword(property.name)) {
+                    errors.add(
+                        "Error in ${value.type.qualifiedName}.${property.name}: property name " +
+                            "must not be a Java keyword."
+                    )
+                }
+            }
+        }
+        for (value in api.values.filterIsInstance<AnnotatedEnumClass>()) {
+            for (variant in value.variants) {
+                if (isJavaKeyword(variant)) {
+                    errors.add(
+                        "Error in ${value.type.qualifiedName}.$variant: enum constant " +
+                            "name must not be a Java keyword."
+                    )
+                }
+            }
+        }
+        for (iface in api.interfaces + api.callbacks + api.services) {
+            for (method in iface.methods) {
+                if (isJavaKeyword(method.name)) {
+                    errors.add(
+                        "Error in ${iface.type.qualifiedName}.${method.name}: method name " +
+                            "must not be a Java keyword."
+                    )
+                }
+            }
+        }
+    }
+
     private fun isValidInterfaceParameterType(type: Type) =
         isValue(type) || isInterface(type) || isPrimitive(type) || isList(type) ||
             isCallback(type) || isBundledType(type)
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt
index c76a38a..38116ce 100644
--- a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/validator/ModelValidatorTest.kt
@@ -17,6 +17,7 @@
 package androidx.privacysandbox.tools.core.validator
 
 import androidx.privacysandbox.tools.core.model.AnnotatedDataClass
+import androidx.privacysandbox.tools.core.model.AnnotatedEnumClass
 import androidx.privacysandbox.tools.core.model.AnnotatedInterface
 import androidx.privacysandbox.tools.core.model.Method
 import androidx.privacysandbox.tools.core.model.Parameter
@@ -293,10 +294,10 @@
         val validationResult = ModelValidator.validate(api)
         assertThat(validationResult.isFailure).isTrue()
         assertThat(validationResult.errors).containsExactly(
-            "Error in com.mysdk.MySdk.returnFoo: only primitives, lists, data classes annotated " +
-                "with @PrivacySandboxValue, interfaces annotated with @PrivacySandboxInterface, " +
-                "and SdkActivityLaunchers are supported as return types.",
-            "Error in com.mysdk.MySdk.receiveFoo: only primitives, lists, data classes " +
+            "Error in com.mysdk.MySdk.returnFoo: only primitives, lists, data/enum classes " +
+                "annotated with @PrivacySandboxValue, interfaces annotated with " +
+                "@PrivacySandboxInterface, and SdkActivityLaunchers are supported as return types.",
+            "Error in com.mysdk.MySdk.receiveFoo: only primitives, lists, data/enum classes " +
                 "annotated with @PrivacySandboxValue, interfaces annotated with " +
                 "@PrivacySandboxCallback or @PrivacySandboxInterface, and SdkActivityLaunchers " +
                 "are supported as parameter types."
@@ -379,9 +380,9 @@
         val validationResult = ModelValidator.validate(api)
         assertThat(validationResult.isFailure).isTrue()
         assertThat(validationResult.errors).containsExactly(
-            "Error in com.mysdk.Foo.bar: only primitives, lists, data classes annotated with " +
-                "@PrivacySandboxValue, interfaces annotated with @PrivacySandboxInterface, and " +
-                "SdkActivityLaunchers are supported as properties."
+            "Error in com.mysdk.Foo.bar: only primitives, lists, data/enum classes annotated " +
+                "with @PrivacySandboxValue, interfaces annotated with @PrivacySandboxInterface, " +
+                "and SdkActivityLaunchers are supported as properties."
         )
     }
 
@@ -410,10 +411,78 @@
         val validationResult = ModelValidator.validate(api)
         assertThat(validationResult.isFailure).isTrue()
         assertThat(validationResult.errors).containsExactly(
-            "Error in com.mysdk.MySdkCallback.foo: only primitives, lists, data classes " +
+            "Error in com.mysdk.MySdkCallback.foo: only primitives, lists, data/enum classes " +
                 "annotated with @PrivacySandboxValue, interfaces annotated with " +
                 "@PrivacySandboxInterface, and SdkActivityLaunchers are supported as callback " +
                 "parameter types."
         )
     }
+
+    @Test
+    fun propertyWithKeywordName_throws() {
+        val api = ParsedApi(
+            services = setOf(
+                AnnotatedInterface(type = Type(packageName = "com.mysdk", simpleName = "MySdk")),
+            ),
+            values = setOf(
+                AnnotatedDataClass(
+                    type = Type(packageName = "com.mysdk", simpleName = "Foo"),
+                    properties = listOf(
+                        ValueProperty("import", Types.int)
+                    )
+                )
+            )
+        )
+        val validationResult = ModelValidator.validate(api)
+        assertThat(validationResult.isFailure).isTrue()
+        assertThat(validationResult.errors).containsExactly(
+            "Error in com.mysdk.Foo.import: property name must not be a Java keyword."
+        )
+    }
+
+    @Test
+    fun enumConstantWithKeywordName_throws() {
+        val api = ParsedApi(
+            services = setOf(
+                AnnotatedInterface(type = Type(packageName = "com.mysdk", simpleName = "MySdk")),
+            ),
+            values = setOf(
+                AnnotatedEnumClass(
+                    type = Type(packageName = "com.mysdk", simpleName = "Foo"),
+                    variants = listOf(
+                        "boolean"
+                    )
+                )
+            )
+        )
+        val validationResult = ModelValidator.validate(api)
+        assertThat(validationResult.isFailure).isTrue()
+        assertThat(validationResult.errors).containsExactly(
+            "Error in com.mysdk.Foo.boolean: enum constant name must not be a Java keyword."
+        )
+    }
+
+    @Test
+    fun methodWithKeywordName_throws() {
+        val api = ParsedApi(
+            services = setOf(
+                AnnotatedInterface(
+                    type = Type(packageName = "com.mysdk", simpleName = "MySdk"),
+                    methods = listOf(
+                        Method(
+                            name = "char",
+                            parameters = listOf(),
+                            returnType = Types.unit,
+                            isSuspend = false,
+                        )
+                    )
+                ),
+            ),
+        )
+        val validationResult = ModelValidator.validate(api)
+        assertThat(validationResult.isFailure).isTrue()
+        assertThat(validationResult.errors).containsExactly(
+            "Error in com.mysdk.MySdk.char: method name must not be a Java keyword."
+        )
+    }
 }
diff --git a/privacysandbox/tools/tools-testing/build.gradle b/privacysandbox/tools/tools-testing/build.gradle
index a1fd9f3..1a20c8a 100644
--- a/privacysandbox/tools/tools-testing/build.gradle
+++ b/privacysandbox/tools/tools-testing/build.gradle
@@ -38,7 +38,7 @@
 
 androidx {
     name = "androidx.privacysandbox.tools:tools-testing"
-    type = LibraryType.INTERNAL_TEST_LIBRARY
+    type = LibraryType.INTERNAL_HOST_TEST_LIBRARY
     inceptionYear = "2022"
     description = "Testing utilities for privacysandbox tools."
 }
diff --git a/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxValue.kt b/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxValue.kt
index 7b002d9..97383f9 100644
--- a/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxValue.kt
+++ b/privacysandbox/tools/tools/src/main/java/androidx/privacysandbox/tools/PrivacySandboxValue.kt
@@ -19,13 +19,22 @@
 /**
  * Annotated values that can be sent to/from SDKs in the Privacy Sandbox.
  *
- * The values should be public Kotlin data classes that only contain immutable properties with types
- * supported by the sandbox (primitives, [PrivacySandboxInterface], [PrivacySandboxValue], or lists
- * of primitives or [PrivacySandboxValue]). [PrivacySandboxCallback] interfaces are not allowed.
+ * The values should be public Kotlin data classes or enum classes.
+ *
+ * For data classes, they should only contain immutable properties with types supported by the
+ * sandbox (primitives, [PrivacySandboxInterface], [PrivacySandboxValue], or lists of primitives or
+ * [PrivacySandboxValue]). [PrivacySandboxCallback] interfaces are not allowed.
+ *
+ * For enum classes, they should only contain basic enum constants.
  *
  * Values cannot have functions, type parameters or properties with default values.
  *
- * Usage example:
+ * Backwards compatibility: After the data/enum class is first published, no new fields should be
+ * added, and existing fields should not be renamed, reordered, or changed to another type, unless
+ * the SDK major version is incremented. This is required for backwards compatibility with possibly
+ * mismatching SDK versions on the client side.
+ *
+ * Data class usage example:
  * ```
  * @PrivacySandboxValue
  * data class ComplicatedStructure(
@@ -39,6 +48,15 @@
  *   val maybeInterface: MyInterface?,
  * )
  * ```
+ *
+ * Enum class usage example:
+ * ```
+ * @PrivacySandboxValue
+ * enum class Direction(
+ *   UP,
+ *   DOWN,
+ * )
+ * ```
  */
 @Retention(AnnotationRetention.RUNTIME)
 @Target(AnnotationTarget.CLASS)
diff --git a/profileinstaller/profileinstaller/api/current.txt b/profileinstaller/profileinstaller/api/current.txt
index 52d8a3c..474d213 100644
--- a/profileinstaller/profileinstaller/api/current.txt
+++ b/profileinstaller/profileinstaller/api/current.txt
@@ -41,7 +41,7 @@
     method public void onResultReceived(int, Object?);
   }
 
-  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result> {
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result!> {
     ctor public ProfileInstallerInitializer();
     method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>!>!> dependencies();
diff --git a/profileinstaller/profileinstaller/api/restricted_current.txt b/profileinstaller/profileinstaller/api/restricted_current.txt
index 52d8a3c..474d213 100644
--- a/profileinstaller/profileinstaller/api/restricted_current.txt
+++ b/profileinstaller/profileinstaller/api/restricted_current.txt
@@ -41,7 +41,7 @@
     method public void onResultReceived(int, Object?);
   }
 
-  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result> {
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result!> {
     ctor public ProfileInstallerInitializer();
     method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>!>!> dependencies();
diff --git a/recyclerview/recyclerview-selection/api/current.txt b/recyclerview/recyclerview-selection/api/current.txt
index 81ea8cd..3fba41b 100644
--- a/recyclerview/recyclerview-selection/api/current.txt
+++ b/recyclerview/recyclerview-selection/api/current.txt
@@ -49,7 +49,7 @@
   @IntDef({androidx.recyclerview.selection.ItemKeyProvider.SCOPE_MAPPED, androidx.recyclerview.selection.ItemKeyProvider.SCOPE_CACHED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ItemKeyProvider.Scope {
   }
 
-  public final class MutableSelection<K> extends androidx.recyclerview.selection.Selection<K> {
+  public final class MutableSelection<K> extends androidx.recyclerview.selection.Selection<K!> {
     ctor public MutableSelection();
     method public boolean add(K);
     method public void clear();
@@ -80,7 +80,7 @@
     method public void onChanged();
   }
 
-  public class Selection<K> implements java.lang.Iterable<K> {
+  public class Selection<K> implements java.lang.Iterable<K!> {
     method public boolean contains(K?);
     method public boolean isEmpty();
     method public java.util.Iterator<K!> iterator();
@@ -139,7 +139,7 @@
     method public abstract boolean canSetStateForKey(K, boolean);
   }
 
-  public final class StableIdKeyProvider extends androidx.recyclerview.selection.ItemKeyProvider<java.lang.Long> {
+  public final class StableIdKeyProvider extends androidx.recyclerview.selection.ItemKeyProvider<java.lang.Long!> {
     ctor public StableIdKeyProvider(androidx.recyclerview.widget.RecyclerView);
     method public Long? getKey(int);
     method public int getPosition(Long);
diff --git a/recyclerview/recyclerview-selection/api/restricted_current.txt b/recyclerview/recyclerview-selection/api/restricted_current.txt
index 81ea8cd..3fba41b 100644
--- a/recyclerview/recyclerview-selection/api/restricted_current.txt
+++ b/recyclerview/recyclerview-selection/api/restricted_current.txt
@@ -49,7 +49,7 @@
   @IntDef({androidx.recyclerview.selection.ItemKeyProvider.SCOPE_MAPPED, androidx.recyclerview.selection.ItemKeyProvider.SCOPE_CACHED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ItemKeyProvider.Scope {
   }
 
-  public final class MutableSelection<K> extends androidx.recyclerview.selection.Selection<K> {
+  public final class MutableSelection<K> extends androidx.recyclerview.selection.Selection<K!> {
     ctor public MutableSelection();
     method public boolean add(K);
     method public void clear();
@@ -80,7 +80,7 @@
     method public void onChanged();
   }
 
-  public class Selection<K> implements java.lang.Iterable<K> {
+  public class Selection<K> implements java.lang.Iterable<K!> {
     method public boolean contains(K?);
     method public boolean isEmpty();
     method public java.util.Iterator<K!> iterator();
@@ -139,7 +139,7 @@
     method public abstract boolean canSetStateForKey(K, boolean);
   }
 
-  public final class StableIdKeyProvider extends androidx.recyclerview.selection.ItemKeyProvider<java.lang.Long> {
+  public final class StableIdKeyProvider extends androidx.recyclerview.selection.ItemKeyProvider<java.lang.Long!> {
     ctor public StableIdKeyProvider(androidx.recyclerview.widget.RecyclerView);
     method public Long? getKey(int);
     method public int getPosition(Long);
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
index d29d0d5..691c578 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
@@ -393,14 +393,15 @@
      * <p>
      * Building a bare-bones instance:
      *
-     * <pre>SelectionTracker<Uri> tracker = new SelectionTracker.Builder<>(
-     *        "my-uri-selection",
-     *        recyclerView,
-     *        new YourItemKeyProvider(recyclerView.getAdapter()),
-     *        new YourItemDetailsLookup(recyclerView),
-     *        StorageStrategy.createParcelableStorage(Uri.class))
-     *        .build();
-     * </pre>
+     * <pre>{@code
+     * SelectionTracker<Uri> tracker = new SelectionTracker.Builder<>(
+     *         "my-uri-selection",
+     *         recyclerView,
+     *         new YourItemKeyProvider(recyclerView.getAdapter()),
+     *         new YourItemDetailsLookup(recyclerView),
+     *         StorageStrategy.createParcelableStorage(Uri.class))
+     *     .build();
+     * }</pre>
      *
      * <p>
      * <b>Restricting which items can be selected and limiting selection size</b>
@@ -413,15 +414,16 @@
      * for single-selection, or write your own {@link SelectionPredicate} if other
      * constraints are required.
      *
+     * <pre>{@code
      * SelectionTracker<String> tracker = new SelectionTracker.Builder<>(
-     *               "my-string-selection",
-     *               recyclerView,
-     *               new YourItemKeyProvider(recyclerView.getAdapter()),
-     *               new YourItemDetailsLookup(recyclerView),
-     *               StorageStrategy.createStringStorage())
-     *        .withSelectionPredicate(SelectionPredicates#createSelectSingleAnything())
-     *        .build();
-     * </pre>
+     *         "my-string-selection",
+     *         recyclerView,
+     *         new YourItemKeyProvider(recyclerView.getAdapter()),
+     *         new YourItemDetailsLookup(recyclerView),
+     *         StorageStrategy.createStringStorage())
+     *     .withSelectionPredicate(SelectionPredicates#createSelectSingleAnything())
+     *     .build();
+     * }</pre>
      *
      * <p>
      * <b>Retaining state across Android lifecycle events</b>
@@ -465,20 +467,20 @@
      * <p>
      * Usage:
      *
-     * <pre>
-     * private SelectionTracker<Uri> mTracker;
+     * <pre>{@code
+     * private SelectionTracker<Uri> tracker;
      *
      * public void onCreate(Bundle savedInstanceState) {
-     * if (savedInstanceState != null) {
-     * mTracker.onRestoreInstanceState(savedInstanceState);
-     * }
+     *   if (savedInstanceState != null) {
+     *     tracker.onRestoreInstanceState(savedInstanceState);
+     *   }
      * }
      *
      * protected void onSaveInstanceState(Bundle outState) {
-     * super.onSaveInstanceState(outState);
-     * mTracker.onSaveInstanceState(outState);
+     *   super.onSaveInstanceState(outState);
+     *   tracker.onSaveInstanceState(outState);
      * }
-     * </pre>
+     * }</pre>
      *
      * @param <K> Selection key type. Built in support is provided for {@link String},
      *            {@link Long}, and {@link Parcelable}. {@link StorageStrategy}
diff --git a/recyclerview/recyclerview/api/current.txt b/recyclerview/recyclerview/api/current.txt
index 866a48d..a204e06 100644
--- a/recyclerview/recyclerview/api/current.txt
+++ b/recyclerview/recyclerview/api/current.txt
@@ -70,7 +70,7 @@
     method public void onRemoved(int, int);
   }
 
-  public final class ConcatAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+  public final class ConcatAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder!> {
     ctor @java.lang.SafeVarargs public ConcatAdapter(androidx.recyclerview.widget.ConcatAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder!>!...);
     ctor public ConcatAdapter(androidx.recyclerview.widget.ConcatAdapter.Config, java.util.List<? extends androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder!>!>);
     ctor @java.lang.SafeVarargs public ConcatAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder!>!...);
@@ -343,7 +343,7 @@
     method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
   }
 
-  public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+  public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH!> {
     ctor protected ListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T!>);
     ctor protected ListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
     method public java.util.List<T!> getCurrentList();
@@ -1023,7 +1023,7 @@
     field public static final int INVALID_POSITION = -1; // 0xffffffff
   }
 
-  public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+  public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2!> {
     ctor public SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2!>!);
     method public boolean areContentsTheSame(T2!, T2!);
     method public boolean areItemsTheSame(T2!, T2!);
@@ -1035,7 +1035,7 @@
     method public void onRemoved(int, int);
   }
 
-  public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2> androidx.recyclerview.widget.ListUpdateCallback {
+  public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2!> androidx.recyclerview.widget.ListUpdateCallback {
     ctor public SortedList.Callback();
     method public abstract boolean areContentsTheSame(T2!, T2!);
     method public abstract boolean areItemsTheSame(T2!, T2!);
@@ -1045,7 +1045,7 @@
     method public void onChanged(int, int, Object!);
   }
 
-  public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+  public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2!> {
     ctor public SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter<?>!);
     method public void onChanged(int, int);
     method public void onInserted(int, int);
diff --git a/recyclerview/recyclerview/api/restricted_current.txt b/recyclerview/recyclerview/api/restricted_current.txt
index bc0ed62..a48e767 100644
--- a/recyclerview/recyclerview/api/restricted_current.txt
+++ b/recyclerview/recyclerview/api/restricted_current.txt
@@ -70,7 +70,7 @@
     method public void onRemoved(int, int);
   }
 
-  public final class ConcatAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+  public final class ConcatAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.recyclerview.widget.RecyclerView.ViewHolder!> {
     ctor @java.lang.SafeVarargs public ConcatAdapter(androidx.recyclerview.widget.ConcatAdapter.Config, androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder!>!...);
     ctor public ConcatAdapter(androidx.recyclerview.widget.ConcatAdapter.Config, java.util.List<? extends androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder!>!>);
     ctor @java.lang.SafeVarargs public ConcatAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder!>!...);
@@ -343,7 +343,7 @@
     method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
   }
 
-  public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+  public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH!> {
     ctor protected ListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T!>);
     ctor protected ListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T!>);
     method public java.util.List<T!> getCurrentList();
@@ -1026,7 +1026,7 @@
     field public static final int INVALID_POSITION = -1; // 0xffffffff
   }
 
-  public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+  public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2!> {
     ctor public SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2!>!);
     method public boolean areContentsTheSame(T2!, T2!);
     method public boolean areItemsTheSame(T2!, T2!);
@@ -1038,7 +1038,7 @@
     method public void onRemoved(int, int);
   }
 
-  public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2> androidx.recyclerview.widget.ListUpdateCallback {
+  public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2!> androidx.recyclerview.widget.ListUpdateCallback {
     ctor public SortedList.Callback();
     method public abstract boolean areContentsTheSame(T2!, T2!);
     method public abstract boolean areItemsTheSame(T2!, T2!);
@@ -1048,7 +1048,7 @@
     method public void onChanged(int, int, Object!);
   }
 
-  public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+  public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2!> {
     ctor public SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter<?>!);
     method public void onChanged(int, int);
     method public void onInserted(int, int);
diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle
index dab5c07..b3d110e 100644
--- a/recyclerview/recyclerview/build.gradle
+++ b/recyclerview/recyclerview/build.gradle
@@ -19,7 +19,7 @@
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.customview:customview:1.0.0")
     implementation("androidx.customview:customview-poolingcontainer:1.0.0")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/remotecallback/remotecallback/api/current.txt b/remotecallback/remotecallback/api/current.txt
index 59631b3..771c360d 100644
--- a/remotecallback/remotecallback/api/current.txt
+++ b/remotecallback/remotecallback/api/current.txt
@@ -1,12 +1,12 @@
 // Signature format: 4.0
 package androidx.remotecallback {
 
-  public class AppWidgetProviderWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.appwidget.AppWidgetProvider implements androidx.remotecallback.CallbackReceiver<T> {
+  public class AppWidgetProviderWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.appwidget.AppWidgetProvider implements androidx.remotecallback.CallbackReceiver<T!> {
     ctor public AppWidgetProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
   }
 
-  public abstract class BroadcastReceiverWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.content.BroadcastReceiver implements androidx.remotecallback.CallbackReceiver<T> {
+  public abstract class BroadcastReceiverWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.content.BroadcastReceiver implements androidx.remotecallback.CallbackReceiver<T!> {
     ctor public BroadcastReceiverWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
     method public void onReceive(android.content.Context!, android.content.Intent!);
@@ -29,7 +29,7 @@
     method public T createRemoteCallback(android.content.Context);
   }
 
-  public abstract class ContentProviderWithCallbacks<T extends androidx.remotecallback.ContentProviderWithCallbacks> extends android.content.ContentProvider implements androidx.remotecallback.CallbackReceiver<T> {
+  public abstract class ContentProviderWithCallbacks<T extends androidx.remotecallback.ContentProviderWithCallbacks> extends android.content.ContentProvider implements androidx.remotecallback.CallbackReceiver<T!> {
     ctor public ContentProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
   }
diff --git a/remotecallback/remotecallback/api/restricted_current.txt b/remotecallback/remotecallback/api/restricted_current.txt
index 766f121..353c81f 100644
--- a/remotecallback/remotecallback/api/restricted_current.txt
+++ b/remotecallback/remotecallback/api/restricted_current.txt
@@ -1,13 +1,13 @@
 // Signature format: 4.0
 package androidx.remotecallback {
 
-  public class AppWidgetProviderWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.appwidget.AppWidgetProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
+  public class AppWidgetProviderWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.appwidget.AppWidgetProvider implements androidx.remotecallback.CallbackBase<T!> androidx.remotecallback.CallbackReceiver<T!> {
     ctor public AppWidgetProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String);
   }
 
-  public abstract class BroadcastReceiverWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.content.BroadcastReceiver implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
+  public abstract class BroadcastReceiverWithCallbacks<T extends androidx.remotecallback.CallbackReceiver> extends android.content.BroadcastReceiver implements androidx.remotecallback.CallbackBase<T!> androidx.remotecallback.CallbackReceiver<T!> {
     ctor public BroadcastReceiverWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
     method public void onReceive(android.content.Context!, android.content.Intent!);
@@ -37,7 +37,7 @@
     method public T createRemoteCallback(android.content.Context);
   }
 
-  public abstract class ContentProviderWithCallbacks<T extends androidx.remotecallback.ContentProviderWithCallbacks> extends android.content.ContentProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
+  public abstract class ContentProviderWithCallbacks<T extends androidx.remotecallback.ContentProviderWithCallbacks> extends android.content.ContentProvider implements androidx.remotecallback.CallbackBase<T!> androidx.remotecallback.CallbackReceiver<T!> {
     ctor public ContentProviderWithCallbacks();
     method public T createRemoteCallback(android.content.Context);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String?, android.os.Bundle, String);
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
index 54b013b..cc8f439 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/MigrationKotlinTest.kt
@@ -16,12 +16,14 @@
 
 package androidx.room.integration.kotlintestapp.migration
 
+import androidx.kruth.assertThrows
 import androidx.room.Room
-import androidx.room.RoomDatabase
+import androidx.room.integration.kotlintestapp.TestDatabase
 import androidx.room.migration.Migration
 import androidx.room.testing.MigrationTestHelper
 import androidx.room.util.TableInfo
 import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.driver.AndroidSQLiteDriver
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
 import java.io.FileNotFoundException
@@ -47,14 +49,12 @@
         const val TEST_DB = "migration-test"
     }
 
-    abstract class EmptyDb : RoomDatabase()
-
     @Test
     @Throws(IOException::class)
     fun giveBadResource() {
         val helper = MigrationTestHelper(
             InstrumentationRegistry.getInstrumentation(),
-            EmptyDb::class.java
+            TestDatabase::class.java
         )
         try {
             helper.createDatabase(TEST_DB, 1)
@@ -297,6 +297,57 @@
         }
     }
 
+    @Test
+    fun compatModeUsingWrongApis() {
+        assertThrows<IllegalStateException> {
+            helper.createDatabase(version = 1)
+        }.hasMessageThat().contains(
+            "MigrationTestHelper functionality returning a SQLiteConnection is not possible " +
+                "because a SupportSQLiteOpenHelper was provided during configuration (i.e. no " +
+                "SQLiteDriver was provided)."
+        )
+
+        assertThrows<IllegalStateException> {
+            helper.runMigrationsAndValidate(
+                version = 1,
+                migrations = emptyList()
+            )
+        }.hasMessageThat().contains(
+            "MigrationTestHelper functionality returning a SQLiteConnection is not possible " +
+                "because a SupportSQLiteOpenHelper was provided during configuration (i.e. no " +
+                "SQLiteDriver was provided)."
+        )
+    }
+
+    @Test
+    fun noCompatModeUsingWrongApis() {
+        val instrumentation = InstrumentationRegistry.getInstrumentation()
+        val dbFile = instrumentation.targetContext.getDatabasePath("test.db")
+        val driverHelper = MigrationTestHelper(
+            instrumentation = instrumentation,
+            fileName = dbFile.path,
+            driver = AndroidSQLiteDriver(),
+            databaseClass = MigrationDbKotlin::class
+        )
+        assertThrows<IllegalStateException> {
+            driverHelper.createDatabase(name = "test.db", version = 1)
+        }.hasMessageThat().contains(
+            "MigrationTestHelper functionality returning a SupportSQLiteDatabase is not possible " +
+                "because a SQLiteDriver was provided during configuration."
+        )
+
+        assertThrows<IllegalStateException> {
+            driverHelper.runMigrationsAndValidate(
+                name = "test.db",
+                version = 1,
+                validateDroppedTables = false
+            )
+        }.hasMessageThat().contains(
+            "MigrationTestHelper functionality returning a SupportSQLiteDatabase is not possible " +
+                "because a SQLiteDriver was provided during configuration."
+        )
+    }
+
     internal val MIGRATION_1_2: Migration = object : Migration(1, 2) {
         override fun migrate(db: SupportSQLiteDatabase) {
             db.execSQL(
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt
index f56e7c7..46d5745 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt
@@ -21,6 +21,7 @@
 import androidx.room.Database
 import androidx.room.Delete
 import androidx.room.Entity
+import androidx.room.Ignore
 import androidx.room.Insert
 import androidx.room.InvalidationTracker
 import androidx.room.OnConflictStrategy
@@ -85,6 +86,7 @@
     }
 
     @Test
+    @Ignore // Flaky test b/330789066
     fun test() {
         val invalidationTracker = database.invalidationTracker
 
diff --git a/room/integration-tests/multiplatformtestapp/build.gradle b/room/integration-tests/multiplatformtestapp/build.gradle
index dfd48c4..1368729 100644
--- a/room/integration-tests/multiplatformtestapp/build.gradle
+++ b/room/integration-tests/multiplatformtestapp/build.gradle
@@ -13,8 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 import androidx.build.KmpPlatformsKt
+import org.apache.commons.io.FileUtils
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
+import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
@@ -37,6 +41,7 @@
             dependencies {
                 implementation(libs.kotlinStdlib)
                 implementation(project(":room:room-runtime"))
+                implementation(project(":room:room-testing"))
                 implementation(project(":sqlite:sqlite-bundled"))
                 implementation(project(":kruth:kruth"))
                 implementation(libs.kotlinTest)
@@ -60,10 +65,25 @@
         nativeTest {
             dependsOn(commonTest)
         }
+        nonIosNativeTest {
+            dependsOn(nativeTest)
+        }
+        if (KmpPlatformsKt.enableMac(project)) {
+            iosTest {
+                dependsOn(nativeTest)
+            }
+        }
         targets.all { target ->
             if (target.platformType == KotlinPlatformType.native) {
-                target.compilations["test"].defaultSourceSet {
-                    dependsOn(nativeTest)
+                def test = target.compilations["test"]
+                if (target.konanTarget.family == Family.IOS) {
+                    test.defaultSourceSet {
+                        dependsOn(iosTest)
+                    }
+                } else {
+                    test.defaultSourceSet {
+                        dependsOn(nonIosNativeTest)
+                    }
                 }
             }
         }
@@ -86,6 +106,23 @@
 
 android {
     namespace "androidx.room.integration.multiplatformtestapp"
+    // TODO(b/317909626): Should be configured by Room Gradle Plugin
+    sourceSets {
+        androidTest.assets.srcDirs += files("$projectDir/schemas-ksp".toString())
+    }
+}
+
+// Copy schema files to the iOS binary output test directory that will be part of the bundle's
+// resources and read with NSBundle. This needs to be replaced with a more proper mechanism.
+// TODO(b/317909626): Should be configured by Room Gradle Plugin
+tasks.withType(KotlinNativeLink).configureEach { task ->
+    if (name.contains("linkDebugTestIos")) {
+        def inputSchemaDir = layout.projectDirectory.dir("schemas-ksp").getAsFile()
+        def outputSchemaDir = new File(task.destinationDirectory.getAsFile().get(), "/schemas-ksp")
+        task.doLast {
+            FileUtils.copyDirectory(inputSchemaDir, outputSchemaDir)
+        }
+    }
 }
 
 room {
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
index 8d2caf0..52b7c70 100644
--- a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
@@ -20,19 +20,31 @@
 import androidx.kruth.assertThrows
 import androidx.room.Room
 import androidx.room.migration.Migration
+import androidx.room.testing.MigrationTestHelper
 import androidx.sqlite.SQLiteDriver
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
-import androidx.sqlite.execSQL
 import androidx.test.platform.app.InstrumentationRegistry
 import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+
 class AutoMigrationTest : BaseAutoMigrationTest() {
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
     private val file = instrumentation.targetContext.getDatabasePath("test.db")
-    override val driver: SQLiteDriver = BundledSQLiteDriver(file.path)
+    private val driver: SQLiteDriver = BundledSQLiteDriver()
+
+    @get:Rule
+    val migrationTestHelper = MigrationTestHelper(
+        instrumentation = instrumentation,
+        driver = driver,
+        databaseClass = AutoMigrationDatabase::class,
+        fileName = file.path
+    )
+
+    override fun getTestHelper() = migrationTestHelper
 
     override fun getRoomDatabase(): AutoMigrationDatabase {
         return Room.databaseBuilder<AutoMigrationDatabase>(
@@ -43,9 +55,8 @@
 
     @Test
     fun migrationWithWrongOverride() = runTest {
-        val connection = driver.open()
         // Create database in V1
-        connection.execSQL("PRAGMA user_version = 1")
+        val connection = migrationTestHelper.createDatabase(1)
         connection.close()
 
         // Auto migrate to V2
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
index 9b81e8a..8d406e8 100644
--- a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
@@ -34,7 +34,7 @@
             context = instrumentation.targetContext,
             name = file.path,
             factory = { SampleDatabase::class.instantiateImpl() }
-        ).setDriver(BundledSQLiteDriver(file.path))
+        ).setDriver(BundledSQLiteDriver())
     }
 
     @BeforeTest
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
index b4a0849..4a36798 100644
--- a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
@@ -27,7 +27,7 @@
     override fun getRoomDatabase(): SampleDatabase {
         return Room.inMemoryDatabaseBuilder<SampleDatabase>(
             context = instrumentation.targetContext,
-        ).setDriver(BundledSQLiteDriver(":memory:"))
+        ).setDriver(BundledSQLiteDriver())
             .build()
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
index 4bf79fb..300ff8c 100644
--- a/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/androidInstrumentedTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
@@ -19,6 +19,7 @@
 import androidx.room.Room
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
 import androidx.test.platform.app.InstrumentationRegistry
+import kotlinx.coroutines.Dispatchers
 
 class SimpleQueryTest : BaseSimpleQueryTest() {
 
@@ -26,8 +27,9 @@
 
     override fun getRoomDatabase(): SampleDatabase {
         return Room.inMemoryDatabaseBuilder<SampleDatabase>(
-            context = instrumentation.targetContext,
-        ).setDriver(BundledSQLiteDriver(":memory:"))
+            context = instrumentation.targetContext
+        ).setDriver(BundledSQLiteDriver())
+            .setQueryCoroutineContext(Dispatchers.IO)
             .build()
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
index 436377d..f898ae5 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.integration.multiplatformtestapp.test
 
 import androidx.kruth.assertThat
+import androidx.kruth.assertThrows
 import androidx.room.AutoMigration
 import androidx.room.ColumnInfo
 import androidx.room.Dao
@@ -27,37 +28,27 @@
 import androidx.room.Query
 import androidx.room.RoomDatabase
 import androidx.room.Update
-import androidx.sqlite.SQLiteDriver
-import androidx.sqlite.execSQL
+import androidx.room.testing.MigrationTestHelper
 import androidx.sqlite.use
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 
 abstract class BaseAutoMigrationTest {
-    abstract val driver: SQLiteDriver
+    abstract fun getTestHelper(): MigrationTestHelper
     abstract fun getRoomDatabase(): AutoMigrationDatabase
 
     @Test
-    fun migrateFromV1ToV2() = runTest {
-        val connection = driver.open()
-        // Create database in V1
-        connection.execSQL("CREATE TABLE IF NOT EXISTS " +
-            "`AutoMigrationEntity` (`pk` INTEGER NOT NULL, PRIMARY KEY(`pk`))"
-        )
-        connection.execSQL("CREATE TABLE IF NOT EXISTS " +
-            "room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)"
-        )
-        connection.execSQL("INSERT OR REPLACE INTO " +
-            "room_master_table (id,identity_hash) VALUES(42, 'a917f82d955ea88cc98a551d197529c3')"
-        )
-        connection.execSQL("PRAGMA user_version = 1")
+    fun migrateFromV1ToLatest() = runTest {
+        val migrationTestHelper = getTestHelper()
+
+        // Create database V1
+        val connection = migrationTestHelper.createDatabase(1)
+        // Insert some data, we'll validate it is present after migration
         connection.prepare("INSERT INTO AutoMigrationEntity (pk) VALUES (?)").use {
             it.bindLong(1, 1)
             assertThat(it.step()).isFalse() // SQLITE_DONE
         }
-        connection.prepare(
-            "SELECT * FROM AutoMigrationEntity"
-        ).use { stmt ->
+        connection.prepare("SELECT * FROM AutoMigrationEntity").use { stmt ->
             assertThat(stmt.step()).isTrue()
             // Make sure that there is only 1 column in V1
             assertThat(stmt.getColumnCount()).isEqualTo(1)
@@ -67,7 +58,7 @@
         }
         connection.close()
 
-        // Auto migrate to V2
+        // Auto migrate to latest
         val dbVersion2 = getRoomDatabase()
         assertThat(dbVersion2.dao().update(AutoMigrationEntity(1, 5))).isEqualTo(1)
         assertThat(dbVersion2.dao().getSingleItem().pk).isEqualTo(1)
@@ -75,6 +66,54 @@
         dbVersion2.close()
     }
 
+    @Test
+    fun migrateFromV1ToV2() = runTest {
+        val migrationTestHelper = getTestHelper()
+
+        // Create database V1
+        val connection = migrationTestHelper.createDatabase(1)
+        // Insert some data, we'll validate it is present after migration
+        connection.prepare("INSERT INTO AutoMigrationEntity (pk) VALUES (?)").use {
+            it.bindLong(1, 1)
+            assertThat(it.step()).isFalse() // SQLITE_DONE
+        }
+        connection.close()
+
+        // Auto migrate to V2
+        migrationTestHelper.runMigrationsAndValidate(2)
+    }
+
+    @Test
+    fun misuseTestHelperAlreadyCreatedDatabase() {
+        val migrationTestHelper = getTestHelper()
+
+        // Create database V1
+        migrationTestHelper.createDatabase(1).close()
+
+        // When trying to create at V1 again, fail due to database file being already created.
+        assertThrows<IllegalStateException> {
+            migrationTestHelper.createDatabase(1)
+        }.hasMessageThat()
+            .contains("Creation of tables didn't occur while creating a new database.")
+
+        // If trying to create at V2, migration will try to run and fail.
+        assertThrows<IllegalStateException> {
+            migrationTestHelper.createDatabase(2)
+        }.hasMessageThat()
+            .contains("A migration from 1 to 2 was required but not found.")
+    }
+
+    @Test
+    fun misuseTestHelperMissingDatabaseForValidateMigrations() {
+        val migrationTestHelper = getTestHelper()
+
+        // Try to validate migrations, but fail due to no previous database created.
+        assertThrows<IllegalStateException> {
+            migrationTestHelper.runMigrationsAndValidate(2, emptyList())
+        }.hasMessageThat()
+            .contains("Creation of tables should never occur while validating migrations.")
+    }
+
     @Entity
     data class AutoMigrationEntity(
         @PrimaryKey
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
index d39179d..836f9c4 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseSimpleQueryTest.kt
@@ -18,10 +18,24 @@
 
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
+import androidx.room.execSQL
+import androidx.room.immediateTransaction
+import androidx.room.useReaderConnection
+import androidx.room.useWriterConnection
+import androidx.sqlite.SQLiteException
 import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
 import kotlin.test.Test
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
 
 abstract class BaseSimpleQueryTest {
 
@@ -93,6 +107,33 @@
     }
 
     @Test
+    fun queryFlow() = runTest {
+        val dao = getRoomDatabase().dao()
+        dao.insertItem(1)
+
+        val channel = dao.getItemListFlow().produceIn(this)
+
+        assertThat(channel.receive()).containsExactly(
+            SampleEntity(1)
+        )
+
+        dao.insertItem(2)
+        assertThat(channel.receive()).containsExactly(
+            SampleEntity(1),
+            SampleEntity(2),
+        )
+
+        dao.insertItem(3)
+        assertThat(channel.receive()).containsExactly(
+            SampleEntity(1),
+            SampleEntity(2),
+            SampleEntity(3),
+        )
+
+        channel.cancel()
+    }
+
+    @Test
     fun insertAndDelete() = runTest {
         val sampleEntity = SampleEntity(1, 1)
         val dao = getRoomDatabase().dao()
@@ -143,7 +184,7 @@
     }
 
     @Test
-    fun simpleInsertMap() = runTest {
+    fun insertMap() = runTest {
         val sampleEntity1 = SampleEntity(1, 1)
         val sampleEntity2 = SampleEntity2(1, 2)
         val dao = getRoomDatabase().dao()
@@ -157,7 +198,7 @@
     }
 
     @Test
-    fun simpleMapWithDupeColumns() = runTest {
+    fun mapWithDupeColumns() = runTest {
         val sampleEntity1 = SampleEntity(1, 1)
         val sampleEntity2 = SampleEntityCopy(1, 2)
         val dao = getRoomDatabase().dao()
@@ -171,7 +212,7 @@
     }
 
     @Test
-    fun simpleInsertNestedMap() = runTest {
+    fun insertNestedMap() = runTest {
         val sampleEntity1 = SampleEntity(1, 1)
         val sampleEntity2 = SampleEntity2(1, 2)
         val sampleEntity3 = SampleEntity3(1, 2)
@@ -187,7 +228,7 @@
     }
 
     @Test
-    fun simpleInsertNestedMapColumnMap() = runTest {
+    fun insertNestedMapColumnMap() = runTest {
         val sampleEntity1 = SampleEntity(1, 1)
         val sampleEntity2 = SampleEntity2(1, 2)
         val sampleEntity3 = SampleEntity3(1, 2)
@@ -201,4 +242,139 @@
         val map = dao.getSimpleNestedMapColumnMap()
         assertThat(map[sampleEntity1]).isEqualTo(mapOf(Pair(sampleEntity2, sampleEntity3.data3)))
     }
+
+    @Test
+    fun combineInsertAndManualWrite() = runTest {
+        val db = getRoomDatabase()
+        db.useWriterConnection { connection ->
+            db.dao().insertItem(1)
+            connection.execSQL("INSERT INTO SampleEntity (pk) VALUES (2)")
+        }
+        db.useReaderConnection { connection ->
+            val count = connection.usePrepared("SELECT count(*) FROM SampleEntity") {
+                it.step()
+                it.getLong(0)
+            }
+            assertThat(count).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun combineQueryAndManualRead() = runTest {
+        val db = getRoomDatabase()
+        val entity = SampleEntity(1, 10)
+        db.dao().insert(entity)
+        db.useReaderConnection { connection ->
+            assertThat(
+                db.dao().getItemList()
+            ).containsExactly(entity)
+            assertThat(
+                connection.usePrepared("SELECT * FROM SampleEntity") {
+                    buildList {
+                        while (it.step()) {
+                            add(SampleEntity(it.getLong(0), it.getLong(1)))
+                        }
+                    }
+                }
+            ).containsExactly(entity)
+        }
+    }
+
+    @Test
+    fun queriesAreIsolated() = runTest {
+        val db = getRoomDatabase()
+        db.dao().insertItem(22)
+
+        // Validates that Room's coroutine scope provides isolation, if one query fails
+        // it doesn't affect others.
+        val failureQueryScope = CoroutineScope(Job())
+        val successQueryScope = CoroutineScope(Job())
+        val failureDeferred = failureQueryScope.async {
+            db.useReaderConnection { connection ->
+                connection.usePrepared("SELECT * FROM WrongTableName") {
+                    assertThat(it.step()).isFalse()
+                }
+            }
+        }
+        val successDeferred = successQueryScope.async {
+            db.useReaderConnection { connection ->
+                connection.usePrepared("SELECT * FROM SampleEntity") {
+                    assertThat(it.step()).isTrue()
+                    it.getLong(0)
+                }
+            }
+        }
+        assertThrows<SQLiteException> { failureDeferred.await() }
+            .hasMessageThat().contains("no such table: WrongTableName")
+        assertThat(successDeferred.await()).isEqualTo(22)
+    }
+
+    @Test
+    fun queriesAreIsolatedWhenCancelled() = runTest {
+        val db = getRoomDatabase()
+
+        // Validates that Room's coroutine scope provides isolation, if scope doing a query is
+        // cancelled it doesn't affect others.
+        val toBeCancelledScope = CoroutineScope(Job())
+        val notCancelledScope = CoroutineScope(Job())
+        val latch = Mutex(locked = true)
+        val cancelledDeferred = toBeCancelledScope.async {
+            db.useReaderConnection { latch.withLock { } }
+            1
+        }
+        val notCancelledDeferred = notCancelledScope.async {
+            db.useReaderConnection { latch.withLock { } }
+            1
+        }
+
+        yield()
+        toBeCancelledScope.cancel()
+        latch.unlock()
+
+        assertThrows<CancellationException> {
+            cancelledDeferred.await()
+        }
+        assertThat(
+            notCancelledDeferred.await()
+        ).isEqualTo(1)
+    }
+
+    @Test
+    fun queryFlowFromManualWrite() = runTest {
+        val db = getRoomDatabase()
+
+        val channel = db.dao().getItemListFlow().produceIn(this)
+
+        assertThat(channel.receive()).isEmpty()
+
+        // Validates that a write using the connection directly will cause invalidation when
+        // a refresh is requested.
+        db.useWriterConnection { connection ->
+            connection.execSQL("INSERT INTO SampleEntity (pk) VALUES (13)")
+        }
+        db.invalidationTracker.refreshAsync()
+        assertThat(channel.receive()).containsExactly(
+            SampleEntity(13),
+        )
+
+        channel.cancel()
+    }
+
+    @Test
+    fun rollbackDaoQuery() = runTest {
+        val db = getRoomDatabase()
+        db.dao().insertItem(1)
+        db.useWriterConnection { transactor ->
+            transactor.immediateTransaction {
+                db.dao().insertItem(2)
+                rollback(Unit)
+            }
+            val count = transactor.usePrepared("SELECT count(*) FROM SampleEntity") {
+                it.step()
+                it.getLong(0)
+            }
+            assertThat(count).isEqualTo(1)
+        }
+        assertThat(db.dao().getItemList()).containsExactly(SampleEntity(1))
+    }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
index 01bceef..1deccba 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SampleDatabase.kt
@@ -30,13 +30,14 @@
 import androidx.room.Transaction
 import androidx.room.Update
 import androidx.room.Upsert
+import kotlinx.coroutines.flow.Flow
 
 @Entity
 data class SampleEntity(
     @PrimaryKey
     val pk: Long,
     @ColumnInfo(defaultValue = "0")
-    val data: Long
+    val data: Long = 0
 )
 
 @Entity
@@ -78,6 +79,9 @@
     @Query("SELECT * FROM SampleEntity")
     suspend fun getItemList(): List<SampleEntity>
 
+    @Query("SELECT * FROM SampleEntity")
+    fun getItemListFlow(): Flow<List<SampleEntity>>
+
     @Transaction
     suspend fun deleteList(pks: List<Long>, withError: Boolean = false) {
         require(!withError)
diff --git a/room/integration-tests/multiplatformtestapp/src/iosTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt b/room/integration-tests/multiplatformtestapp/src/iosTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
new file mode 100644
index 0000000..1cc894a
--- /dev/null
+++ b/room/integration-tests/multiplatformtestapp/src/iosTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.multiplatformtestapp.test
+
+import platform.Foundation.NSBundle
+
+/**
+ * Gets the schema directory path for tests with [androidx.room.testing.MigrationTestHelper].
+ *
+ * For iOS, it will be the main resource directory in the bundle.
+ */
+internal actual fun getSchemaDirectoryPath(): String {
+    return checkNotNull(NSBundle.mainBundle().resourcePath) + "/schemas-ksp"
+}
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
index 1bf1332..3e36969 100644
--- a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
@@ -17,18 +17,29 @@
 package androidx.room.integration.multiplatformtestapp.test
 
 import androidx.room.Room
+import androidx.room.testing.MigrationTestHelper
 import androidx.sqlite.SQLiteDriver
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.io.path.Path
 import kotlin.io.path.createTempFile
+import org.junit.Rule
 
 class AutoMigrationTest : BaseAutoMigrationTest() {
-    private val tempFile = createTempFile(
-        "test.db"
-    ).also { it.toFile().deleteOnExit() }
-    override val driver: SQLiteDriver = BundledSQLiteDriver(tempFile.toString())
+    private val tempFilePath = createTempFile("test.db").also { it.toFile().deleteOnExit() }
+    private val driver: SQLiteDriver = BundledSQLiteDriver()
+
+    @get:Rule
+    val migrationTestHelper = MigrationTestHelper(
+        schemaDirectoryPath = Path("schemas-ksp"),
+        databasePath = tempFilePath,
+        driver = driver,
+        databaseClass = AutoMigrationDatabase::class
+    )
+
+    override fun getTestHelper() = migrationTestHelper
 
     override fun getRoomDatabase(): AutoMigrationDatabase {
-        return Room.databaseBuilder<AutoMigrationDatabase>(tempFile.toString())
+        return Room.databaseBuilder<AutoMigrationDatabase>(tempFilePath.toString())
             .setDriver(driver).build()
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
index d36069c..e83e0f9 100644
--- a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
@@ -25,6 +25,6 @@
     override fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase> {
         val tempFile = createTempFile("test.db").also { it.toFile().deleteOnExit() }
         return Room.databaseBuilder(tempFile.toString()) { SampleDatabase::class.instantiateImpl() }
-            .setDriver(BundledSQLiteDriver(tempFile.toString()))
+            .setDriver(BundledSQLiteDriver())
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
index 8f52839..c6ec8bb 100644
--- a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
@@ -23,7 +23,7 @@
 
     override fun getRoomDatabase(): SampleDatabase {
         return Room.inMemoryDatabaseBuilder<SampleDatabase>()
-            .setDriver(BundledSQLiteDriver(":memory:"))
+            .setDriver(BundledSQLiteDriver())
             .build()
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
index df1f8024..6be6358f 100644
--- a/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/jvmTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
@@ -18,12 +18,14 @@
 
 import androidx.room.Room
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlinx.coroutines.Dispatchers
 
 class SimpleQueryTest : BaseSimpleQueryTest() {
 
     override fun getRoomDatabase(): SampleDatabase {
         return Room.inMemoryDatabaseBuilder<SampleDatabase>()
-            .setDriver(BundledSQLiteDriver(":memory:"))
+            .setDriver(BundledSQLiteDriver())
+            .setQueryCoroutineContext(Dispatchers.IO)
             .build()
     }
 }
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
index 5200b40..4300bb2 100644
--- a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/AutoMigrationTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.integration.multiplatformtestapp.test
 
 import androidx.room.Room
+import androidx.room.testing.MigrationTestHelper
 import androidx.sqlite.SQLiteDriver
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
 import kotlin.random.Random
@@ -26,10 +27,22 @@
 
 class AutoMigrationTest : BaseAutoMigrationTest() {
     private val filename = "/tmp/test-${Random.nextInt()}.db"
-    override val driver: SQLiteDriver = BundledSQLiteDriver(filename)
+    private val driver: SQLiteDriver = BundledSQLiteDriver()
+
+    private val migrationTestHelper = MigrationTestHelper(
+        schemaDirectoryPath = getSchemaDirectoryPath(),
+        fileName = filename,
+        driver = driver,
+        databaseClass = AutoMigrationDatabase::class,
+        databaseFactory = { AutoMigrationDatabase::class.instantiateImpl() }
+    )
+
+    override fun getTestHelper() = migrationTestHelper
 
     override fun getRoomDatabase(): AutoMigrationDatabase {
-        return Room.databaseBuilder(filename) { AutoMigrationDatabase::class.instantiateImpl() }
+        return Room.databaseBuilder<AutoMigrationDatabase>(filename) {
+            AutoMigrationDatabase::class.instantiateImpl()
+        }
             .setDriver(driver).build()
     }
 
@@ -40,6 +53,7 @@
 
     @AfterTest
     fun after() {
+        migrationTestHelper.finished()
         deleteDatabaseFile()
     }
 
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
index 2411bc0..17e05f8 100644
--- a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BuilderTest.kt
@@ -30,7 +30,7 @@
 
     override fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase> {
         return Room.databaseBuilder(filename) { SampleDatabase::class.instantiateImpl() }
-            .setDriver(BundledSQLiteDriver(filename))
+            .setDriver(BundledSQLiteDriver())
     }
 
     @BeforeTest
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
index 8db97f5..fdc83b7 100644
--- a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/InvalidationTest.kt
@@ -23,7 +23,7 @@
 
     override fun getRoomDatabase(): SampleDatabase {
         return Room.inMemoryDatabaseBuilder { SampleDatabase::class.instantiateImpl() }
-            .setDriver(BundledSQLiteDriver(":memory:"))
+            .setDriver(BundledSQLiteDriver())
             .build()
     }
 }
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
similarity index 73%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
index 3e91597..ba54f49 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.room.integration.multiplatformtestapp.test
+
+/**
+ * Gets the schema directory path for tests with [androidx.room.testing.MigrationTestHelper]
+ */
+internal expect fun getSchemaDirectoryPath(): String
diff --git a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
index 6257320..5a0d3c9 100644
--- a/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/nativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SimpleQueryTest.kt
@@ -18,12 +18,15 @@
 
 import androidx.room.Room
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.IO
 
 class SimpleQueryTest : BaseSimpleQueryTest() {
 
     override fun getRoomDatabase(): SampleDatabase {
         return Room.inMemoryDatabaseBuilder { SampleDatabase::class.instantiateImpl() }
-            .setDriver(BundledSQLiteDriver(":memory:"))
+            .setDriver(BundledSQLiteDriver())
+            .setQueryCoroutineContext(Dispatchers.IO)
             .build()
     }
 }
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/room/integration-tests/multiplatformtestapp/src/nonIosNativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
similarity index 61%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to room/integration-tests/multiplatformtestapp/src/nonIosNativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
index 3e91597..9afd69d 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/room/integration-tests/multiplatformtestapp/src/nonIosNativeTest/kotlin/androidx/room/integration/multiplatformtestapp/test/SchemaDirectory.kt
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.room.integration.multiplatformtestapp.test
+
+/**
+ * Gets the schema directory path for tests with [androidx.room.testing.MigrationTestHelper].
+ *
+ * For native (not iOS), it will be the directory in the project.
+ */
+// TODO(b/329526300): Investigate native resources for placing schemas.
+internal actual fun getSchemaDirectoryPath(): String = "schemas-ksp"
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index a79ba9c..dc31269 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -82,6 +82,7 @@
     coreLibraryDesugaring(libs.desugarJdkLibs)
     implementation(project(":room:room-common"))
     implementation(project(":room:room-runtime"))
+    implementation(project(":room:room-migration"))
     implementation("androidx.arch.core:core-runtime:2.2.0")
     implementation("androidx.lifecycle:lifecycle-livedata:2.6.1")
     implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
index 081c285..fa12d0a 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/migration/AutoMigrationTest.java
@@ -22,7 +22,6 @@
 import android.database.SQLException;
 
 import androidx.annotation.NonNull;
-import androidx.room.DatabaseConfiguration;
 import androidx.room.migration.Migration;
 import androidx.room.testing.MigrationTestHelper;
 import androidx.room.util.TableInfo;
@@ -147,10 +146,6 @@
                 true,
                 MIGRATION_1_0
         );
-        DatabaseConfiguration config = helper.databaseConfiguration;
-        assertThat(config).isNotNull();
-        assertThat(config.migrationContainer.findMigrationPath(1, 2)).isNotNull();
-        assertThat(config.migrationContainer.findMigrationPath(1, 2)).isNotEmpty();
     }
 
     private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
index 0e71304..bf9a575 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/MultiInstanceInvalidationTest.java
@@ -238,6 +238,7 @@
         assertFalse(changed3.second.await(3, TimeUnit.SECONDS));
     }
 
+    @Ignore // Flaky b/330519843
     @Test
     public void invalidationCausesNoLoop() throws Exception {
         final SampleDatabase db1 = openDatabase(true);
diff --git a/room/room-common/api/restricted_current.txt b/room/room-common/api/restricted_current.txt
index a618b5c..d6c9b38 100644
--- a/room/room-common/api/restricted_current.txt
+++ b/room/room-common/api/restricted_current.txt
@@ -1,6 +1,12 @@
 // Signature format: 4.0
 package androidx.room {
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class AmbiguousColumnResolver {
+    method public static int[][] resolve(String[] resultColumns, String[][] mappings);
+    method public static int[][] resolve(java.util.List<java.lang.String> resultColumns, String[][] mappings);
+    field public static final androidx.room.AmbiguousColumnResolver INSTANCE;
+  }
+
   @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets=kotlin.annotation.AnnotationTarget.CLASS) public @interface AutoMigration {
     method public abstract int from();
     method public abstract Class<?> spec() default java.lang.Object;
diff --git a/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt b/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
index c810bde..ba3536d 100644
--- a/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
+++ b/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
@@ -47,7 +47,7 @@
  * are continuous.
  *
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 public object AmbiguousColumnResolver {
 
     /**
@@ -61,6 +61,23 @@
      */
     @JvmStatic
     public fun resolve(
+        resultColumns: List<String>,
+        mappings: Array<Array<String>>
+    ): Array<IntArray> {
+        return resolve(resultColumns.toTypedArray(), mappings)
+    }
+
+    /**
+     * Maps query result column indices to result object columns.
+     *
+     * @param resultColumns The ordered result column names.
+     * @param mappings      An array containing the list of result object column names that must be
+     *                      mapped to indices of `resultColumns`.
+     * @return An array with the same dimensions as `mappings` whose values correspond to the
+     * index in `resultColumns` that match the object column at `mappings[i][j]`.
+     */
+    @JvmStatic
+    public fun resolve(
         resultColumns: Array<String>,
         mappings: Array<Array<String>>
     ): Array<IntArray> {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
index 94cd311..0171412 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -193,6 +193,12 @@
          * respectively.
          */
         fun getArrayName(componentTypeName: XTypeName): XTypeName {
+            componentTypeName.java.let {
+                require(it !is JWildcardTypeName || it.lowerBounds.isEmpty()) {
+                    "Can't have contra-variant component types in Java arrays. Found '$it'."
+                }
+            }
+
             val (java, kotlin) = when (componentTypeName) {
                 PRIMITIVE_BOOLEAN ->
                     JArrayTypeName.of(JTypeName.BOOLEAN) to BOOLEAN_ARRAY
@@ -210,9 +216,16 @@
                     JArrayTypeName.of(JTypeName.FLOAT) to FLOAT_ARRAY
                 PRIMITIVE_DOUBLE ->
                     JArrayTypeName.of(JTypeName.DOUBLE) to DOUBLE_ARRAY
-                else ->
-                    JArrayTypeName.of(componentTypeName.java) to
-                        ARRAY.parameterizedBy(componentTypeName.kotlin)
+                else -> {
+                    componentTypeName.java.let {
+                        if (it is JWildcardTypeName) {
+                            JArrayTypeName.of(it.upperBounds.single())
+                        } else {
+                            JArrayTypeName.of(it)
+                        }
+                    } to
+                    ARRAY.parameterizedBy(componentTypeName.kotlin)
+                }
             }
             return XTypeName(
                 java = java,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
index 15a9043..a0549e5 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableParameterElement.kt
@@ -20,6 +20,7 @@
 import androidx.room.compiler.processing.XExecutableParameterElement
 import androidx.room.compiler.processing.XMemberContainer
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.isArray
 import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_METHOD_PARAMETER
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
 import androidx.room.compiler.processing.util.sanitizeAsJavaParameterName
@@ -74,7 +75,7 @@
     private fun createAsMemberOf(container: XType?): KspType {
         check(container is KspType?)
         val resolvedType = parameter.type.resolve()
-        return env.wrap(
+        val type = env.wrap(
             originalAnnotations = parameter.type.annotations,
             ksType = parameter.typeAsMemberOf(
                 functionDeclaration = enclosingElement.declaration,
@@ -91,6 +92,13 @@
                 asMemberOf = container,
             )
         )
+        // In KSP2 the varargs have the component type instead of the array type. We make it always
+        // return the array type in XProcessing.
+        return if (isVarArgs() && !type.isArray()) {
+            env.getArrayType(type)
+        } else {
+            type
+        }
     }
 
     override fun kindName(): String {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index a10ed56b..93b7fb7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -181,6 +181,39 @@
             }
     }
 
+    private val _constructors by lazy {
+        if (isAnnotationClass()) {
+            emptyList()
+        } else {
+            val constructors = declaration.getConstructors().toList()
+            buildList {
+                addAll(
+                    constructors.map { env.wrapFunctionDeclaration(it) as XConstructorElement }
+                )
+                constructors
+                    .filter { it.hasOverloads() }
+                    .forEach { addAll(enumerateSyntheticConstructors(it)) }
+
+                // To match KAPT if all params in the primary constructor have default values then
+                // synthesize a no-arg constructor if one is not already present.
+                val hasNoArgConstructor = constructors.any { it.parameters.isEmpty() }
+                if (!hasNoArgConstructor) {
+                    declaration.primaryConstructor?.let {
+                        if (!it.hasOverloads() && it.parameters.all { it.hasDefault }) {
+                            add(
+                                KspSyntheticConstructorElement(
+                                    env = env,
+                                    declaration = it,
+                                    valueParameters = emptyList()
+                                )
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private val _declaredFields by lazy {
         _declaredProperties.filter {
             it.declaration.hasBackingField
@@ -328,36 +361,7 @@
     }
 
     override fun getConstructors(): List<XConstructorElement> {
-        if (isAnnotationClass()) {
-            return emptyList()
-        }
-        val constructors = declaration.getConstructors().toList()
-
-        return buildList {
-            addAll(
-                constructors.map { env.wrapFunctionDeclaration(it) as XConstructorElement }
-            )
-            constructors
-                .filter { it.hasOverloads() }
-                .forEach { addAll(enumerateSyntheticConstructors(it)) }
-
-            // To match KAPT if all params in the primary constructor have default values then
-            // synthesize a no-arg constructor if one is not already present.
-            val hasNoArgConstructor = constructors.any { it.parameters.isEmpty() }
-            if (!hasNoArgConstructor) {
-                declaration.primaryConstructor?.let {
-                    if (!it.hasOverloads() && it.parameters.all { it.hasDefault }) {
-                        add(
-                            KspSyntheticConstructorElement(
-                                env = env,
-                                declaration = it,
-                                valueParameters = emptyList()
-                            )
-                        )
-                    }
-                }
-            }
-        }
+        return _constructors
     }
 
     private fun enumerateSyntheticConstructors(
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
index e93dfd6..f42e1f5 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/codegen/XTypeNameTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.codegen
 
 import androidx.kruth.assertThat
+import androidx.kruth.assertThrows
 import androidx.room.compiler.processing.XNullability
 import com.squareup.kotlinpoet.INT
 import com.squareup.kotlinpoet.SHORT
@@ -176,4 +177,25 @@
         assertThat(XTypeName.getProducerExtendsName(typeName).kotlin)
             .isEqualTo(XTypeName.UNAVAILABLE_KTYPE_NAME)
     }
+
+    @Test
+    fun arrays() {
+        XTypeName.getArrayName(Number::class.asClassName()).let {
+            assertThat(it.toString(CodeLanguage.JAVA))
+                .isEqualTo("java.lang.Number[]")
+            assertThat(it.toString(CodeLanguage.KOTLIN))
+                .isEqualTo("kotlin.Array<kotlin.Number>")
+        }
+        XTypeName.getArrayName(
+            XTypeName.getProducerExtendsName(Number::class.asClassName())).let {
+            assertThat(it.toString(CodeLanguage.JAVA))
+                .isEqualTo("java.lang.Number[]")
+            assertThat(it.toString(CodeLanguage.KOTLIN))
+                .isEqualTo("kotlin.Array<out kotlin.Number>")
+        }
+        assertThrows<IllegalArgumentException> {
+            XTypeName.getArrayName(XTypeName.getConsumerSuperName(Number::class.asClassName()))
+        }.hasMessageThat().isEqualTo("Can't have contra-variant component types in Java " +
+            "arrays. Found '? super java.lang.Number'.")
+    }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index fab71288..d60da50 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -108,6 +108,7 @@
             package foo.bar;
             interface Baz {
                 void method(String... inputs);
+                void methodPrimitive(int... inputs);
             }
             """.trimIndent()
         )
@@ -115,7 +116,16 @@
             sources = listOf(subject)
         ) {
             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
-            assertThat(element.getMethodByJvmName("method").isVarArgs()).isTrue()
+            element.getMethodByJvmName("method").let { method ->
+                assertThat(method.isVarArgs()).isTrue()
+                assertThat(method.parameters.single().type.asTypeName()).isEqualTo(
+                    XTypeName.getArrayName(String::class.asClassName()))
+            }
+            element.getMethodByJvmName("methodPrimitive").let { method ->
+                assertThat(method.isVarArgs()).isTrue()
+                assertThat(method.parameters.single().type.asTypeName()).isEqualTo(
+                    XTypeName.getArrayName(XTypeName.PRIMITIVE_INT))
+            }
         }
     }
 
@@ -128,6 +138,7 @@
                 fun method(vararg inputs: String)
                 suspend fun suspendMethod(vararg inputs: String)
                 fun method2(vararg inputs: String, arg: Int)
+                fun methodPrimitive(vararg inputs: Int)
                 fun String.extFun(vararg inputs: String)
             }
             """.trimIndent()
@@ -141,6 +152,10 @@
                 assertThat(method.isVarArgs()).isTrue()
                 assertThat(method.parameters).hasSize(1)
                 assertThat(method.parameters.single().isVarArgs()).isTrue()
+                assertThat(method.parameters.single().type.asTypeName()).isEqualTo(
+                    XTypeName.getArrayName(
+                        XTypeName.getProducerExtendsName(String::class.asClassName()))
+                )
             }
 
             element.getMethodByJvmName("suspendMethod").let { suspendMethod ->
@@ -149,6 +164,12 @@
                 assertThat(
                     suspendMethod.parameters.first { it.name == "inputs" }.isVarArgs()
                 ).isTrue()
+                assertThat(
+                    suspendMethod.parameters.first { it.name == "inputs" }.type.asTypeName()
+                ).isEqualTo(
+                    XTypeName.getArrayName(
+                        XTypeName.getProducerExtendsName(String::class.asClassName()))
+                )
             }
 
             element.getMethodByJvmName("extFun").let { extFun ->
@@ -157,6 +178,10 @@
                 // kapt messed with parameter names, sometimes the synthetic parameter can use the
                 // second parameter's name.
                 assertThat(extFun.parameters.get(1).isVarArgs()).isTrue()
+                assertThat(extFun.parameters.get(1).type.asTypeName()).isEqualTo(
+                    XTypeName.getArrayName(
+                        XTypeName.getProducerExtendsName(String::class.asClassName()))
+                )
             }
 
             element.getMethodByJvmName("method2").let { method2 ->
@@ -164,6 +189,19 @@
                 assertThat(method2.parameters).hasSize(2)
                 assertThat(method2.parameters.first { it.name == "inputs" }.isVarArgs())
                     .isTrue()
+                assertThat(method2.parameters.first { it.name == "inputs" }.type.asTypeName())
+                    .isEqualTo(
+                        XTypeName.getArrayName(
+                            XTypeName.getProducerExtendsName(String::class.asClassName()))
+                    )
+            }
+            element.getMethodByJvmName("methodPrimitive").let { method ->
+                assertThat(method.isVarArgs()).isTrue()
+                assertThat(method.parameters).hasSize(1)
+                assertThat(method.parameters.single().isVarArgs()).isTrue()
+                assertThat(method.parameters.single().type.asTypeName()).isEqualTo(
+                    XTypeName.getArrayName(XTypeName.PRIMITIVE_INT)
+                )
             }
         }
     }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
index 9578966..7b40165 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/ext/xpoet_ext.kt
@@ -87,6 +87,7 @@
         XClassName.get(ROOM_PACKAGE, "RoomOpenDelegate", "ValidationResult")
     val STATEMENT_UTIL = XClassName.get("$ROOM_PACKAGE.util", "SQLiteStatementUtil")
     val CONNECTION_UTIL = XClassName.get("$ROOM_PACKAGE.util", "SQLiteConnectionUtil")
+    val FLOW_UTIL = XClassName.get("$ROOM_PACKAGE.coroutines", "FlowUtil")
 }
 
 object RoomAnnotationTypeNames {
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 3fdef4e..135ebb4 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
@@ -240,8 +240,8 @@
             )
             null
         } catch (th: Throwable) {
-            // For debugging support include exception message in a WARN message.
-            context.logger.w("Unable to read schema file: ${th.message ?: ""}")
+            // For debugging support include exception message in an error too.
+            context.logger.e("Unable to read schema file: ${th.message ?: ""}")
             context.logger.e(
                 element,
                 invalidAutoMigrationSchema(
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt
index 3090f83..31c6b80 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/AmbiguousColumnIndexAdapter.kt
@@ -18,7 +18,6 @@
 
 import androidx.room.AmbiguousColumnResolver
 import androidx.room.compiler.codegen.XCodeBlock
-import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.DoubleArrayLiteral
@@ -84,24 +83,13 @@
                     typeName = XTypeName.getArrayName(
                         XTypeName.getArrayName(XTypeName.PRIMITIVE_INT)
                     ),
-                    assignExpr = if (scope.useDriverApi) {
-                        XCodeBlock.of(
-                            language,
-                            "%T.resolve(%M(%L), %L)",
-                            RoomTypeNames.AMBIGUOUS_COLUMN_RESOLVER,
-                            RoomTypeNames.STATEMENT_UTIL.packageMember("getColumnNames"),
-                            cursorVarName,
-                            rowMappings
-                        )
-                    } else {
-                        XCodeBlock.of(
-                            language,
-                            "%T.resolve(%L.getColumnNames(), %L)",
-                            RoomTypeNames.AMBIGUOUS_COLUMN_RESOLVER,
-                            cursorVarName,
-                            rowMappings
-                        )
-                    }
+                    assignExpr = XCodeBlock.of(
+                        language,
+                        "%T.resolve(%L.getColumnNames(), %L)",
+                        RoomTypeNames.AMBIGUOUS_COLUMN_RESOLVER,
+                        cursorVarName,
+                        rowMappings
+                    )
                 )
             }
         }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
index fc5e066..2a7ff10 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineFlowResultBinder.kt
@@ -16,17 +16,24 @@
 
 package androidx.room.solver.query.result
 
+import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.codegen.XCodeBlock
+import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.addLocalVal
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XPropertySpec
+import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.XType
 import androidx.room.ext.ArrayLiteral
 import androidx.room.ext.CallableTypeSpecBuilder
 import androidx.room.ext.CommonTypeNames
+import androidx.room.ext.Function1TypeSpec
 import androidx.room.ext.RoomCoroutinesTypeNames.COROUTINES_ROOM
+import androidx.room.ext.RoomTypeNames
+import androidx.room.ext.SQLiteDriverTypeNames
 import androidx.room.solver.CodeGenScope
 
 /**
- * Binds the result of a of a Kotlin Coroutine Flow<T>
+ * Binds the result of a Kotlin Coroutine Flow<T>
  */
 class CoroutineFlowResultBinder(
     val typeArg: XType,
@@ -76,4 +83,124 @@
             )
         }
     }
+
+    override fun isMigratedToDriver() = adapter?.isMigratedToDriver() == true
+
+    override fun convertAndReturn(
+        sqlQueryVar: String,
+        dbProperty: XPropertySpec,
+        bindStatement: CodeGenScope.(String) -> Unit,
+        returnTypeName: XTypeName,
+        inTransaction: Boolean,
+        scope: CodeGenScope
+    ) {
+        val arrayOfTableNamesLiteral = ArrayLiteral(
+            scope.language,
+            CommonTypeNames.STRING,
+            *tableNames.toTypedArray()
+        )
+        when (scope.language) {
+            CodeLanguage.JAVA -> convertAndReturnJava(
+                sqlQueryVar,
+                dbProperty,
+                bindStatement,
+                inTransaction,
+                arrayOfTableNamesLiteral,
+                scope
+            )
+
+            CodeLanguage.KOTLIN -> convertAndReturnKotlin(
+                sqlQueryVar,
+                dbProperty,
+                bindStatement,
+                inTransaction,
+                arrayOfTableNamesLiteral,
+                scope
+            )
+        }
+    }
+
+    private fun convertAndReturnJava(
+        sqlQueryVar: String,
+        dbProperty: XPropertySpec,
+        bindStatement: CodeGenScope.(String) -> Unit,
+        inTransaction: Boolean,
+        arrayOfTableNamesLiteral: XCodeBlock,
+        scope: CodeGenScope
+    ) {
+        val connectionVar = scope.getTmpVar("_connection")
+        val statementVar = scope.getTmpVar("_stmt")
+        scope.builder.addStatement(
+            "return %M(%N, %L, %L, %L)",
+            RoomTypeNames.FLOW_UTIL.packageMember("createFlow"),
+            dbProperty,
+            inTransaction,
+            arrayOfTableNamesLiteral,
+            // TODO(b/322387497): Generate lambda syntax if possible
+            Function1TypeSpec(
+                language = scope.language,
+                parameterTypeName = SQLiteDriverTypeNames.CONNECTION,
+                parameterName = connectionVar,
+                returnTypeName = typeArg.asTypeName()
+            ) {
+                val functionScope = scope.fork()
+                val outVar = functionScope.getTmpVar("_result")
+                val functionCode = functionScope.builder.apply {
+                    addLocalVal(
+                        statementVar,
+                        SQLiteDriverTypeNames.STATEMENT,
+                        "%L.prepare(%L)",
+                        connectionVar,
+                        sqlQueryVar
+                    )
+                    beginControlFlow("try")
+                    bindStatement(functionScope, statementVar)
+                    adapter?.convert(outVar, statementVar, functionScope)
+                    addStatement("return %L", outVar)
+                    nextControlFlow("finally")
+                    addStatement("%L.close()", statementVar)
+                    endControlFlow()
+                }.build()
+                this.addCode(functionCode)
+            }
+        )
+    }
+
+    private fun convertAndReturnKotlin(
+        sqlQueryVar: String,
+        dbProperty: XPropertySpec,
+        bindStatement: CodeGenScope.(String) -> Unit,
+        inTransaction: Boolean,
+        arrayOfTableNamesLiteral: XCodeBlock,
+        scope: CodeGenScope
+    ) {
+        val connectionVar = scope.getTmpVar("_connection")
+        val statementVar = scope.getTmpVar("_stmt")
+        scope.builder.apply {
+            beginControlFlow(
+                "return %M(%N, %L, %L) { %L ->",
+                RoomTypeNames.FLOW_UTIL.packageMember("createFlow"),
+                dbProperty,
+                inTransaction,
+                arrayOfTableNamesLiteral,
+                connectionVar
+            )
+            addLocalVal(
+                statementVar,
+                SQLiteDriverTypeNames.STATEMENT,
+                "%L.prepare(%L)",
+                connectionVar,
+                sqlQueryVar
+            )
+            beginControlFlow("try")
+            bindStatement(scope, statementVar)
+            val outVar = scope.getTmpVar("_result")
+            adapter?.convert(outVar, statementVar, scope)
+            addStatement("%L", outVar)
+            nextControlFlow("finally")
+            addStatement("%L.close()", statementVar)
+            endControlFlow()
+            endControlFlow()
+        }
+    }
 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 1f0d37d..f74dcbb 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -1370,7 +1370,8 @@
             USER, AUTOMIGRATION,
         ) { _, invocation ->
             invocation.assertCompilationResult {
-                hasErrorCount(1)
+                hasErrorCount(2)
+                hasErrorContaining("Unable to read schema file")
                 hasErrorContaining(
                     ProcessorErrors.invalidAutoMigrationSchema(
                         1,
@@ -1401,7 +1402,8 @@
             USER, AUTOMIGRATION,
         ) { _, invocation ->
             invocation.assertCompilationResult {
-                hasErrorCount(1)
+                hasErrorCount(2)
+                hasErrorContaining("Unable to read schema file")
                 hasErrorContaining(
                     invalidAutoMigrationSchema(
                         1,
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
index 0abb567..64bc0a4 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/coroutines.kt
@@ -1,16 +1,11 @@
-import android.database.Cursor
-import androidx.room.CoroutinesRoom
 import androidx.room.RoomDatabase
-import androidx.room.RoomSQLiteQuery
-import androidx.room.RoomSQLiteQuery.Companion.acquire
+import androidx.room.coroutines.createFlow
 import androidx.room.util.appendPlaceholders
 import androidx.room.util.getColumnIndexOrThrow
 import androidx.room.util.getLastInsertedRowId
 import androidx.room.util.getTotalChangedRows
 import androidx.room.util.performSuspending
-import androidx.room.util.query
 import androidx.sqlite.SQLiteStatement
-import java.util.concurrent.Callable
 import javax.`annotation`.processing.Generated
 import kotlin.Int
 import kotlin.Long
@@ -40,43 +35,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return CoroutinesRoom.createFlow(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity> {
-      public override fun call(): MyEntity {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createFlow(__db, false, arrayOf("MyEntity")) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          error("The query result was empty, but expected a single row to return a NON-NULL object of type <MyEntity>.")
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override fun getFlowNullable(vararg arg: String?): Flow<MyEntity?> {
@@ -86,44 +73,35 @@
     appendPlaceholders(_stringBuilder, _inputSize)
     _stringBuilder.append(")")
     val _sql: String = _stringBuilder.toString()
-    val _argCount: Int = 0 + _inputSize
-    val _statement: RoomSQLiteQuery = acquire(_sql, _argCount)
-    var _argIndex: Int = 1
-    for (_item: String? in arg) {
-      if (_item == null) {
-        _statement.bindNull(_argIndex)
-      } else {
-        _statement.bindString(_argIndex, _item)
-      }
-      _argIndex++
-    }
-    return CoroutinesRoom.createFlow(__db, false, arrayOf("MyEntity"), object : Callable<MyEntity?>
-        {
-      public override fun call(): MyEntity? {
-        val _cursor: Cursor = query(__db, _statement, false, null)
-        try {
-          val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_cursor, "pk")
-          val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_cursor, "other")
-          val _result: MyEntity?
-          if (_cursor.moveToFirst()) {
-            val _tmpPk: Int
-            _tmpPk = _cursor.getInt(_cursorIndexOfPk)
-            val _tmpOther: String
-            _tmpOther = _cursor.getString(_cursorIndexOfOther)
-            _result = MyEntity(_tmpPk,_tmpOther)
+    return createFlow(__db, false, arrayOf("MyEntity")) { _connection ->
+      val _stmt: SQLiteStatement = _connection.prepare(_sql)
+      try {
+        var _argIndex: Int = 1
+        for (_item: String? in arg) {
+          if (_item == null) {
+            _stmt.bindNull(_argIndex)
           } else {
-            _result = null
+            _stmt.bindText(_argIndex, _item)
           }
-          return _result
-        } finally {
-          _cursor.close()
+          _argIndex++
         }
+        val _cursorIndexOfPk: Int = getColumnIndexOrThrow(_stmt, "pk")
+        val _cursorIndexOfOther: Int = getColumnIndexOrThrow(_stmt, "other")
+        val _result: MyEntity?
+        if (_stmt.step()) {
+          val _tmpPk: Int
+          _tmpPk = _stmt.getLong(_cursorIndexOfPk).toInt()
+          val _tmpOther: String
+          _tmpOther = _stmt.getText(_cursorIndexOfOther)
+          _result = MyEntity(_tmpPk,_tmpOther)
+        } else {
+          _result = null
+        }
+        _result
+      } finally {
+        _stmt.close()
       }
-
-      protected fun finalize() {
-        _statement.release()
-      }
-    })
+    }
   }
 
   public override suspend fun getSuspendList(vararg arg: String?): List<MyEntity> {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
index 449a95f..379cfe0 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_map_ambiguousIndexAdapter.kt
@@ -4,7 +4,6 @@
 import androidx.room.RoomSQLiteQuery
 import androidx.room.RoomSQLiteQuery.Companion.acquire
 import androidx.room.util.getColumnIndex
-import androidx.room.util.getColumnNames
 import androidx.room.util.performBlocking
 import androidx.room.util.query
 import androidx.room.util.wrapMappedColumns
@@ -38,8 +37,9 @@
     return performBlocking(__db, true, false) { _connection ->
       val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _cursorIndices: Array<IntArray> = AmbiguousColumnResolver.resolve(getColumnNames(_stmt),
-            arrayOf(arrayOf("id", "name"), arrayOf("id", "userId", "text")))
+        val _cursorIndices: Array<IntArray> =
+            AmbiguousColumnResolver.resolve(_stmt.getColumnNames(), arrayOf(arrayOf("id", "name"),
+            arrayOf("id", "userId", "text")))
         val _result: MutableMap<User, MutableList<Comment>> =
             LinkedHashMap<User, MutableList<Comment>>()
         while (_stmt.step()) {
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt
index 375ce65..571867b 100644
--- a/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/queryResultAdapter_nestedMap_ambiguousIndexAdapter.kt
@@ -1,6 +1,5 @@
 import androidx.room.AmbiguousColumnResolver
 import androidx.room.RoomDatabase
-import androidx.room.util.getColumnNames
 import androidx.room.util.performBlocking
 import androidx.sqlite.SQLiteStatement
 import java.nio.ByteBuffer
@@ -34,9 +33,9 @@
     return performBlocking(__db, true, false) { _connection ->
       val _stmt: SQLiteStatement = _connection.prepare(_sql)
       try {
-        val _cursorIndices: Array<IntArray> = AmbiguousColumnResolver.resolve(getColumnNames(_stmt),
-            arrayOf(arrayOf("id", "name"), arrayOf("userId", "url", "data"), arrayOf("id", "userId",
-            "text")))
+        val _cursorIndices: Array<IntArray> =
+            AmbiguousColumnResolver.resolve(_stmt.getColumnNames(), arrayOf(arrayOf("id", "name"),
+            arrayOf("userId", "url", "data"), arrayOf("id", "userId", "text")))
         val _result: MutableMap<User, MutableMap<Avatar, MutableList<Comment>>> =
             LinkedHashMap<User, MutableMap<Avatar, MutableList<Comment>>>()
         while (_stmt.step()) {
diff --git a/room/room-migration/build.gradle b/room/room-migration/build.gradle
index c51ca24..4aed7d4 100644
--- a/room/room-migration/build.gradle
+++ b/room/room-migration/build.gradle
@@ -66,6 +66,10 @@
         }
         nativeMain {
             dependsOn(commonMain)
+            dependencies {
+                api(libs.okio)
+                implementation(libs.kotlinSerializationJsonOkio)
+            }
         }
         targets.all { target ->
             if (target.platformType == KotlinPlatformType.native) {
diff --git a/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/FieldBundle.kt b/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/FieldBundle.kt
index a902d66..eea6dc5 100644
--- a/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/FieldBundle.kt
+++ b/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/FieldBundle.kt
@@ -33,7 +33,7 @@
     @SerialName("affinity")
     val affinity: String,
     @SerialName("notNull")
-    val isNonNull: Boolean,
+    val isNonNull: Boolean = false,
     @SerialName("defaultValue")
     val defaultValue: String? = null,
 ) : SchemaEquality<FieldBundle> {
diff --git a/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/SchemaBundle.kt b/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/SchemaBundle.kt
index 050eaa2..8921134 100644
--- a/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/SchemaBundle.kt
+++ b/room/room-migration/src/commonMain/kotlin/androidx/room/migration/bundle/SchemaBundle.kt
@@ -20,6 +20,7 @@
 
 import androidx.annotation.RestrictTo
 import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.json.ClassDiscriminatorMode
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonContentPolymorphicSerializer
@@ -42,9 +43,12 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 const val SCHEMA_LATEST_FORMAT_VERSION = 1
 
+@OptIn(ExperimentalSerializationApi::class) // due to prettyPrintIndex
 internal val json = Json {
     // The schema files are meant to be human readable and are checked-in into repositories.
     prettyPrint = true
+    // Keep index to 2 spaces as that is what we used before kotlinx-serialization
+    prettyPrintIndent = "  "
     // Don't output class discriminator as that would encode library class names into JSON file
     // making implementation details harder to refactor. When reading, we use a content inspector
     // that will perform polymorphic deserialization.
diff --git a/room/room-migration/src/jvmTest/kotlin/androidx/room/migration/bundle/SerializationTest.kt b/room/room-migration/src/jvmTest/kotlin/androidx/room/migration/bundle/SerializationTest.kt
index 7bd26e0..ab2a049 100644
--- a/room/room-migration/src/jvmTest/kotlin/androidx/room/migration/bundle/SerializationTest.kt
+++ b/room/room-migration/src/jvmTest/kotlin/androidx/room/migration/bundle/SerializationTest.kt
@@ -20,6 +20,9 @@
 import java.io.ByteArrayInputStream
 import java.io.FileInputStream
 import java.io.FileNotFoundException
+import java.nio.file.Path
+import kotlin.io.path.Path
+import kotlin.io.path.inputStream
 import kotlin.test.Test
 import kotlinx.serialization.SerializationException
 
@@ -44,4 +47,52 @@
             FileInputStream("/fake/file/path").use { SchemaBundle.deserialize(it) }
         }
     }
+
+    /**
+     * Validates old schemas that didn't have [FieldBundle.isNonNull] ('notNull'),
+     * added in ag/2579620
+     */
+    @Test
+    fun missingFieldBundleNotNull() {
+        getSchemaPath("missing_field_notnull").inputStream().use {
+            SchemaBundle.deserialize(it)
+        }
+    }
+
+    /**
+     * Validates old schemas that didn't have [DatabaseBundle.views] ('views'),
+     * added in aosp/731045
+     */
+    @Test
+    fun missingDatabaseBundleViews() {
+        getSchemaPath("missing_database_views").inputStream().use {
+            SchemaBundle.deserialize(it)
+        }
+    }
+
+    /**
+     * Validates old schemas that didn't have [FieldBundle.defaultValue] ('defaultValue'),
+     * added in aosp/825803
+     */
+    @Test
+    fun missingFieldBundleDefaultValue() {
+        getSchemaPath("missing_field_defaultvalue").inputStream().use {
+            SchemaBundle.deserialize(it)
+        }
+    }
+
+    /**
+     * Validates old schemas that didn't have [IndexBundle.orders] ('orders'),
+     * added in aosp/1707963
+     */
+    @Test
+    fun missingIndexBundleOrders() {
+        getSchemaPath("missing_index_orders").inputStream().use {
+            SchemaBundle.deserialize(it)
+        }
+    }
+
+    private fun getSchemaPath(name: String): Path {
+        return Path("src/jvmTest/test-data/$name.json")
+    }
 }
diff --git a/room/room-migration/src/jvmTest/test-data/missing_database_views.json b/room/room-migration/src/jvmTest/test-data/missing_database_views.json
new file mode 100644
index 0000000..52eb9ac
--- /dev/null
+++ b/room/room-migration/src/jvmTest/test-data/missing_database_views.json
@@ -0,0 +1,34 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "12345",
+    "entities": [
+      {
+        "tableName": "Entity",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk` INTEGER NOT NULL, PRIMARY KEY(`pk`))",
+        "fields": [
+          {
+            "fieldPath": "pk",
+            "columnName": "pk",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "pk"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '12345')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/room-migration/src/jvmTest/test-data/missing_field_defaultvalue.json b/room/room-migration/src/jvmTest/test-data/missing_field_defaultvalue.json
new file mode 100644
index 0000000..52eb9ac
--- /dev/null
+++ b/room/room-migration/src/jvmTest/test-data/missing_field_defaultvalue.json
@@ -0,0 +1,34 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "12345",
+    "entities": [
+      {
+        "tableName": "Entity",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk` INTEGER NOT NULL, PRIMARY KEY(`pk`))",
+        "fields": [
+          {
+            "fieldPath": "pk",
+            "columnName": "pk",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "pk"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '12345')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/room-migration/src/jvmTest/test-data/missing_field_notnull.json b/room/room-migration/src/jvmTest/test-data/missing_field_notnull.json
new file mode 100644
index 0000000..05bda24
--- /dev/null
+++ b/room/room-migration/src/jvmTest/test-data/missing_field_notnull.json
@@ -0,0 +1,32 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "12345",
+    "entities": [
+      {
+        "tableName": "Entity",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk` INTEGER, PRIMARY KEY(`pk`))",
+        "fields": [
+          {
+            "fieldPath": "pk",
+            "columnName": "pk",
+            "affinity": "INTEGER"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "pk"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '12345')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/room-migration/src/jvmTest/test-data/missing_index_orders.json b/room/room-migration/src/jvmTest/test-data/missing_index_orders.json
new file mode 100644
index 0000000..7a8ff87
--- /dev/null
+++ b/room/room-migration/src/jvmTest/test-data/missing_index_orders.json
@@ -0,0 +1,42 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "12345",
+    "entities": [
+      {
+        "tableName": "Entity",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk` INTEGER, PRIMARY KEY(`pk`))",
+        "fields": [
+          {
+            "fieldPath": "pk",
+            "columnName": "pk",
+            "affinity": "INTEGER"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "pk"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_AutoMigrationEntity_data",
+            "unique": false,
+            "columnNames": [
+              "data"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_Entity_data` ON `${TABLE_NAME}` (`data`)"
+          }
+        ],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '12345')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/room-migration/src/nativeMain/kotlin/androidx/room/migration/bundle/SchemaBundle.native.kt b/room/room-migration/src/nativeMain/kotlin/androidx/room/migration/bundle/SchemaBundle.native.kt
index 41b8b2e..bb9dfd3 100644
--- a/room/room-migration/src/nativeMain/kotlin/androidx/room/migration/bundle/SchemaBundle.native.kt
+++ b/room/room-migration/src/nativeMain/kotlin/androidx/room/migration/bundle/SchemaBundle.native.kt
@@ -17,8 +17,13 @@
 package androidx.room.migration.bundle
 
 import androidx.annotation.RestrictTo
+import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.okio.decodeFromBufferedSource
+import kotlinx.serialization.json.okio.encodeToBufferedSink
+import okio.BufferedSink
+import okio.BufferedSource
 
 @Serializable
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -33,4 +38,15 @@
         return formatVersion == other.formatVersion &&
             SchemaEqualityUtil.checkSchemaEquality(database, other.database)
     }
+
+    companion object {
+        @OptIn(ExperimentalSerializationApi::class) // For decodeFromBufferedSource
+        fun deserialize(source: BufferedSource): SchemaBundle =
+            json.decodeFromBufferedSource(source)
+
+        @OptIn(ExperimentalSerializationApi::class) // For encodeToBufferedSink
+        fun serialize(bundle: SchemaBundle, sink: BufferedSink) {
+            json.encodeToBufferedSink(bundle, sink)
+        }
+    }
 }
diff --git a/room/room-runtime/api/current.txt b/room/room-runtime/api/current.txt
index fe0f7f8..7b91ca2 100644
--- a/room/room-runtime/api/current.txt
+++ b/room/room-runtime/api/current.txt
@@ -32,6 +32,7 @@
 
   public class InvalidationTracker {
     method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer observer);
+    method public final void refreshAsync();
     method public void refreshVersionsAsync();
     method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer observer);
     field public static final androidx.room.InvalidationTracker.Companion Companion;
@@ -170,6 +171,8 @@
 
   public final class RoomDatabaseKt {
     method public static kotlinx.coroutines.flow.Flow<java.util.Set<java.lang.String>> invalidationTrackerFlow(androidx.room.RoomDatabase, String[] tables, optional boolean emitInitialState);
+    method public static suspend <R> Object? useReaderConnection(androidx.room.RoomDatabase, kotlin.jvm.functions.Function2<? super androidx.room.Transactor,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend <R> Object? useWriterConnection(androidx.room.RoomDatabase, kotlin.jvm.functions.Function2<? super androidx.room.Transactor,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
     method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
   }
 
diff --git a/room/room-runtime/api/restricted_current.txt b/room/room-runtime/api/restricted_current.txt
index dfc296a..7039636 100644
--- a/room/room-runtime/api/restricted_current.txt
+++ b/room/room-runtime/api/restricted_current.txt
@@ -2,14 +2,14 @@
 package androidx.room {
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class CoroutinesRoom {
-    method public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<R> callable);
+    method @Deprecated public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<R> callable);
     method @Deprecated public static suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R>);
     method @Deprecated public static suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R>);
     field public static final androidx.room.CoroutinesRoom.Companion Companion;
   }
 
   public static final class CoroutinesRoom.Companion {
-    method public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<R> callable);
+    method @Deprecated public <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, java.util.concurrent.Callable<R> callable);
     method @Deprecated public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R>);
     method @Deprecated public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.Continuation<? super R>);
   }
@@ -141,6 +141,7 @@
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void addWeakObserver(androidx.room.InvalidationTracker.Observer observer);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, boolean inTransaction, java.util.concurrent.Callable<T?> computeFunction);
     method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T> createLiveData(String[] tableNames, java.util.concurrent.Callable<T?> computeFunction);
+    method public final void refreshAsync();
     method public void refreshVersionsAsync();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
     method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer observer);
@@ -288,6 +289,8 @@
 
   public final class RoomDatabaseKt {
     method public static kotlinx.coroutines.flow.Flow<java.util.Set<java.lang.String>> invalidationTrackerFlow(androidx.room.RoomDatabase, String[] tables, optional boolean emitInitialState);
+    method public static suspend <R> Object? useReaderConnection(androidx.room.RoomDatabase, kotlin.jvm.functions.Function2<? super androidx.room.Transactor,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method public static suspend <R> Object? useWriterConnection(androidx.room.RoomDatabase, kotlin.jvm.functions.Function2<? super androidx.room.Transactor,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
     method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
   }
 
@@ -392,6 +395,14 @@
 
 }
 
+package androidx.room.coroutines {
+
+  public final class FlowUtil {
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <R> kotlinx.coroutines.flow.Flow<R> createFlow(androidx.room.RoomDatabase db, boolean inTransaction, String[] tableNames, kotlin.jvm.functions.Function1<? super androidx.sqlite.SQLiteConnection,? extends R> block);
+  }
+
+}
+
 package androidx.room.migration {
 
   public interface AutoMigrationSpec {
@@ -415,7 +426,7 @@
 
 package androidx.room.paging {
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T!> {
     ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.room.RoomSQLiteQuery, boolean, boolean, java.lang.String!...);
     ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.room.RoomSQLiteQuery, boolean, java.lang.String!...);
     ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase, androidx.sqlite.db.SupportSQLiteQuery, boolean, boolean, java.lang.String!...);
@@ -489,7 +500,6 @@
 
   public final class SQLiteStatementUtil {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int getColumnIndexOrThrow(androidx.sqlite.SQLiteStatement stmt, String name);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static String[] getColumnNames(androidx.sqlite.SQLiteStatement statement);
   }
 
   @RestrictTo({androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class StringUtil {
@@ -504,7 +514,7 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TableInfo {
     ctor @Deprecated public TableInfo(String name, java.util.Map<java.lang.String,androidx.room.util.TableInfo.Column> columns, java.util.Set<androidx.room.util.TableInfo.ForeignKey> foreignKeys);
-    ctor public TableInfo(String name, java.util.Map<java.lang.String,androidx.room.util.TableInfo.Column> columns, java.util.Set<androidx.room.util.TableInfo.ForeignKey> foreignKeys, java.util.Set<androidx.room.util.TableInfo.Index>? indices);
+    ctor public TableInfo(String name, java.util.Map<java.lang.String,androidx.room.util.TableInfo.Column> columns, java.util.Set<androidx.room.util.TableInfo.ForeignKey> foreignKeys, optional java.util.Set<androidx.room.util.TableInfo.Index>? indices);
     method @Deprecated public static androidx.room.util.TableInfo read(androidx.sqlite.db.SupportSQLiteDatabase database, String tableName);
     method public static androidx.room.util.TableInfo read(androidx.sqlite.SQLiteConnection connection, String tableName);
     field public static final int CREATED_FROM_DATABASE = 2; // 0x2
diff --git a/room/room-runtime/build.gradle b/room/room-runtime/build.gradle
index 2de218e..b2fa665 100644
--- a/room/room-runtime/build.gradle
+++ b/room/room-runtime/build.gradle
@@ -22,12 +22,12 @@
  * modifying its settings.
  */
 
+
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 import androidx.build.Publish
-import androidx.build.SdkHelperKt
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.konan.target.KonanTarget
+import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
@@ -198,14 +198,11 @@
                 test.defaultSourceSet {
                     dependsOn(nativeTest)
                 }
-                if (target.konanTarget == KonanTarget.LINUX_X64.INSTANCE) {
+                if (target.konanTarget.family == Family.LINUX) {
                     // For tests in Linux host, statically include androidx's compiled SQLite
                     // via a generated C interop definition
                     createCinteropFromArchiveConfiguration(test, configurations["sqliteSharedArchive"])
-                } else if (
-                        target.konanTarget == KonanTarget.MACOS_X64.INSTANCE ||
-                                target.konanTarget == KonanTarget.MACOS_ARM64.INSTANCE
-                ) {
+                } else if (target.konanTarget.family == Family.OSX) {
                     // For tests in Mac host, link to shared SQLite library included in MacOS
                     test.kotlinOptions.freeCompilerArgs += [
                             "-linker-options", "-lsqlite3"
diff --git a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt
index 5c64129..398be07 100644
--- a/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt
+++ b/room/room-runtime/src/androidInstrumentedTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt
@@ -44,8 +44,10 @@
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
     private val file = instrumentation.targetContext.getDatabasePath("test.db")
 
+    override val fileName = file.path
+
     override fun getDriver(): SQLiteDriver {
-        return BundledSQLiteDriver(file.path)
+        return BundledSQLiteDriver()
     }
 
     @BeforeTest
@@ -63,7 +65,12 @@
     @Test
     fun reusingConnectionOnBlocking() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var count = 0
         withContext(NewThreadDispatcher()) {
             pool.useConnection(isReadOnly = true) { initialConnection ->
@@ -84,7 +91,12 @@
     @Test
     fun newThreadDispatcherDoesNotAffectThreadConfinement() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         val job = launch(Dispatchers.IO) {
             pool.useReaderConnection {
                 delay(500)
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/CoroutinesRoom.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/CoroutinesRoom.android.kt
index 84ff12e..dfd95da 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/CoroutinesRoom.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/CoroutinesRoom.android.kt
@@ -18,16 +18,12 @@
 
 import android.os.CancellationSignal
 import androidx.annotation.RestrictTo
+import androidx.room.coroutines.createFlow as createFlowCommon
 import androidx.room.util.getCoroutineContext
 import java.util.concurrent.Callable
 import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
@@ -88,41 +84,13 @@
         }
 
         @JvmStatic
+        @Deprecated("No longer called by generated implementation")
         public fun <R> createFlow(
             db: RoomDatabase,
             inTransaction: Boolean,
             tableNames: Array<String>,
             callable: Callable<R>
-        ): Flow<@JvmSuppressWildcards R> = flow {
-            coroutineScope {
-                // Observer channel receives signals from the invalidation tracker to emit queries.
-                val observerChannel = Channel<Unit>(Channel.CONFLATED)
-                val observer = object : InvalidationTracker.Observer(tableNames) {
-                    override fun onInvalidated(tables: Set<String>) {
-                        observerChannel.trySend(Unit)
-                    }
-                }
-                observerChannel.trySend(Unit) // Initial signal to perform first query.
-                // Use the database context minus the Job since the collector already has one and
-                // the child coroutine should be tied to it.
-                val queryContext = db.getCoroutineContext(inTransaction).minusKey(Job)
-                val resultChannel = Channel<R>()
-                launch(queryContext) {
-                    db.invalidationTracker.addObserver(observer)
-                    try {
-                        // Iterate until cancelled, transforming observer signals to query results
-                        // to be emitted to the flow.
-                        for (signal in observerChannel) {
-                            val result = callable.call()
-                            resultChannel.send(result)
-                        }
-                    } finally {
-                        db.invalidationTracker.removeObserver(observer)
-                    }
-                }
-
-                emitAll(resultChannel)
-            }
-        }
+        ): Flow<@JvmSuppressWildcards R> =
+            createFlowCommon(db, inTransaction, tableNames) { callable.call() }
     }
 }
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt
index 006ffa0..1307781 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/DatabaseConfiguration.android.kt
@@ -783,7 +783,8 @@
         return isMigrationRequiredExt(fromVersion, toVersion)
     }
 
-    internal fun copy(
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    fun copy(
         context: Context = this.context,
         name: String? = this.name,
         sqliteOpenHelperFactory: SupportSQLiteOpenHelper.Factory? = this.sqliteOpenHelperFactory,
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
index f37b490..52ca268 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/InvalidationTracker.android.kt
@@ -148,10 +148,8 @@
      *
      * This function should be called after any write operation is performed on the database,
      * such that tracked tables and its associated observers are notified if invalidated.
-     *
-     * @see sync
      */
-    internal actual fun refreshAsync() {
+    actual fun refreshAsync() {
         implementation.refreshInvalidationAsync(onRefreshScheduled, onRefreshCompleted)
     }
 
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/Room.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/Room.android.kt
index 56a97d2..d6a942a 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/Room.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/Room.android.kt
@@ -26,9 +26,9 @@
     internal const val LOG_TAG = "ROOM"
 
     /**
-     * The master table where room keeps its metadata information.
+     * The master table name where Room keeps its metadata information.
      */
-    const val MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME
+    actual const val MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME
 
     /**
      * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomConnectionManager.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomConnectionManager.android.kt
index 6b3c7ae..1e6b4ec 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomConnectionManager.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomConnectionManager.android.kt
@@ -35,10 +35,11 @@
 internal actual class RoomConnectionManager : BaseRoomConnectionManager {
 
     override val configuration: DatabaseConfiguration
-    override val connectionPool: ConnectionPool
     override val openDelegate: RoomOpenDelegate
     override val callbacks: List<RoomDatabase.Callback>
 
+    private val connectionPool: ConnectionPool
+
     internal val supportOpenHelper: SupportSQLiteOpenHelper?
         get() = (connectionPool as? SupportConnectionPool)?.supportDriver?.openHelper
 
@@ -49,6 +50,8 @@
         openDelegate: RoomOpenDelegate
     ) {
         this.configuration = config
+        this.openDelegate = openDelegate
+        this.callbacks = config.callbacks ?: emptyList()
         if (config.sqliteDriver == null) {
             // Compatibility mode due to no driver provided, instead a driver (SupportSQLiteDriver)
             // is created that wraps SupportSQLite* APIs. The underlying SupportSQLiteDatabase will
@@ -69,18 +72,18 @@
             this.connectionPool = if (configuration.name == null) {
                 // An in-memory database must use a single connection pool.
                 newSingleConnectionPool(
-                    driver = DriverWrapper(config.sqliteDriver)
+                    driver = DriverWrapper(config.sqliteDriver),
+                    fileName = ":memory:"
                 )
             } else {
                 newConnectionPool(
                     driver = DriverWrapper(config.sqliteDriver),
+                    fileName = configuration.name,
                     maxNumOfReaders = configuration.journalMode.getMaxNumberOfReaders(),
                     maxNumOfWriters = configuration.journalMode.getMaxNumberOfWriters()
                 )
             }
         }
-        this.openDelegate = openDelegate
-        this.callbacks = config.callbacks ?: emptyList()
         init()
     }
 
@@ -90,6 +93,7 @@
     ) {
         this.configuration = config
         this.openDelegate = NoOpOpenDelegate()
+        this.callbacks = config.callbacks ?: emptyList()
         // Compatibility mode due to no driver provided, the SupportSQLiteDriver and
         // SupportConnectionPool are created. A Room onOpen callback is installed so that the
         // SupportSQLiteDatabase is extracted out of the RoomOpenHelper installed.
@@ -98,7 +102,6 @@
         this.connectionPool = SupportConnectionPool(
             SupportSQLiteDriver(supportOpenHelperFactory.invoke(configWithCompatibilityCallback))
         )
-        this.callbacks = config.callbacks ?: emptyList()
         init()
     }
 
@@ -198,8 +201,9 @@
     private class SupportConnectionPool(
         val supportDriver: SupportSQLiteDriver
     ) : ConnectionPool {
-        private val supportConnection by lazy(LazyThreadSafetyMode.NONE) {
-            SupportPooledConnection(supportDriver.open())
+        private val supportConnection by lazy(LazyThreadSafetyMode.PUBLICATION) {
+            val fileName = supportDriver.openHelper.databaseName ?: ":memory:"
+            SupportPooledConnection(supportDriver.open(fileName))
         }
 
         override suspend fun <R> useConnection(
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
index f587588..f585bab 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt
@@ -43,7 +43,7 @@
 import androidx.room.support.PrePackagedCopyOpenHelper
 import androidx.room.support.PrePackagedCopyOpenHelperFactory
 import androidx.room.support.QueryInterceptorOpenHelperFactory
-import androidx.room.util.contains as containsExt
+import androidx.room.util.contains as containsCommon
 import androidx.room.util.findAndInstantiateDatabaseImpl
 import androidx.room.util.findMigrationPath as findMigrationPathExt
 import androidx.room.util.getCoroutineContext
@@ -611,6 +611,10 @@
 
     /**
      * Use a connection to perform database operations.
+     *
+     * This function is for internal access to the pool, it is an unconfined coroutine function to
+     * be used by Room generated code paths. For the public version see [useReaderConnection] and
+     * [useWriterConnection].
      */
     internal actual suspend fun <R> useConnection(
         isReadOnly: Boolean,
@@ -641,6 +645,8 @@
      * @return A Cursor obtained by running the given query in the Room database.
      */
     open fun query(query: String, args: Array<out Any?>?): Cursor {
+        assertNotMainThread()
+        assertNotSuspendingTransaction()
         return openHelper.writableDatabase.query(SimpleSQLiteQuery(query, args))
     }
 
@@ -1779,7 +1785,7 @@
          *
          * @param migrations List of available migrations.
          */
-        open fun addMigrations(migrations: List<Migration>) {
+        actual open fun addMigrations(migrations: List<Migration>) {
             migrations.forEach(::addMigration)
         }
 
@@ -1789,7 +1795,8 @@
          *
          * @param migration the migration to add.
          */
-        internal actual fun addMigration(migration: Migration) {
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        actual fun addMigration(migration: Migration) {
             val start = migration.startVersion
             val end = migration.endVersion
             val targetMap = migrations.getOrPut(start) { TreeMap<Int, Migration>() }
@@ -1831,8 +1838,8 @@
          * @param endVersion End version of the migration
          * @return True if it contains a migration with the same start-end version, false otherwise.
          */
-        fun contains(startVersion: Int, endVersion: Int): Boolean {
-            return this.containsExt(startVersion, endVersion)
+        actual fun contains(startVersion: Int, endVersion: Int): Boolean {
+            return this.containsCommon(startVersion, endVersion)
         }
 
         internal actual fun getSortedNodes(
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteConnection.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteConnection.android.kt
index a80722a..470036a 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteConnection.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteConnection.android.kt
@@ -16,10 +16,12 @@
 
 package androidx.room.driver
 
+import androidx.annotation.RestrictTo
 import androidx.sqlite.SQLiteConnection
 import androidx.sqlite.db.SupportSQLiteDatabase
 
-internal class SupportSQLiteConnection(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class SupportSQLiteConnection(
     val db: SupportSQLiteDatabase
 ) : SQLiteConnection {
     override fun prepare(sql: String): SupportSQLiteStatement {
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteDriver.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteDriver.android.kt
index a5801f3..70fe31b 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteDriver.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteDriver.android.kt
@@ -16,13 +16,15 @@
 
 package androidx.room.driver
 
+import androidx.annotation.RestrictTo
 import androidx.sqlite.SQLiteDriver
 import androidx.sqlite.db.SupportSQLiteOpenHelper
 
-internal class SupportSQLiteDriver(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class SupportSQLiteDriver(
     val openHelper: SupportSQLiteOpenHelper
 ) : SQLiteDriver {
-    override fun open(): SupportSQLiteConnection {
+    override fun open(fileName: String): SupportSQLiteConnection {
         return SupportSQLiteConnection(openHelper.writableDatabase)
     }
 }
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
index b3ec4a1..13f49dd 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
@@ -18,6 +18,7 @@
 
 import android.database.Cursor
 import android.database.DatabaseUtils
+import androidx.annotation.RestrictTo
 import androidx.sqlite.SQLiteStatement
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteProgram
@@ -26,7 +27,8 @@
 
 private typealias SupportStatement = androidx.sqlite.db.SupportSQLiteStatement
 
-internal sealed class SupportSQLiteStatement(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+sealed class SupportSQLiteStatement(
     protected val db: SupportSQLiteDatabase,
     protected val sql: String,
 ) : SQLiteStatement {
diff --git a/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/InvalidationTrackerTest.kt b/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/InvalidationTrackerTest.kt
index 90eb6d5..f3f192c 100644
--- a/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/InvalidationTrackerTest.kt
+++ b/room/room-runtime/src/androidUnitTest/kotlin/androidx/room/InvalidationTrackerTest.kt
@@ -585,7 +585,7 @@
 
         val preparedQueries = mutableListOf<String>()
 
-        override fun open(): SQLiteConnection {
+        override fun open(fileName: String): SQLiteConnection {
             return FakeSQLiteConnection()
         }
 
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
index 8562ad9..413f4e8 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/InvalidationTracker.kt
@@ -92,10 +92,8 @@
      *
      * This function should be called after any write operation is performed on the database,
      * such that tracked tables and its associated observers are notified if invalidated.
-     *
-     * @see sync
      */
-    internal fun refreshAsync()
+    fun refreshAsync()
 
     /**
      * Stops invalidation tracker operations.
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/Room.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/Room.kt
index 24c13d7..6035a4b 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/Room.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/Room.kt
@@ -19,4 +19,9 @@
 /**
  * Entry point for building and initializing a [RoomDatabase].
  */
-expect object Room
+expect object Room {
+    /**
+     * The master table name where Room keeps its metadata information.
+     */
+    val MASTER_TABLE_NAME: String
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
index 3cfd90e..e8d3c30 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
@@ -16,9 +16,9 @@
 
 package androidx.room
 
+import androidx.annotation.RestrictTo
 import androidx.room.RoomDatabase.JournalMode.TRUNCATE
 import androidx.room.RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING
-import androidx.room.coroutines.ConnectionPool
 import androidx.room.util.findMigrationPath
 import androidx.room.util.isMigrationRequired
 import androidx.sqlite.SQLiteConnection
@@ -35,10 +35,10 @@
  * Base class for Room's database connection manager, responsible for opening and managing such
  * connections, including performing migrations if necessary and validating schema.
  */
-internal abstract class BaseRoomConnectionManager {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+abstract class BaseRoomConnectionManager {
 
     protected abstract val configuration: DatabaseConfiguration
-    protected abstract val connectionPool: ConnectionPool
     protected abstract val openDelegate: RoomOpenDelegate
     protected abstract val callbacks: List<RoomDatabase.Callback>
 
@@ -51,10 +51,8 @@
     protected inner class DriverWrapper(
         private val actual: SQLiteDriver
     ) : SQLiteDriver {
-        override fun open(): SQLiteConnection {
-            // TODO(b/317973999): Try to validate connections point to the same filename as the
-            //                    one in the database configuration provided in the builder.
-            return configureConnection(actual.open())
+        override fun open(fileName: String): SQLiteConnection {
+            return configureConnection(actual.open(fileName))
         }
     }
 
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
index 4d035f6..919cf1a 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomDatabase.kt
@@ -22,16 +22,17 @@
 import androidx.room.concurrent.CloseBarrier
 import androidx.room.migration.AutoMigrationSpec
 import androidx.room.migration.Migration
-import androidx.room.util.contains
 import androidx.room.util.isAssignableFrom
 import androidx.sqlite.SQLiteConnection
 import androidx.sqlite.SQLiteDriver
+import androidx.sqlite.SQLiteException
 import kotlin.coroutines.CoroutineContext
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 import kotlin.reflect.KClass
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.withContext
 
 /**
  * Base class for all Room databases. All classes that are annotated with [Database] must
@@ -42,7 +43,7 @@
  *
  * @see Database
  */
-expect abstract class RoomDatabase {
+expect abstract class RoomDatabase() {
 
     /**
      * The invalidation tracker for this database.
@@ -186,6 +187,10 @@
 
     /**
      * Use a connection to perform database operations.
+     *
+     * This function is for internal access to the pool, it is an unconfined coroutine function to
+     * be used by Room generated code paths. For the public version see [useReaderConnection] and
+     * [useWriterConnection].
      */
     internal suspend fun <R> useConnection(isReadOnly: Boolean, block: suspend (Transactor) -> R): R
 
@@ -254,7 +259,7 @@
      * A container to hold migrations. It also allows querying its contents to find migrations
      * between two versions.
      */
-    class MigrationContainer {
+    class MigrationContainer() {
         /**
          * Returns the map of available migrations where the key is the start version of the
          * migration, and the value is a map of (end version -> Migration).
@@ -264,12 +269,31 @@
         fun getMigrations(): Map<Int, Map<Int, Migration>>
 
         /**
+         * Adds the given migrations to the list of available migrations. If 2 migrations have the
+         * same start-end versions, the latter migration overrides the previous one.
+         *
+         * @param migrations List of available migrations.
+         */
+        fun addMigrations(migrations: List<Migration>)
+
+        /**
          * Add a [Migration] to the container. If the container already has a migration with the
          * same start-end versions then it will be overwritten.
          *
          * @param migration the migration to add.
          */
-        internal fun addMigration(migration: Migration)
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        fun addMigration(migration: Migration)
+
+        /**
+         * Indicates if the given migration is contained within the [MigrationContainer] based
+         * on its start-end versions.
+         *
+         * @param startVersion Start version of the migration.
+         * @param endVersion End version of the migration
+         * @return True if it contains a migration with the same start-end version, false otherwise.
+         */
+        fun contains(startVersion: Int, endVersion: Int): Boolean
 
         /**
          * Returns a pair corresponding to an entry in the map of available migrations whose key
@@ -317,6 +341,69 @@
     }
 }
 
+/**
+ * Acquires a READ connection, suspending while waiting if none is available and then calling the
+ * [block] to use the connection once it is acquired. A [RoomDatabase] will have one or more READ
+ * connections. The connection to use in the [block] is an instance of [Transactor] that provides
+ * the capabilities for performing nested transactions.
+ *
+ * Using the connection after [block] completes is prohibited.
+ *
+ * The connection will be confined to the coroutine on which [block] executes, attempting to use
+ * the connection from a different coroutine will result in an error.
+ *
+ * If the current coroutine calling this function already has a confined connection, then that
+ * connection is used.
+ *
+ * A connection is a limited resource and should not be held for more than it is needed. The best
+ * practice in using connections is to avoid executing long-running computations within the [block].
+ * If a caller has to wait too long to acquire a connection a [SQLiteException] will be thrown due
+ * to a timeout.
+ *
+ * @param block The code to use the connection.
+ * @throws SQLiteException when the database is closed or a thread confined connection needs to be
+ * upgraded or there is a timeout acquiring a connection.
+ *
+ * @see [useWriterConnection]
+ */
+suspend fun <R> RoomDatabase.useReaderConnection(
+    block: suspend (Transactor) -> R
+): R = withContext(getCoroutineScope().coroutineContext) {
+    useConnection(isReadOnly = true, block)
+}
+
+/**
+ * Acquires a WRITE connection, suspending while waiting if none is available and then calling the
+ * [block] to use the connection once it is acquired. A [RoomDatabase] will have only one WRITE
+ * connection. The connection to use in the [block] is an instance of [Transactor] that provides the
+ * capabilities for performing nested transactions.
+ *
+ * Using the connection after [block] completes is prohibited.
+ *
+ * The connection will be confined to the coroutine on which [block] executes, attempting to use
+ * the connection from a different coroutine will result in an error.
+ *
+ * If the current coroutine calling this function already has a confined connection, then that
+ * connection is used as long as it isn't required to be upgraded to a writer. If an upgrade is
+ * required then a [SQLiteException] is thrown.
+ *
+ * A connection is a limited resource and should not be held for more than it is needed. The best
+ * practice in using connections is to avoid executing long-running computations within the [block].
+ * If a caller has to wait too long to acquire a connection a [SQLiteException] will be thrown due
+ * to a timeout.
+ *
+ * @param block The code to use the connection.
+ * @throws SQLiteException when the database is closed or a thread confined connection needs to be
+ * upgraded or there is a timeout acquiring a connection.
+ *
+ * @see [useReaderConnection]
+ */
+suspend fun <R> RoomDatabase.useWriterConnection(
+    block: suspend (Transactor) -> R
+): R = withContext(getCoroutineScope().coroutineContext) {
+    useConnection(isReadOnly = false, block)
+}
+
 internal fun RoomDatabase.validateAutoMigrations(configuration: DatabaseConfiguration) {
     val autoMigrationSpecs = mutableMapOf<KClass<out AutoMigrationSpec>, AutoMigrationSpec>()
     val requiredAutoMigrationSpecs = getRequiredAutoMigrationSpecClasses()
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
index 0e2a3bd..f50a613 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
@@ -43,15 +43,14 @@
      * Using the connection after [block] completes is prohibited.
      *
      * The connection will be confined to the coroutine on which [block] executes, attempting to use
-     * from another coroutine is an error.
+     * the connection from a different coroutine will result in an error.
      *
      * If the current coroutine calling this function already has a confined connection, then that
-     * connection is used as long as the connection isn't required to be upgraded to a writer.
-     * If an upgrade is required then a [SQLiteException] is thrown.
+     * connection is used as long as it isn't required to be upgraded to a writer. If an upgrade is
+     * required then a [SQLiteException] is thrown.
      *
-     * A connection is a resource and shouldn't be held more than it needs to, therefore try not to
-     * do long-running computations within the [block]. If a caller has to wait too long to
-     * acquire a connection a [SQLiteException] will be thrown due to a timeout.
+     * If a caller has to wait too long to acquire a connection a [SQLiteException] will be thrown
+     * due to a timeout.
      *
      * @param isReadOnly Whether to use a reader or a writer connection.
      * @param block The code to use the connection.
@@ -74,10 +73,11 @@
  * in-memory databases whose schema and data are isolated to a database connection.
  *
  * @param driver The driver from which to request the connection to be opened.
+ * @param fileName The database file name.
  * @return The newly created connection pool
  */
-internal fun newSingleConnectionPool(driver: SQLiteDriver): ConnectionPool =
-    ConnectionPoolImpl(driver)
+internal fun newSingleConnectionPool(driver: SQLiteDriver, fileName: String): ConnectionPool =
+    ConnectionPoolImpl(driver, fileName)
 
 /**
  * Creates a new [ConnectionPool] with multiple connections separated by readers and writers.
@@ -87,15 +87,17 @@
  * DELETE or PERSIST) then it is recommended to create a pool of one writer and one reader.
  *
  * @param driver The driver from which to request new connections to be opened.
+ * @param fileName The database file name.
  * @param maxNumOfReaders The maximum number of connections to be opened and used as readers.
  * @param maxNumOfWriters The maximum number of connections to be opened and used as writers.
  * @return The newly created connection pool
  */
 internal fun newConnectionPool(
     driver: SQLiteDriver,
+    fileName: String,
     maxNumOfReaders: Int,
     maxNumOfWriters: Int
-): ConnectionPool = ConnectionPoolImpl(driver, maxNumOfReaders, maxNumOfWriters)
+): ConnectionPool = ConnectionPoolImpl(driver, fileName, maxNumOfReaders, maxNumOfWriters)
 
 /**
  * Defines an object that provides 'raw' access to a connection.
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt
index 562bbfb..887111f 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt
@@ -61,17 +61,19 @@
 
     constructor(
         driver: SQLiteDriver,
+        fileName: String
     ) {
         this.driver = driver
         this.readers = Pool(
             capacity = 1,
-            connectionFactory = { driver.open() }
+            connectionFactory = { driver.open(fileName) }
         )
         this.writers = readers
     }
 
     constructor(
         driver: SQLiteDriver,
+        fileName: String,
         maxNumOfReaders: Int,
         maxNumOfWriters: Int,
     ) {
@@ -85,7 +87,7 @@
         this.readers = Pool(
             capacity = maxNumOfReaders,
             connectionFactory = {
-                driver.open().also { newConnection ->
+                driver.open(fileName).also { newConnection ->
                     // Enforce to be read only (might be disabled by a YOLO developer)
                     newConnection.execSQL("PRAGMA query_only = 1")
                 }
@@ -93,7 +95,7 @@
         )
         this.writers = Pool(
             capacity = maxNumOfWriters,
-            connectionFactory = { driver.open() }
+            connectionFactory = { driver.open(fileName) }
         )
     }
 
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/FlowBuilder.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/FlowBuilder.kt
new file mode 100644
index 0000000..658fcde
--- /dev/null
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/FlowBuilder.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("FlowUtil")
+
+package androidx.room.coroutines
+
+import androidx.annotation.RestrictTo
+import androidx.room.InvalidationTracker
+import androidx.room.RoomDatabase
+import androidx.room.util.getCoroutineContext
+import androidx.room.util.internalPerform
+import androidx.sqlite.SQLiteConnection
+import kotlin.jvm.JvmName
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.launch
+
+// TODO(b/329315924): Migrate to Flow based machinery.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+fun <R> createFlow(
+    db: RoomDatabase,
+    inTransaction: Boolean,
+    tableNames: Array<String>,
+    block: (SQLiteConnection) -> R
+): Flow<R> = flow {
+    coroutineScope {
+        // Observer channel receives signals from the invalidation tracker to emit queries.
+        val observerChannel = Channel<Unit>(Channel.CONFLATED)
+        val observer = object : InvalidationTracker.Observer(tableNames) {
+            override fun onInvalidated(tables: Set<String>) {
+                observerChannel.trySend(Unit)
+            }
+        }
+        observerChannel.trySend(Unit) // Initial signal to perform first query.
+        val resultChannel = Channel<R>()
+        launch(db.getCoroutineContext(inTransaction).minusKey(Job)) {
+            db.invalidationTracker.subscribe(observer)
+            try {
+                // Iterate until cancelled, transforming observer signals to query results
+                // to be emitted to the flow.
+                for (signal in observerChannel) {
+                    val result = db.internalPerform(true, inTransaction) { connection ->
+                        val rawConnection = (connection as RawConnectionAccessor).rawConnection
+                        block.invoke(rawConnection)
+                    }
+                    resultChannel.send(result)
+                }
+            } finally {
+                db.invalidationTracker.unsubscribe(observer)
+            }
+        }
+
+        emitAll(resultChannel)
+    }
+}
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/MigrationUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/MigrationUtil.kt
index 85119cd..d4f233e 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/MigrationUtil.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/MigrationUtil.kt
@@ -60,7 +60,7 @@
  * @param endVersion End version of the migration
  * @return True if it contains a migration with the same start-end version, false otherwise.
  */
-@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // On purpose and only in Android source set.
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // On purpose in non-common platforms source sets.
 internal fun MigrationContainer.contains(startVersion: Int, endVersion: Int): Boolean {
     val migrations = getMigrations()
     if (migrations.containsKey(startVersion)) {
diff --git a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
index 8145bcf..cebd289 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/util/StatementUtil.kt
@@ -53,13 +53,3 @@
     }
     return -1
 }
-
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-fun getColumnNames(statement: SQLiteStatement): Array<String> {
-    val columnCount = statement.getColumnCount()
-    val columnNames = mutableListOf<String>()
-    for (index in 0 until columnCount) {
-        columnNames.add(index, statement.getColumnName(index))
-    }
-    return columnNames.toTypedArray()
-}
diff --git a/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt b/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt
index 16a7bac..42ba221 100644
--- a/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt
+++ b/room/room-runtime/src/commonTest/kotlin/androidx/room/coroutines/BaseConnectionPoolTest.kt
@@ -61,10 +61,17 @@
 
     abstract fun getDriver(): SQLiteDriver
 
+    abstract val fileName: String
+
     @Test
     fun readerIsReadOnlyConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         fun assertMsg(ex: SQLiteException) {
             assertThat(ex.message)
                 .isEqualTo("Error code: 8, message: attempt to write a readonly database")
@@ -108,7 +115,12 @@
     @Test
     fun reusingConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var count = 0
         pool.useReaderConnection { initialConnection ->
             pool.useReaderConnection { reusedConnection ->
@@ -127,7 +139,12 @@
     @Test
     fun reusingConnectionOnLaunch() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var count = 0
         pool.useReaderConnection { initialConnection ->
             coroutineScope {
@@ -150,7 +167,12 @@
     @Test
     fun reusingConnectionOnAsync() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var count = 0
         pool.useReaderConnection { initialConnection ->
             coroutineScope {
@@ -173,7 +195,12 @@
     @Test
     fun reusingConnectionWithContext() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var count = 0
         pool.useReaderConnection { initialConnection ->
             withContext(Dispatchers.IO) {
@@ -194,7 +221,12 @@
     @Test
     fun failureToUpgradeConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useReaderConnection {
             assertThat(
                 assertFailsWith<SQLiteException> {
@@ -208,7 +240,12 @@
     @Test
     fun cannotUseAlreadyRecycledConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var leakedConnection: PooledConnection? = null
         pool.useReaderConnection {
             leakedConnection = it
@@ -224,7 +261,12 @@
     @Test
     fun cannotUseAlreadyRecycledStatement() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var leakedRawStatement: SQLiteStatement? = null
         pool.useReaderConnection { connection ->
             connection.usePrepared("SELECT * FROM Pet") {
@@ -242,7 +284,12 @@
     @Test
     fun cannotUsedAlreadyClosedPool() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.close()
         assertThat(
             assertFailsWith<SQLiteException> {
@@ -254,7 +301,12 @@
     @Test
     fun idempotentPoolClosing() {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.close()
         pool.close()
     }
@@ -263,7 +315,12 @@
     fun connectionUsedOnWrongCoroutine() = runTest {
         val singleThreadContext = newFixedThreadPoolContext(1, "Test-Threads")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useReaderConnection { connection ->
             launch(singleThreadContext) {
                 assertThat(
@@ -283,7 +340,12 @@
     fun connectionUsedOnWrongCoroutineWithLeakedContext() = runTest {
         val singleThreadContext = newSingleThreadContext("Test-Thread")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var leakedContext: CoroutineContext? = null
         var leakedConnection: PooledConnection? = null
         val job = launch(singleThreadContext) {
@@ -313,7 +375,12 @@
     fun statementUsedOnWrongThread() = runTest {
         val singleThreadContext = newFixedThreadPoolContext(1, "Test-Threads")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useReaderConnection { connection ->
             connection.usePrepared("SELECT * FROM Pet") { statement ->
                 val expectedErrorMsg =
@@ -375,7 +442,12 @@
     fun useStatementLocksConnection() = runTest {
         val multiThreadContext = newFixedThreadPoolContext(2, "Test-Threads")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var count = 0
         pool.useReaderConnection { connection ->
             coroutineScope {
@@ -412,9 +484,10 @@
         val connectionsOpened = atomic(0)
         val actualDriver = setupDriver()
         val driver = object : SQLiteDriver by actualDriver {
-            override fun open() = actualDriver.open().also { connectionsOpened.incrementAndGet() }
+            override fun open(fileName: String) = actualDriver.open(fileName)
+                .also { connectionsOpened.incrementAndGet() }
         }
-        val pool = newSingleConnectionPool(driver)
+        val pool = newSingleConnectionPool(driver, ":memory:")
         val jobs = mutableListOf<Job>()
         repeat(5) {
             val job1 = launch(multiThreadContext) {
@@ -443,9 +516,15 @@
         val connectionsOpened = atomic(0)
         val actualDriver = setupDriver()
         val driver = object : SQLiteDriver by actualDriver {
-            override fun open() = actualDriver.open().also { connectionsOpened.incrementAndGet() }
+            override fun open(fileName: String) = actualDriver.open(fileName)
+                .also { connectionsOpened.incrementAndGet() }
         }
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 4, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 4,
+            maxNumOfWriters = 1
+        )
         repeat(5) {
             pool.useReaderConnection { connection ->
                 var count = 0
@@ -465,7 +544,12 @@
     fun cancelCoroutineWaitingForConnection() = runTest {
         val multiThreadContext = newFixedThreadPoolContext(2, "Test-Threads")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         val coroutineStartedMutex = Mutex(locked = true)
         var acquiredSecondConnection = false
         pool.useWriterConnection {
@@ -489,7 +573,12 @@
     fun timeoutCoroutineWaitingForConnection() = runTest {
         val multiThreadContext = newFixedThreadPoolContext(2, "Test-Threads")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         val coroutineStartedMutex = Mutex(locked = true)
         var acquiredSecondConnection = false
         val testContext = coroutineContext
@@ -521,7 +610,12 @@
     @Test
     fun timeoutWhileUsingConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         assertFailsWith<TimeoutCancellationException> {
             pool.useWriterConnection {
                 withTimeout(0) {
@@ -541,7 +635,12 @@
     @Test
     fun errorUsingConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         assertThat(
             assertFailsWith<IllegalStateException> {
                 pool.useWriterConnection {
@@ -572,12 +671,17 @@
         val connectionsArr = arrayOfNulls<CloseAwareConnection>(4)
         val actualDriver = setupDriver()
         val driver = object : SQLiteDriver by actualDriver {
-            override fun open() =
-                CloseAwareConnection(actualDriver.open()).also {
+            override fun open(fileName: String) =
+                CloseAwareConnection(actualDriver.open(fileName)).also {
                     connectionsArr[connectionArrCount.getAndIncrement()] = it
                 }
         }
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 4, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 4,
+            maxNumOfWriters = 1
+        )
         val multiThreadContext = newFixedThreadPoolContext(4, "Test-Threads")
         val jobs = mutableListOf<Job>()
         val barrier = Mutex(locked = true)
@@ -606,7 +710,12 @@
     @Test
     fun rollbackTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -626,7 +735,12 @@
     @Test
     fun rollbackTransactionWithResult() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.execSQL("CREATE TEMP TABLE Cat (name)")
             val name = "Pelusa"
@@ -647,7 +761,12 @@
     @Test
     fun rollbackTransactionDueToException() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             assertFailsWith<TestingRollbackException> {
                 connection.exclusiveTransaction {
@@ -669,7 +788,12 @@
     @Test
     fun rollbackNestedTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -692,7 +816,12 @@
     @Test
     fun rollbackParentTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -716,7 +845,12 @@
     @Test
     fun rollbackDeeplyNestedTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -745,7 +879,12 @@
     @Test
     fun rollbackNestedTransactionOnReusedConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -770,7 +909,12 @@
     @Test
     fun rollbackNestedTransactionDueToExceptionOnReusedConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -799,7 +943,12 @@
     @Test
     fun rollbackEvenWhenCatchingRollbackException() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -817,7 +966,12 @@
     @Test
     fun nestedWriteTransactionDoesNotUpgradeConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         var nestedTransactionBlockExecuted = false
         pool.useReaderConnection { connection ->
             connection.deferredTransaction<Unit> {
@@ -838,7 +992,12 @@
     @Test
     fun endNestedTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction<Unit> {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -861,7 +1020,12 @@
     @Test
     fun endNestedTransactionOnReusedConnection() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.exclusiveTransaction<Unit> {
                 execSQL("INSERT INTO Pet (id, name) VALUES (100, 'Pelusa')")
@@ -886,7 +1050,12 @@
     @Test
     fun explicitRollbackTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             assertThat(
                 assertFailsWith<SQLiteException> {
@@ -907,7 +1076,12 @@
     @Test
     fun explicitEndTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             assertThat(
                 assertFailsWith<SQLiteException> {
@@ -928,7 +1102,12 @@
     @Test
     fun unfinishedExplicitTransaction() = runTest {
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useWriterConnection { connection ->
             connection.execSQL("BEGIN EXCLUSIVE TRANSACTION")
         }
@@ -948,7 +1127,12 @@
     fun parallelConnectionUsage() = runTest {
         val multiThreadContext = newFixedThreadPoolContext(4, "Test-Threads")
         val driver = setupDriver()
-        val pool = newConnectionPool(driver = driver, maxNumOfReaders = 1, maxNumOfWriters = 1)
+        val pool = newConnectionPool(
+            driver = driver,
+            fileName = fileName,
+            maxNumOfReaders = 1,
+            maxNumOfWriters = 1
+        )
         pool.useReaderConnection { connection ->
             coroutineScope {
                 repeat(10) {
@@ -973,7 +1157,7 @@
     }
 
     private fun setupTestDatabase(driver: SQLiteDriver) {
-        val connection = driver.open()
+        val connection = driver.open(fileName)
         val compileOptions = buildList {
             connection.prepare("PRAGMA compile_options").use {
                 while (it.step()) { add(it.getText(0)) }
diff --git a/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt b/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt
index 1ab569b..4626508 100644
--- a/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt
+++ b/room/room-runtime/src/jvmMain/kotlin/androidx/room/Room.jvm.kt
@@ -24,6 +24,11 @@
 actual object Room {
 
     /**
+     * The master table name where Room keeps its metadata information.
+     */
+    actual const val MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME
+
+    /**
      * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
      * database disappears when the process is killed. Once a database is built, you should keep a
      * reference to it and re-use it.
diff --git a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt
index 4ec00aa..af3d9ab 100644
--- a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt
+++ b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/InvalidationTracker.jvmNative.kt
@@ -94,10 +94,8 @@
      *
      * This function should be called after any write operation is performed on the database,
      * such that tracked tables and its associated observers are notified if invalidated.
-     *
-     * @see sync
      */
-    internal actual fun refreshAsync() {
+    actual fun refreshAsync() {
         implementation.refreshInvalidationAsync()
     }
 
diff --git a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomConnectionManager.jvmNative.kt b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomConnectionManager.jvmNative.kt
index c42c280..91d9268 100644
--- a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomConnectionManager.jvmNative.kt
+++ b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomConnectionManager.jvmNative.kt
@@ -28,15 +28,17 @@
     override val callbacks: List<RoomDatabase.Callback>,
 ) : BaseRoomConnectionManager() {
 
-    override val connectionPool: ConnectionPool =
+    private val connectionPool: ConnectionPool =
         if (configuration.name == null) {
             // An in-memory database must use a single connection pool.
             newSingleConnectionPool(
-                driver = DriverWrapper(sqliteDriver)
+                driver = DriverWrapper(sqliteDriver),
+                fileName = ":memory:"
             )
         } else {
             newConnectionPool(
                 driver = DriverWrapper(sqliteDriver),
+                fileName = configuration.name,
                 maxNumOfReaders = configuration.journalMode.getMaxNumberOfReaders(),
                 maxNumOfWriters = configuration.journalMode.getMaxNumberOfWriters()
             )
diff --git a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomDatabase.jvmNative.kt b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomDatabase.jvmNative.kt
index 99c9c9f..2d4277c 100644
--- a/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomDatabase.jvmNative.kt
+++ b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomDatabase.jvmNative.kt
@@ -23,6 +23,7 @@
 import androidx.room.concurrent.CloseBarrier
 import androidx.room.migration.AutoMigrationSpec
 import androidx.room.migration.Migration
+import androidx.room.util.contains as containsCommon
 import androidx.sqlite.SQLiteConnection
 import androidx.sqlite.SQLiteDriver
 import kotlin.coroutines.ContinuationInterceptor
@@ -241,6 +242,10 @@
 
     /**
      * Use a connection to perform database operations.
+     *
+     * This function is for internal access to the pool, it is an unconfined coroutine function to
+     * be used by Room generated code paths. For the public version see [useReaderConnection] and
+     * [useWriterConnection].
      */
     internal actual suspend fun <R> useConnection(
         isReadOnly: Boolean,
@@ -252,7 +257,7 @@
     /**
      * Journal modes for SQLite database.
      *
-     * @see Builder.setJournalMode
+     * @see Builder#setJournalMode
      */
     actual enum class JournalMode {
         /**
@@ -375,12 +380,23 @@
         }
 
         /**
+         * Adds the given migrations to the list of available migrations. If 2 migrations have the
+         * same start-end versions, the latter migration overrides the previous one.
+         *
+         * @param migrations List of available migrations.
+         */
+        actual fun addMigrations(migrations: List<Migration>) {
+            migrations.forEach(::addMigration)
+        }
+
+        /**
          * Add a [Migration] to the container. If the container already has a migration with the
          * same start-end versions then it will be overwritten.
          *
          * @param migration the migration to add.
          */
-        internal actual fun addMigration(migration: Migration) {
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        actual fun addMigration(migration: Migration) {
             val start = migration.startVersion
             val end = migration.endVersion
             val targetMap = migrations.getOrPut(start) { mutableMapOf() }
@@ -388,6 +404,18 @@
         }
 
         /**
+         * Indicates if the given migration is contained within the [MigrationContainer] based
+         * on its start-end versions.
+         *
+         * @param startVersion Start version of the migration.
+         * @param endVersion End version of the migration
+         * @return True if it contains a migration with the same start-end version, false otherwise.
+         */
+        actual fun contains(startVersion: Int, endVersion: Int): Boolean {
+            return this.containsCommon(startVersion, endVersion)
+        }
+
+        /**
          * Returns a pair corresponding to an entry in the map of available migrations whose key
          * is [migrationStart] and its sorted keys in ascending order.
          */
diff --git a/room/room-runtime/src/jvmTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt b/room/room-runtime/src/jvmTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt
index b8ac5e2..d6d4e54 100644
--- a/room/room-runtime/src/jvmTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt
+++ b/room/room-runtime/src/jvmTest/kotlin/androidx/room/coroutines/BundledSQLiteConnectionPoolTest.kt
@@ -18,12 +18,14 @@
 
 import androidx.sqlite.SQLiteDriver
 import androidx.sqlite.driver.bundled.BundledSQLiteDriver
+import kotlin.io.path.absolutePathString
 import kotlin.io.path.createTempFile
 
 class BundledSQLiteConnectionPoolTest : BaseConnectionPoolTest() {
+    override val fileName = createTempFile("test.db").also { it.toFile().deleteOnExit() }
+        .absolutePathString()
 
     override fun getDriver(): SQLiteDriver {
-        val tempFile = createTempFile("test.db").also { it.toFile().deleteOnExit() }
-        return BundledSQLiteDriver(tempFile.toString())
+        return BundledSQLiteDriver()
     }
 }
diff --git a/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt b/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt
index e133d30..319dbea 100644
--- a/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt
+++ b/room/room-runtime/src/nativeMain/kotlin/androidx/room/Room.native.kt
@@ -22,6 +22,11 @@
 actual object Room {
 
     /**
+     * The master table name where Room keeps its metadata information.
+     */
+    actual const val MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME
+
+    /**
      * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory
      * database disappears when the process is killed. Once a database is built, you should keep a
      * reference to it and re-use it.
diff --git a/room/room-runtime/src/nativeTest/kotlin/androidx.room/BuilderTest.kt b/room/room-runtime/src/nativeTest/kotlin/androidx.room/BuilderTest.kt
index 78d8d57..676817d 100644
--- a/room/room-runtime/src/nativeTest/kotlin/androidx.room/BuilderTest.kt
+++ b/room/room-runtime/src/nativeTest/kotlin/androidx.room/BuilderTest.kt
@@ -27,7 +27,7 @@
         val db = databaseBuilder(
             name = "TestDatabase",
             factory = { TestDatabase::class.instantiateImpl() }
-        ).setDriver(NativeSQLiteDriver(":memory:")).build()
+        ).setDriver(NativeSQLiteDriver()).build()
 
         // Assert that the db is built successfully.
         assertThat(db).isInstanceOf<TestDatabase>()
diff --git a/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt b/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt
index 8124a7c..36a29a4 100644
--- a/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt
+++ b/room/room-runtime/src/nativeTest/kotlin/androidx.room/coroutines/BundledSQLiteConnectionPoolTest.kt
@@ -25,10 +25,10 @@
 
 class BundledSQLiteConnectionPoolTest : BaseConnectionPoolTest() {
 
-    private val filename = "/tmp/test-${Random.nextInt()}.db"
+    override val fileName = "/tmp/test-${Random.nextInt()}.db"
 
     override fun getDriver(): SQLiteDriver {
-        return BundledSQLiteDriver(filename)
+        return BundledSQLiteDriver()
     }
 
     @BeforeTest
@@ -42,8 +42,8 @@
     }
 
     private fun deleteDatabaseFile() {
-        remove(filename)
-        remove("$filename-wal")
-        remove("$filename-shm")
+        remove(fileName)
+        remove("$fileName-wal")
+        remove("$fileName-shm")
     }
 }
diff --git a/room/room-testing/api/api_lint.ignore b/room/room-testing/api/api_lint.ignore
new file mode 100644
index 0000000..3812179
--- /dev/null
+++ b/room/room-testing/api/api_lint.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+MissingJvmstatic: androidx.room.testing.MigrationTestHelper#runMigrationsAndValidate(int, java.util.List<? extends androidx.room.migration.Migration>):
+    A Kotlin method with default parameter values should be annotated with @JvmOverloads for better Java interoperability; see https://android.github.io/kotlin-guides/interop.html#function-overloads-for-defaults
diff --git a/room/room-testing/api/current.txt b/room/room-testing/api/current.txt
index 482f988..2499cb4 100644
--- a/room/room-testing/api/current.txt
+++ b/room/room-testing/api/current.txt
@@ -7,9 +7,12 @@
     ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
     ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
     ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, String fileName, androidx.sqlite.SQLiteDriver driver, kotlin.reflect.KClass<? extends androidx.room.RoomDatabase> databaseClass, optional kotlin.jvm.functions.Function0<? extends androidx.room.RoomDatabase> databaseFactory, optional java.util.List<? extends androidx.room.migration.AutoMigrationSpec> autoMigrationSpecs);
     method public void closeWhenFinished(androidx.room.RoomDatabase db);
     method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
+    method public final androidx.sqlite.SQLiteConnection createDatabase(int version);
     method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public androidx.sqlite.db.SupportSQLiteDatabase createDatabase(String name, int version) throws java.io.IOException;
+    method public final androidx.sqlite.SQLiteConnection runMigrationsAndValidate(int version, optional java.util.List<? extends androidx.room.migration.Migration> migrations);
     method public androidx.sqlite.db.SupportSQLiteDatabase runMigrationsAndValidate(String name, int version, boolean validateDroppedTables, androidx.room.migration.Migration... migrations);
   }
 
diff --git a/room/room-testing/api/restricted_current.txt b/room/room-testing/api/restricted_current.txt
index 482f988..2499cb4 100644
--- a/room/room-testing/api/restricted_current.txt
+++ b/room/room-testing/api/restricted_current.txt
@@ -7,9 +7,12 @@
     ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
     ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder);
     ctor @Deprecated public MigrationTestHelper(android.app.Instrumentation instrumentation, String assetsFolder, optional androidx.sqlite.db.SupportSQLiteOpenHelper.Factory openFactory);
+    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, String fileName, androidx.sqlite.SQLiteDriver driver, kotlin.reflect.KClass<? extends androidx.room.RoomDatabase> databaseClass, optional kotlin.jvm.functions.Function0<? extends androidx.room.RoomDatabase> databaseFactory, optional java.util.List<? extends androidx.room.migration.AutoMigrationSpec> autoMigrationSpecs);
     method public void closeWhenFinished(androidx.room.RoomDatabase db);
     method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase db);
+    method public final androidx.sqlite.SQLiteConnection createDatabase(int version);
     method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public androidx.sqlite.db.SupportSQLiteDatabase createDatabase(String name, int version) throws java.io.IOException;
+    method public final androidx.sqlite.SQLiteConnection runMigrationsAndValidate(int version, optional java.util.List<? extends androidx.room.migration.Migration> migrations);
     method public androidx.sqlite.db.SupportSQLiteDatabase runMigrationsAndValidate(String name, int version, boolean validateDroppedTables, androidx.room.migration.Migration... migrations);
   }
 
diff --git a/room/room-testing/build.gradle b/room/room-testing/build.gradle
index 037fbc6..aa35ba0 100644
--- a/room/room-testing/build.gradle
+++ b/room/room-testing/build.gradle
@@ -46,8 +46,8 @@
                 api(libs.kotlinStdlib)
                 api(project(":room:room-common"))
                 api(project(":room:room-runtime"))
-                api(project(":room:room-migration"))
                 api(project(":sqlite:sqlite"))
+                implementation(project(":room:room-migration"))
             }
         }
         commonTest {
@@ -58,6 +58,9 @@
         }
         jvmMain {
             dependsOn(commonMain)
+            dependencies {
+                api(libs.junit)
+            }
         }
         androidMain {
             dependsOn(commonMain)
@@ -69,6 +72,9 @@
         }
         nativeMain {
             dependsOn(commonMain)
+            dependencies {
+                implementation(libs.okio)
+            }
         }
         targets.all { target ->
             if (target.platformType == KotlinPlatformType.native) {
diff --git a/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt b/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt
index 0d34fda..cec9729 100644
--- a/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt
+++ b/room/room-testing/src/androidMain/kotlin/androidx/room/testing/MigrationTestHelper.android.kt
@@ -18,84 +18,80 @@
 
 import android.app.Instrumentation
 import android.content.Context
-import android.util.Log
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.room.DatabaseConfiguration
-import androidx.room.Room
+import androidx.room.InvalidationTracker
 import androidx.room.RoomDatabase
+import androidx.room.RoomOpenDelegate
+import androidx.room.driver.SupportSQLiteConnection
+import androidx.room.driver.SupportSQLiteDriver
 import androidx.room.migration.AutoMigrationSpec
 import androidx.room.migration.Migration
-import androidx.room.migration.bundle.DatabaseBundle
-import androidx.room.migration.bundle.EntityBundle
-import androidx.room.migration.bundle.FtsEntityBundle
 import androidx.room.migration.bundle.SchemaBundle
-import androidx.room.migration.bundle.SchemaBundle.Companion.deserialize
-import androidx.room.util.FtsTableInfo
-import androidx.room.util.TableInfo
-import androidx.room.util.ViewInfo
 import androidx.room.util.findAndInstantiateDatabaseImpl
-import androidx.room.util.useCursor
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteDriver
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
-import java.io.File
 import java.io.FileNotFoundException
 import java.io.IOException
 import java.lang.ref.WeakReference
 import kotlin.reflect.KClass
+import kotlin.reflect.cast
 import org.junit.rules.TestWatcher
 import org.junit.runner.Description
 
 /**
- * A class that can be used in your Instrumentation tests that can create the database in an
- * older schema.
+ * A class that can help test and verify database creation and migration at different versions with
+ * different schemas in Instrumentation tests.
  *
- * You must copy the schema json files (created by passing `room.schemaLocation` argument
- * into the annotation processor) into your test assets and pass in the path for that folder into
- * the constructor. This class will read the folder and extract the schemas from there.
- *
+ * The helper relies on exported schemas so [androidx.room.Database.exportSchema] should
+ * be enabled. Schema location should be configured via Room's Gradle Plugin (id 'androidx.room'):
+ * ```
+ * room {
+ *   schemaDirectory("$projectDir/schemas")
+ * }
+ * ```
+ * The schema files must also be copied into the test assets in order for the helper to open them
+ * during the instrumentation test:
  * ```
  * android {
- *     defaultConfig {
- *         javaCompileOptions {
- *             annotationProcessorOptions {
- *                 arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
- *             }
- *         }
- *     }
  *     sourceSets {
  *         androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
  *     }
  * }
  * ```
+ * See the various constructors documentation for possible configurations.
+ *
+ * See also [Room's Test Migrations Documentation](https://developer.android.com/training/data-storage/room/migrating-db-versions#test)
  */
-open class MigrationTestHelper : TestWatcher {
-    private val assetsFolder: String
-    private val openFactory: SupportSQLiteOpenHelper.Factory
-    private val managedDatabases = mutableListOf<WeakReference<SupportSQLiteDatabase>>()
+actual open class MigrationTestHelper : TestWatcher {
+    private val delegate: AndroidMigrationTestHelper
+
+    private val managedSupportDatabases = mutableListOf<WeakReference<SupportSQLiteDatabase>>()
     private val managedRoomDatabases = mutableListOf<WeakReference<RoomDatabase>>()
+
     private var testStarted = false
-    private val instrumentation: Instrumentation
-    private val specs: List<AutoMigrationSpec>
-    private val databaseClass: Class<out RoomDatabase>?
-    internal lateinit var databaseConfiguration: DatabaseConfiguration
 
     /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * Creates a new migration helper. It uses the [instrumentation] context to load the schema
      * (falls back to the app resources) and the target context to create the database.
      *
+     * When the [MigrationTestHelper] is created with this constructor configuration then only
+     * [createDatabase] and [runMigrationsAndValidate] that return [SupportSQLiteDatabase] can
+     * be used.
+     *
      * @param instrumentation The instrumentation instance.
      * @param assetsFolder    The asset folder in the assets directory.
      * @param openFactory factory for creating an [SupportSQLiteOpenHelper]
      */
     @Deprecated(
         """
-            Cannot be used to run migration tests involving [AutoMigration].
-            To test [AutoMigration], you must use [MigrationTestHelper(Instrumentation, Class, List,
-            SupportSQLiteOpenHelper.Factory)] for tests containing a
-            [androidx.room.ProvidedAutoMigrationSpec], or use
-            [MigrationTestHelper(Instrumentation, Class, List)] otherwise.
-      """
+        Cannot be used to run migration tests involving auto migrations.
+        To test an auto migrations, you must use the constructors that receives the database
+        class as parameter.
+        """
     )
     @JvmOverloads
     constructor(
@@ -103,20 +99,22 @@
         assetsFolder: String,
         openFactory: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory()
     ) {
-        this.instrumentation = instrumentation
-        this.assetsFolder = assetsFolder
-        this.openFactory = openFactory
-        databaseClass = null
-        specs = mutableListOf()
+        this.delegate = SupportSQLiteMigrationTestHelper(
+            instrumentation = instrumentation,
+            assetsFolder = assetsFolder,
+            databaseClass = null,
+            openFactory = openFactory,
+            autoMigrationSpecs = emptyList()
+        )
     }
 
     /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * Creates a new migration helper. It uses the [instrumentation] context to load the schema
      * (falls back to the app resources) and the target context to create the database.
      *
-     * An instance of a class annotated with [androidx.room.ProvidedAutoMigrationSpec] has
-     * to be provided to Room using this constructor. MigrationTestHelper will map auto migration
-     * spec classes to their provided instances before running and validating the Migrations.
+     * When the [MigrationTestHelper] is created with this constructor configuration then only
+     * [createDatabase] and [runMigrationsAndValidate] that return [SupportSQLiteDatabase] can
+     * be used.
      *
      * @param instrumentation The instrumentation instance.
      * @param databaseClass   The Database class to be tested.
@@ -125,22 +123,28 @@
         instrumentation: Instrumentation,
         databaseClass: Class<out RoomDatabase>
     ) : this(
-        instrumentation, databaseClass, emptyList(), FrameworkSQLiteOpenHelperFactory()
+        instrumentation = instrumentation,
+        databaseClass = databaseClass,
+        specs = emptyList(),
+        openFactory = FrameworkSQLiteOpenHelperFactory()
     )
 
     /**
-     * Creates a new migration helper. It uses the Instrumentation context to load the schema
+     * Creates a new migration helper. It uses the [instrumentation] context to load the schema
      * (falls back to the app resources) and the target context to create the database.
      *
+     * Instances of classes annotated with [androidx.room.ProvidedAutoMigrationSpec] have
+     * provided using this constructor. [MigrationTestHelper] will map auto migration
+     * spec classes to their provided instances before running and validating the migrations.
      *
-     * An instance of a class annotated with [androidx.room.ProvidedAutoMigrationSpec] has
-     * to be provided to Room using this constructor. MigrationTestHelper will map auto migration
-     * spec classes to their provided instances before running and validating the Migrations.
+     * When the [MigrationTestHelper] is created with this constructor configuration then only
+     * [createDatabase] and [runMigrationsAndValidate] that return [SupportSQLiteDatabase] can
+     * be used.
      *
      * @param instrumentation The instrumentation instance.
      * @param databaseClass   The Database class to be tested.
      * @param specs           The list of available auto migration specs that will be provided to
-     * Room at runtime.
+     *                        the RoomDatabase at runtime.
      * @param openFactory factory for creating an [SupportSQLiteOpenHelper]
      */
     @JvmOverloads
@@ -150,17 +154,67 @@
         specs: List<AutoMigrationSpec>,
         openFactory: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory()
     ) {
-        this.assetsFolder = checkNotNull(databaseClass.canonicalName).let {
+        val assetsFolder = checkNotNull(databaseClass.canonicalName).let {
             if (it.endsWith("/")) {
-                it.substring(0, databaseClass.canonicalName!!.length - 1)
+                it.substring(0, it.length - 1)
             } else {
                 it
             }
         }
-        this.instrumentation = instrumentation
-        this.openFactory = openFactory
-        this.databaseClass = databaseClass
-        this.specs = specs
+        this.delegate = SupportSQLiteMigrationTestHelper(
+            instrumentation = instrumentation,
+            assetsFolder = assetsFolder,
+            databaseClass = databaseClass,
+            openFactory = openFactory,
+            autoMigrationSpecs = specs
+        )
+    }
+
+    /**
+     * Creates a new migration helper. It uses the [instrumentation] context to load the schema
+     * (falls back to the app resources) and the target context to create the database.
+     *
+     * When the [MigrationTestHelper] is created with this constructor configuration then only
+     * [createDatabase] and [runMigrationsAndValidate] that return [SQLiteConnection] can
+     * be used.
+     *
+     * @param instrumentation The instrumentation instance.
+     * @param fileName Name of the database.
+     * @param driver A driver that opens connection to a file database. A driver that opens connections
+     * to an in-memory database would be meaningless.
+     * @param databaseClass The [androidx.room.Database] annotated class.
+     * @param databaseFactory An optional factory function to create an instance of the
+     * [databaseClass]. Should be the same factory used when building the database via
+     * [androidx.room.Room.databaseBuilder].
+     * @param autoMigrationSpecs The list of [androidx.room.ProvidedAutoMigrationSpec] instances
+     * for [androidx.room.AutoMigration]s that require them.
+     */
+    constructor(
+        instrumentation: Instrumentation,
+        fileName: String,
+        driver: SQLiteDriver,
+        databaseClass: KClass<out RoomDatabase>,
+        databaseFactory: () -> RoomDatabase = {
+            findAndInstantiateDatabaseImpl(databaseClass.java)
+        },
+        autoMigrationSpecs: List<AutoMigrationSpec> = emptyList()
+    ) {
+        val assetsFolder = checkNotNull(databaseClass.qualifiedName).let {
+            if (it.endsWith("/")) {
+                it.substring(0, it.length - 1)
+            } else {
+                it
+            }
+        }
+        this.delegate = SQLiteDriverMigrationTestHelper(
+            instrumentation = instrumentation,
+            assetsFolder = assetsFolder,
+            fileName = fileName,
+            driver = driver,
+            databaseClass = databaseClass,
+            databaseFactory = databaseFactory,
+            autoMigrationSpecs = autoMigrationSpecs
+        )
     }
 
     override fun starting(description: Description?) {
@@ -170,6 +224,7 @@
 
     /**
      * Creates the database in the given version.
+     *
      * If the database file already exists, it tries to delete it first. If delete fails, throws
      * an exception.
      *
@@ -179,51 +234,11 @@
      */
     @Throws(IOException::class)
     open fun createDatabase(name: String, version: Int): SupportSQLiteDatabase {
-        val dbPath: File = instrumentation.targetContext.getDatabasePath(name)
-        if (dbPath.exists()) {
-            Log.d(TAG, "deleting database file $name")
-            check(dbPath.delete()) {
-                "There is a database file and I could not delete" +
-                    " it. Make sure you don't have any open connections to that database" +
-                    " before calling this method."
-            }
+        check(delegate is SupportSQLiteMigrationTestHelper) {
+            "MigrationTestHelper functionality returning a SupportSQLiteDatabase is not possible " +
+                "because a SQLiteDriver was provided during configuration."
         }
-        val schemaBundle = loadSchema(version)
-        val container: RoomDatabase.MigrationContainer = RoomDatabase.MigrationContainer()
-        val configuration = DatabaseConfiguration(
-            context = instrumentation.targetContext,
-            name = name,
-            sqliteOpenHelperFactory = openFactory,
-            migrationContainer = container,
-            callbacks = null,
-            allowMainThreadQueries = true,
-            journalMode = RoomDatabase.JournalMode.TRUNCATE,
-            queryExecutor = ArchTaskExecutor.getIOThreadExecutor(),
-            transactionExecutor = ArchTaskExecutor.getIOThreadExecutor(),
-            multiInstanceInvalidationServiceIntent = null,
-            requireMigration = true,
-            allowDestructiveMigrationOnDowngrade = false,
-            migrationNotRequiredFrom = emptySet(),
-            copyFromAssetPath = null,
-            copyFromFile = null,
-            copyFromInputStream = null,
-            prepackagedDatabaseCallback = null,
-            typeConverters = emptyList(),
-            autoMigrationSpecs = emptyList(),
-            allowDestructiveMigrationForAllTables = false,
-            sqliteDriver = null,
-            queryCoroutineContext = null
-        )
-        @Suppress("DEPRECATION") // Due to RoomOpenHelper
-        val roomOpenHelper = androidx.room.RoomOpenHelper(
-            configuration = configuration,
-            delegate = CreatingDelegate(schemaBundle.database),
-            identityHash = schemaBundle.database.identityHash,
-            // we pass the same hash twice since an old schema does not necessarily have
-            // a legacy hash and we would not even persist it.
-            legacyHash = schemaBundle.database.identityHash
-        )
-        return openDatabase(name, roomOpenHelper)
+        return delegate.createDatabase(name, version)
     }
 
     /**
@@ -245,7 +260,7 @@
      * @param validateDroppedTables If set to true, validation will fail if the database has
      * unknown tables.
      * @param migrations            The list of available migrations.
-     * @throws IllegalArgumentException If the schema validation fails.
+     * @throws IllegalStateException If the schema validation fails.
      */
     open fun runMigrationsAndValidate(
         name: String,
@@ -253,153 +268,75 @@
         validateDroppedTables: Boolean,
         vararg migrations: Migration
     ): SupportSQLiteDatabase {
-        val dbPath = instrumentation.targetContext.getDatabasePath(name)
-        check(dbPath.exists()) {
-            "Cannot find the database file for $name. " +
-                "Before calling runMigrations, you must first create the database via " +
-                "createDatabase."
+        check(delegate is SupportSQLiteMigrationTestHelper) {
+            "MigrationTestHelper functionality returning a SupportSQLiteDatabase is not possible " +
+                "because a SQLiteDriver was provided during configuration."
         }
-        val schemaBundle = loadSchema(version)
-        val container = RoomDatabase.MigrationContainer()
-        container.addMigrations(*migrations)
-        val autoMigrations = getAutoMigrations(specs)
-        autoMigrations.forEach { autoMigration ->
-            val migrationExists = container.contains(
-                autoMigration.startVersion,
-                autoMigration.endVersion
-            )
-            if (!migrationExists) {
-                container.addMigrations(autoMigration)
-            }
-        }
-        databaseConfiguration = DatabaseConfiguration(
-            context = instrumentation.targetContext,
-            name = name,
-            sqliteOpenHelperFactory = openFactory,
-            migrationContainer = container,
-            callbacks = null,
-            allowMainThreadQueries = true,
-            journalMode = RoomDatabase.JournalMode.TRUNCATE,
-            queryExecutor = ArchTaskExecutor.getIOThreadExecutor(),
-            transactionExecutor = ArchTaskExecutor.getIOThreadExecutor(),
-            multiInstanceInvalidationServiceIntent = null,
-            requireMigration = true,
-            allowDestructiveMigrationOnDowngrade = false,
-            migrationNotRequiredFrom = emptySet(),
-            copyFromAssetPath = null,
-            copyFromFile = null,
-            copyFromInputStream = null,
-            prepackagedDatabaseCallback = null,
-            typeConverters = emptyList(),
-            autoMigrationSpecs = emptyList(),
-            allowDestructiveMigrationForAllTables = false,
-            sqliteDriver = null,
-            queryCoroutineContext = null
-        )
-        @Suppress("DEPRECATION") // Due to RoomOpenHelper
-        val roomOpenHelper = androidx.room.RoomOpenHelper(
-            configuration = databaseConfiguration,
-            delegate = MigratingDelegate(
-                databaseBundle = schemaBundle.database,
-                // we pass the same hash twice since an old schema does not necessarily have
-                // a legacy hash and we would not even persist it.
-                mVerifyDroppedTables = validateDroppedTables
-            ),
-            identityHash = schemaBundle.database.identityHash,
-            legacyHash = schemaBundle.database.identityHash
-        )
-        return openDatabase(name, roomOpenHelper)
-    }
-
-    /**
-     * Returns a list of [Migration] of a database that has been generated using
-     * [androidx.room.AutoMigration].
-     */
-    private fun getAutoMigrations(userProvidedSpecs: List<AutoMigrationSpec>): List<Migration> {
-        if (databaseClass == null) {
-            return if (userProvidedSpecs.isEmpty()) {
-                // TODO: Detect that there are auto migrations to test when a deprecated
-                //  constructor is used.
-                Log.e(
-                    TAG, "If you have any AutoMigrations in your implementation, you must use " +
-                        "a non-deprecated MigrationTestHelper constructor to provide the " +
-                        "Database class in order to test them. If you do not have any " +
-                        "AutoMigrations to test, you may ignore this warning."
-                )
-                mutableListOf()
-            } else {
-                error(
-                    "You must provide the database class in the " +
-                        "MigrationTestHelper constructor in order to test auto migrations."
-                )
-            }
-        }
-        val db: RoomDatabase = findAndInstantiateDatabaseImpl(databaseClass)
-        val requiredAutoMigrationSpecs = db.getRequiredAutoMigrationSpecClasses()
-        return db.createAutoMigrations(
-            createAutoMigrationSpecMap(requiredAutoMigrationSpecs, userProvidedSpecs)
+        return delegate.runMigrationsAndValidate(
+            name, version, validateDroppedTables, migrations
         )
     }
 
     /**
-     * Maps auto migration spec classes to their provided instance.
+     * Creates the database at the given version.
+     *
+     * Once a database is created it can further validate with [runMigrationsAndValidate].
+     *
+     * @param version The version of the schema at which the database should be created.
+     * @return A database connection of the newly created database.
+     * @throws IllegalStateException If a new database was not created.
      */
-    private fun createAutoMigrationSpecMap(
-        requiredAutoMigrationSpecs: Set<KClass<out AutoMigrationSpec>>,
-        userProvidedSpecs: List<AutoMigrationSpec>
-    ): Map<KClass<out AutoMigrationSpec>, AutoMigrationSpec> {
-        if (requiredAutoMigrationSpecs.isEmpty()) {
-            return emptyMap()
+    actual fun createDatabase(version: Int): SQLiteConnection {
+        check(delegate is SQLiteDriverMigrationTestHelper) {
+            "MigrationTestHelper functionality returning a SQLiteConnection is not possible " +
+                "because a SupportSQLiteOpenHelper was provided during configuration (i.e. no " +
+                "SQLiteDriver was provided)."
         }
-        return buildMap {
-            requiredAutoMigrationSpecs.forEach { spec ->
-                val match = userProvidedSpecs.firstOrNull { provided ->
-                    spec.java.isAssignableFrom(provided.javaClass)
-                }
-                require(match != null) {
-                    "A required auto migration spec (${spec.qualifiedName}) has not been provided."
-                }
-                put(spec, match)
-            }
-        }
+        return delegate.createDatabase(version)
     }
 
-    private fun openDatabase(
-        name: String,
-        @Suppress("DEPRECATION")
-        roomOpenHelper: androidx.room.RoomOpenHelper
-    ): SupportSQLiteDatabase {
-        val config = SupportSQLiteOpenHelper.Configuration.builder(instrumentation.targetContext)
-            .callback(roomOpenHelper)
-            .name(name)
-            .build()
-        val db = openFactory.create(config).writableDatabase
-        managedDatabases.add(WeakReference(db))
-        return db
+    /**
+     * Runs the given set of migrations on the existing database once created via [createDatabase].
+     *
+     * This function uses the same algorithm that Room performs to choose migrations such that the
+     * [migrations] instances provided must be sufficient to bring the database from current
+     * version to the desired version. If the database contains
+     * [androidx.room.AutoMigration]s, then those are already included in the list of migrations
+     * to execute if necessary. Note that provided manual migrations take precedence over
+     * auto migrations if they overlap in migration paths.
+     *
+     * Once migrations are done, this functions validates the database schema to ensure the
+     * migration performed resulted in the expected schema.
+     *
+     * @param version The final version the database should migrate to.
+     * @param migrations The list of migrations used to attempt the database migration.
+     * @return A database connection of the migrated database.
+     * @throws IllegalStateException If the schema validation fails.
+     */
+    actual fun runMigrationsAndValidate(
+        version: Int,
+        migrations: List<Migration>,
+    ): SQLiteConnection {
+        check(delegate is SQLiteDriverMigrationTestHelper) {
+            "MigrationTestHelper functionality returning a SQLiteConnection is not possible " +
+                "because a SupportSQLiteOpenHelper was provided during configuration (i.e. no " +
+                "SQLiteDriver was provided)."
+        }
+        return delegate.runMigrationsAndValidate(version, migrations)
     }
 
     override fun finished(description: Description?) {
         super.finished(description)
-        managedDatabases.forEach { dbRef ->
-            val db = dbRef.get()
-            if (db != null && db.isOpen) {
-                try {
-                    db.close()
-                } catch (ignored: Throwable) {
-                }
-            }
-        }
-        managedRoomDatabases.forEach { dbRef ->
-            val roomDatabase = dbRef.get()
-            roomDatabase?.close()
-        }
+        delegate.finished()
+        managedSupportDatabases.forEach { it.get()?.close() }
+        managedRoomDatabases.forEach { it.get()?.close() }
     }
 
     /**
      * Registers a database connection to be automatically closed when the test finishes.
      *
-     * This only works if `MigrationTestHelper` is registered as a Junit test rule via
-     * [Rule][org.junit.Rule] annotation.
+     * This only works if [MigrationTestHelper] is registered as a Junit test rule via
+     * the [org.junit.Rule] annotation.
      *
      * @param db The database connection that should be closed after the test finishes.
      */
@@ -409,14 +346,14 @@
                 " the test starts. Maybe you forgot to annotate MigrationTestHelper as a" +
                 " test rule? (@Rule)"
         }
-        managedDatabases.add(WeakReference(db))
+        managedSupportDatabases.add(WeakReference(db))
     }
 
     /**
      * Registers a database connection to be automatically closed when the test finishes.
      *
-     * This only works if `MigrationTestHelper` is registered as a Junit test rule via
-     * [Rule][org.junit.Rule] annotation.
+     * This only works if [MigrationTestHelper] is registered as a Junit test rule via
+     * the [org.junit.Rule] annotation.
      *
      * @param db The RoomDatabase instance which holds the database.
      */
@@ -428,18 +365,28 @@
         }
         managedRoomDatabases.add(WeakReference(db))
     }
+}
 
-    private fun loadSchema(version: Int): SchemaBundle {
+/**
+ * Base implementation of Android's [MigrationTestHelper]
+ */
+private sealed class AndroidMigrationTestHelper(
+    private val instrumentation: Instrumentation,
+    private val assetsFolder: String
+) {
+    protected val managedConnections = mutableListOf<WeakReference<SQLiteConnection>>()
+
+    fun finished() {
+        managedConnections.forEach { it.get()?.close() }
+    }
+
+    protected fun loadSchema(version: Int): SchemaBundle {
         return try {
             loadSchema(instrumentation.context, version)
-        } catch (testAssetsIOExceptions: FileNotFoundException) {
-            Log.w(
-                TAG, "Could not find the schema file in the test assets. Checking the" +
-                    " application assets"
-            )
+        } catch (testAssetsNotFoundEx: FileNotFoundException) {
             try {
                 loadSchema(instrumentation.targetContext, version)
-            } catch (appAssetsException: FileNotFoundException) {
+            } catch (appAssetsNotFoundEx: FileNotFoundException) {
                 // throw the test assets exception instead
                 throw FileNotFoundException(
                     "Cannot find the schema file in the assets folder. " +
@@ -447,156 +394,223 @@
                         "inputs. See " +
                         "https://developer.android.com/training/data-storage/room/" +
                         "migrating-db-versions#export-schema for details. Missing file: " +
-                        testAssetsIOExceptions.message
+                        testAssetsNotFoundEx.message
                 )
             }
         }
     }
 
-    private fun loadSchema(context: Context, version: Int): SchemaBundle {
+    protected fun loadSchema(context: Context, version: Int): SchemaBundle {
         val input = context.assets.open("$assetsFolder/$version.json")
-        return deserialize(input)
+        return SchemaBundle.deserialize(input)
     }
 
-    @Suppress("DEPRECATION") // Due to RoomOpenHelper
-    internal class MigratingDelegate(
-        databaseBundle: DatabaseBundle,
-        private val mVerifyDroppedTables: Boolean
-    ) : RoomOpenHelperDelegate(databaseBundle) {
-        override fun createAllTables(db: SupportSQLiteDatabase) {
-            throw UnsupportedOperationException(
-                "Was expecting to migrate but received create." +
-                    "Make sure you have created the database first."
-            )
-        }
+    protected fun createDatabaseConfiguration(
+        container: RoomDatabase.MigrationContainer,
+        openFactory: SupportSQLiteOpenHelper.Factory?,
+        sqliteDriver: SQLiteDriver?,
+        databaseFileName: String?
+    ) = DatabaseConfiguration(
+        context = instrumentation.targetContext,
+        name = databaseFileName,
+        sqliteOpenHelperFactory = openFactory,
+        migrationContainer = container,
+        callbacks = null,
+        allowMainThreadQueries = true,
+        journalMode = RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING,
+        queryExecutor = ArchTaskExecutor.getIOThreadExecutor(),
+        transactionExecutor = ArchTaskExecutor.getIOThreadExecutor(),
+        multiInstanceInvalidationServiceIntent = null,
+        requireMigration = true,
+        allowDestructiveMigrationOnDowngrade = false,
+        migrationNotRequiredFrom = emptySet(),
+        copyFromAssetPath = null,
+        copyFromFile = null,
+        copyFromInputStream = null,
+        prepackagedDatabaseCallback = null,
+        typeConverters = emptyList(),
+        autoMigrationSpecs = emptyList(),
+        allowDestructiveMigrationForAllTables = false,
+        sqliteDriver = sqliteDriver,
+        queryCoroutineContext = null
+    )
+}
 
-        override fun onValidateSchema(
-            db: SupportSQLiteDatabase
-        ): androidx.room.RoomOpenHelper.ValidationResult {
-            val tables = mDatabaseBundle.entitiesByTableName
-            tables.values.forEach { entity ->
-                when (entity) {
-                    is EntityBundle -> {
-                        val expected = entity.toTableInfo()
-                        val found = TableInfo.read(db, entity.tableName)
-                        if (expected != found) {
-                            return androidx.room.RoomOpenHelper.ValidationResult(
-                                false,
-                                """ ${expected.name.trimEnd()}
-                                |
-                                |Expected:
-                                |
-                                |$expected
-                                |
-                                |Found:
-                                |
-                                |$found
-                            """.trimMargin()
-                            )
-                        }
-                    }
-                    is FtsEntityBundle -> {
-                        val expected = entity.toFtsTableInfo()
-                        val found = FtsTableInfo.read(db, entity.tableName)
-                        if (expected != found) {
-                            return androidx.room.RoomOpenHelper.ValidationResult(
-                                false,
-                                """ ${expected.name.trimEnd()}
-                                |
-                                |Expected:
-                                |
-                                |$expected
-                                |
-                                |Found:
-                                |
-                                |$found
-                            """.trimMargin()
-                            )
-                        }
-                    }
-                }
-            }
-            mDatabaseBundle.views.forEach { view ->
-                val expected = view.toViewInfo()
-                val found = ViewInfo.read(db, view.viewName)
-                if (expected != found) {
-                    return androidx.room.RoomOpenHelper.ValidationResult(
-                        false,
-                        """ ${expected.name.trimEnd()}
-                                |
-                                |Expected: $expected
-                                |
-                                |Found: $found
-                            """.trimMargin()
-                    )
-                }
-            }
-            if (mVerifyDroppedTables) {
-                // now ensure tables that should be removed are removed.
-                val expectedTables = buildSet {
-                    tables.values.forEach { entity ->
-                        add(entity.tableName)
-                        if (entity is FtsEntityBundle) {
-                            addAll(entity.shadowTableNames)
-                        }
-                    }
-                }
-                db.query(
-                    "SELECT name FROM sqlite_master WHERE type='table'" +
-                        " AND name NOT IN(?, ?, ?)",
-                    arrayOf(
-                        Room.MASTER_TABLE_NAME, "android_metadata",
-                        "sqlite_sequence"
-                    )
-                ).useCursor { cursor ->
-                    while (cursor.moveToNext()) {
-                        val tableName = cursor.getString(0)
-                        if (!expectedTables.contains(tableName)) {
-                            return androidx.room.RoomOpenHelper.ValidationResult(
-                                false, "Unexpected table $tableName"
-                            )
-                        }
-                    }
-                }
-            }
-            return androidx.room.RoomOpenHelper.ValidationResult(true, null)
-        }
-    }
+/**
+ * Compatibility implementation of the [MigrationTestHelper] for [SupportSQLiteOpenHelper] and
+ * [SupportSQLiteDatabase].
+ */
+private class SupportSQLiteMigrationTestHelper(
+    instrumentation: Instrumentation,
+    assetsFolder: String,
+    databaseClass: Class<out RoomDatabase>?,
+    private val openFactory: SupportSQLiteOpenHelper.Factory,
+    private val autoMigrationSpecs: List<AutoMigrationSpec>,
+) : AndroidMigrationTestHelper(instrumentation, assetsFolder) {
 
-    @Suppress("DEPRECATION") // Due to RoomOpenHelper
-    internal class CreatingDelegate(
-        databaseBundle: DatabaseBundle
-    ) : RoomOpenHelperDelegate(databaseBundle) {
-        override fun createAllTables(db: SupportSQLiteDatabase) {
-            mDatabaseBundle.buildCreateQueries().forEach { query ->
-                db.execSQL(query)
+    private val context = instrumentation.targetContext
+    private val databaseInstance: RoomDatabase = if (databaseClass == null) {
+        object : RoomDatabase() {
+            override fun createInvalidationTracker(): InvalidationTracker {
+                return InvalidationTracker(this, emptyMap(), emptyMap())
+            }
+            override fun clearAllTables() {
+                error("Function should never be called during tests.")
+            }
+            override fun createAutoMigrations(
+                autoMigrationSpecs: Map<KClass<out AutoMigrationSpec>, AutoMigrationSpec>
+            ): List<Migration> {
+                return emptyList()
             }
         }
-
-        override fun onValidateSchema(
-            db: SupportSQLiteDatabase
-        ): androidx.room.RoomOpenHelper.ValidationResult {
-            throw UnsupportedOperationException(
-                "This open helper just creates the database but it received a migration request."
-            )
-        }
+    } else {
+        findAndInstantiateDatabaseImpl(databaseClass)
     }
 
-    @Suppress("DEPRECATION") // Due to RoomOpenHelper
-    internal abstract class RoomOpenHelperDelegate(
-        val mDatabaseBundle: DatabaseBundle
-    ) : androidx.room.RoomOpenHelper.Delegate(
-            mDatabaseBundle.version
-        ) {
-        override fun dropAllTables(db: SupportSQLiteDatabase) {
-            throw UnsupportedOperationException("cannot drop all tables in the test")
+    fun createDatabase(name: String, version: Int): SupportSQLiteDatabase {
+        val dbPath = context.getDatabasePath(name)
+        if (dbPath.exists()) {
+            check(dbPath.delete()) {
+                "There is a database file and I could not delete it."
+            }
+        }
+        val schemaBundle = loadSchema(version)
+        val connection = createDatabaseCommon(
+            schema = schemaBundle.database,
+            configurationFactory = ::createConfiguration,
+            connectionManagerFactory = { config, openDelegate ->
+                SupportTestConnectionManager(config.copy(name = name), openDelegate)
+            }
+        )
+        managedConnections.add(WeakReference(connection))
+        check(connection is SupportSQLiteConnection) {
+            "Expected connection to be a SupportSQLiteConnection but was ${connection::class}"
+        }
+        return connection.db
+    }
+
+    fun runMigrationsAndValidate(
+        name: String,
+        version: Int,
+        validateDroppedTables: Boolean,
+        migrations: Array<out Migration>
+    ): SupportSQLiteDatabase {
+        val dbPath = context.getDatabasePath(name)
+        check(dbPath.exists()) {
+            "Cannot find the database file for $name. " +
+                "Before calling runMigrations, you must first create the database via " +
+                "createDatabase()."
+        }
+        val schemaBundle = loadSchema(version)
+        val connection = runMigrationsAndValidateCommon(
+            databaseInstance = databaseInstance,
+            schema = schemaBundle.database,
+            migrations = migrations.toList(),
+            autoMigrationSpecs = autoMigrationSpecs,
+            validateUnknownTables = validateDroppedTables,
+            configurationFactory = ::createConfiguration,
+            connectionManagerFactory = { config, openDelegate ->
+                SupportTestConnectionManager(config.copy(name = name), openDelegate)
+            }
+        )
+        managedConnections.add(WeakReference(connection))
+        check(connection is SupportSQLiteConnection) {
+            "Expected connection to be a SupportSQLiteConnection but was ${connection::class}"
+        }
+        return connection.db
+    }
+
+    private fun createConfiguration(container: RoomDatabase.MigrationContainer) =
+        createDatabaseConfiguration(container, openFactory, null, null)
+
+    private class SupportTestConnectionManager(
+        override val configuration: DatabaseConfiguration,
+        override val openDelegate: RoomOpenDelegate
+    ) : TestConnectionManager() {
+
+        private val driverWrapper: SQLiteDriver
+
+        init {
+            val openFactory = checkNotNull(configuration.sqliteOpenHelperFactory)
+            val openHelperConfig =
+                SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
+                    .name(configuration.name)
+                    .callback(SupportOpenHelperCallback(openDelegate.version))
+                    .build()
+            val supportDriver = SupportSQLiteDriver(openFactory.create(openHelperConfig))
+            this.driverWrapper = DriverWrapper(supportDriver)
         }
 
-        override fun onCreate(db: SupportSQLiteDatabase) {}
-        override fun onOpen(db: SupportSQLiteDatabase) {}
+        override fun openConnection() = driverWrapper.open(configuration.name ?: ":memory:")
+
+        inner class SupportOpenHelperCallback(
+            version: Int
+        ) : SupportSQLiteOpenHelper.Callback(version) {
+            override fun onCreate(db: SupportSQLiteDatabase) {
+                this@SupportTestConnectionManager.onCreate(
+                    SupportSQLiteConnection(db)
+                )
+            }
+
+            override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+                this@SupportTestConnectionManager.onMigrate(
+                    SupportSQLiteConnection(db), oldVersion, newVersion
+                )
+            }
+
+            override fun onDowngrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
+                this.onUpgrade(db, oldVersion, newVersion)
+            }
+
+            override fun onOpen(db: SupportSQLiteDatabase) {
+                this@SupportTestConnectionManager.onOpen(SupportSQLiteConnection(db))
+            }
+        }
+    }
+}
+
+/**
+ * Implementation of the [MigrationTestHelper] for [SQLiteDriver] and [SQLiteConnection].
+ */
+private class SQLiteDriverMigrationTestHelper(
+    instrumentation: Instrumentation,
+    assetsFolder: String,
+    private val driver: SQLiteDriver,
+    databaseClass: KClass<out RoomDatabase>,
+    databaseFactory: () -> RoomDatabase,
+    private val fileName: String,
+    private val autoMigrationSpecs: List<AutoMigrationSpec>
+) : AndroidMigrationTestHelper(instrumentation, assetsFolder) {
+
+    private val databaseInstance = databaseClass.cast(databaseFactory.invoke())
+
+    fun createDatabase(version: Int): SQLiteConnection {
+        val schemaBundle = loadSchema(version)
+        val connection = createDatabaseCommon(
+            schema = schemaBundle.database,
+            configurationFactory = ::createConfiguration
+        )
+        managedConnections.add(WeakReference(connection))
+        return connection
     }
 
-    internal companion object {
-        private const val TAG = "MigrationTestHelper"
+    fun runMigrationsAndValidate(
+        version: Int,
+        migrations: List<Migration>,
+    ): SQLiteConnection {
+        val schemaBundle = loadSchema(version)
+        val connection = runMigrationsAndValidateCommon(
+            databaseInstance = databaseInstance,
+            schema = schemaBundle.database,
+            migrations = migrations,
+            autoMigrationSpecs = autoMigrationSpecs,
+            validateUnknownTables = false,
+            configurationFactory = ::createConfiguration
+        )
+        managedConnections.add(WeakReference(connection))
+        return connection
     }
+
+    private fun createConfiguration(container: RoomDatabase.MigrationContainer) =
+        createDatabaseConfiguration(container, null, driver, fileName)
 }
diff --git a/room/room-testing/src/androidMain/kotlin/androidx/room/testing/BundleUtil.android.kt b/room/room-testing/src/commonMain/kotlin/androidx/room/testing/BundleUtil.kt
similarity index 98%
rename from room/room-testing/src/androidMain/kotlin/androidx/room/testing/BundleUtil.android.kt
rename to room/room-testing/src/commonMain/kotlin/androidx/room/testing/BundleUtil.kt
index 9bde741..a9a5390 100644
--- a/room/room-testing/src/androidMain/kotlin/androidx/room/testing/BundleUtil.android.kt
+++ b/room/room-testing/src/commonMain/kotlin/androidx/room/testing/BundleUtil.kt
@@ -30,6 +30,7 @@
 import androidx.room.util.FtsTableInfo
 import androidx.room.util.TableInfo
 import androidx.room.util.ViewInfo
+import kotlin.jvm.JvmName
 
 internal fun EntityBundle.toTableInfo(): TableInfo {
     return TableInfo(
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/MigrationTestHelper.kt b/room/room-testing/src/commonMain/kotlin/androidx/room/testing/MigrationTestHelper.kt
new file mode 100644
index 0000000..64ccb0d
--- /dev/null
+++ b/room/room-testing/src/commonMain/kotlin/androidx/room/testing/MigrationTestHelper.kt
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing
+
+import androidx.room.BaseRoomConnectionManager
+import androidx.room.DatabaseConfiguration
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.RoomOpenDelegate
+import androidx.room.Transactor
+import androidx.room.migration.AutoMigrationSpec
+import androidx.room.migration.Migration
+import androidx.room.migration.bundle.DatabaseBundle
+import androidx.room.migration.bundle.EntityBundle
+import androidx.room.migration.bundle.FtsEntityBundle
+import androidx.room.util.FtsTableInfo
+import androidx.room.util.TableInfo
+import androidx.room.util.ViewInfo
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.execSQL
+import androidx.sqlite.use
+import kotlin.reflect.KClass
+import kotlin.reflect.safeCast
+
+/**
+ * A class that can help test and verify database creation and migration at different versions with
+ * different schemas.
+ *
+ * Common usage of this helper is to create a database at an older version first and then
+ * attempt a migration and validation:
+ * ```
+ * @Test
+ * fun migrationTest() {
+ *   val migrationTestHelper = getMigrationTestHelper()
+ *   // Create the database at version 1
+ *   val newConnection = migrationTestHelper.createDatabase(1)
+ *   // Insert some data that should be preserved
+ *   newConnection.execSQL("INSERT INTO Pet (id, name) VALUES (1, 'Tom')")
+ *   newConnection.close()
+ *
+ *   // Migrate the database to version 2
+ *   val migratedConnection =
+ *       migrationTestHelper.runMigrationsAndValidate(2, listOf(MIGRATION_1_2)))
+ *   migratedConnection.prepare("SELECT * FROM Pet).use { stmt ->
+ *     // Validates data is preserved between migrations.
+ *     assertThat(stmt.step()).isTrue()
+ *     assertThat(stmt.getText(1)).isEqualTo("Tom")
+ *   }
+ *   migratedConnection.close()
+ * }
+ * ```
+ *
+ * The helper relies on exported schemas so [androidx.room.Database.exportSchema] should
+ * be enabled. Schema location should be configured via Room's Gradle Plugin (id 'androidx.room'):
+ * ```
+ * room {
+ *   schemaDirectory("$projectDir/schemas")
+ * }
+ * ```
+ * The helper is then instantiated to use the same schema location where they are exported to. See
+ * platform-specific documentation for further configuration.
+ */
+expect class MigrationTestHelper {
+    /**
+     * Creates the database at the given version.
+     *
+     * Once a database is created it can further validate with [runMigrationsAndValidate].
+     *
+     * @param version The version of the schema at which the database should be created.
+     * @return A database connection of the newly created database.
+     * @throws IllegalStateException If a new database was not created.
+     */
+    fun createDatabase(version: Int): SQLiteConnection
+
+    /**
+     * Runs the given set of migrations on the existing database once created via [createDatabase].
+     *
+     * This function uses the same algorithm that Room performs to choose migrations such that the
+     * [migrations] instances provided must be sufficient to bring the database from current
+     * version to the desired version. If the database contains
+     * [androidx.room.AutoMigration]s, then those are already included in the list of migrations
+     * to execute if necessary. Note that provided manual migrations take precedence over
+     * auto migrations if they overlap in migration paths.
+     *
+     * Once migrations are done, this functions validates the database schema to ensure the
+     * migration performed resulted in the expected schema.
+     *
+     * @param version The final version the database should migrate to.
+     * @param migrations The list of migrations used to attempt the database migration.
+     * @return A database connection of the migrated database.
+     * @throws IllegalStateException If the schema validation fails.
+     */
+    fun runMigrationsAndValidate(
+        version: Int,
+        migrations: List<Migration> = emptyList()
+    ): SQLiteConnection
+}
+
+internal typealias ConnectionManagerFactory =
+        (DatabaseConfiguration, RoomOpenDelegate) -> TestConnectionManager
+
+internal typealias ConfigurationFactory =
+        (RoomDatabase.MigrationContainer) -> DatabaseConfiguration
+
+/**
+ * Common logic for [MigrationTestHelper.createDatabase]
+ */
+internal fun createDatabaseCommon(
+    schema: DatabaseBundle,
+    configurationFactory: ConfigurationFactory,
+    connectionManagerFactory: ConnectionManagerFactory = { config, openDelegate ->
+        DefaultTestConnectionManager(config, openDelegate)
+    }
+): SQLiteConnection {
+    val emptyContainer = RoomDatabase.MigrationContainer()
+    val configuration = configurationFactory.invoke(emptyContainer)
+    val testConnectionManager = connectionManagerFactory.invoke(
+        configuration, CreateOpenDelegate(schema)
+    )
+    return testConnectionManager.openConnection()
+}
+
+/**
+ * Common logic for [MigrationTestHelper.runMigrationsAndValidate]
+ */
+internal fun runMigrationsAndValidateCommon(
+    databaseInstance: RoomDatabase,
+    schema: DatabaseBundle,
+    migrations: List<Migration>,
+    autoMigrationSpecs: List<AutoMigrationSpec>,
+    validateUnknownTables: Boolean,
+    configurationFactory: ConfigurationFactory,
+    connectionManagerFactory: ConnectionManagerFactory = { config, openDelegate ->
+        DefaultTestConnectionManager(config, openDelegate)
+    }
+): SQLiteConnection {
+    val container = RoomDatabase.MigrationContainer()
+    container.addMigrations(migrations)
+    val autoMigrations = getAutoMigrations(databaseInstance, autoMigrationSpecs)
+    autoMigrations.forEach { autoMigration ->
+        val migrationExists = container.contains(
+            autoMigration.startVersion,
+            autoMigration.endVersion
+        )
+        if (!migrationExists) {
+            container.addMigration(autoMigration)
+        }
+    }
+    val configuration = configurationFactory.invoke(container)
+    val testConnectionManager = connectionManagerFactory.invoke(
+        configuration, MigrateOpenDelegate(schema, validateUnknownTables)
+    )
+    return testConnectionManager.openConnection()
+}
+
+private fun getAutoMigrations(
+    databaseInstance: RoomDatabase,
+    providedSpecs: List<AutoMigrationSpec>
+): List<Migration> {
+    val autoMigrationSpecMap =
+        createAutoMigrationSpecMap(
+            databaseInstance.getRequiredAutoMigrationSpecClasses(),
+            providedSpecs
+        )
+    return databaseInstance.createAutoMigrations(autoMigrationSpecMap)
+}
+
+private fun createAutoMigrationSpecMap(
+    requiredAutoMigrationSpecs: Set<KClass<out AutoMigrationSpec>>,
+    providedSpecs: List<AutoMigrationSpec>
+): Map<KClass<out AutoMigrationSpec>, AutoMigrationSpec> {
+    if (requiredAutoMigrationSpecs.isEmpty()) {
+        return emptyMap()
+    }
+    return buildMap {
+        requiredAutoMigrationSpecs.forEach { spec ->
+            val match = providedSpecs.firstOrNull { provided ->
+                spec.safeCast(provided) != null
+            }
+            requireNotNull(match) {
+                "A required auto migration spec (${spec.qualifiedName}) has not been provided."
+            }
+            put(spec, match)
+        }
+    }
+}
+
+internal abstract class TestConnectionManager : BaseRoomConnectionManager() {
+    override val callbacks: List<RoomDatabase.Callback> = emptyList()
+
+    override suspend fun <R> useConnection(
+        isReadOnly: Boolean,
+        block: suspend (Transactor) -> R
+    ): R {
+        error("Function should never be invoked during tests.")
+    }
+
+    abstract fun openConnection(): SQLiteConnection
+}
+
+private class DefaultTestConnectionManager(
+    override val configuration: DatabaseConfiguration,
+    override val openDelegate: RoomOpenDelegate
+) : TestConnectionManager() {
+
+    private val driverWrapper = DriverWrapper(requireNotNull(configuration.sqliteDriver))
+
+    override fun openConnection() = driverWrapper.open(configuration.name ?: ":memory:")
+}
+
+private sealed class TestOpenDelegate(
+    databaseBundle: DatabaseBundle
+) : RoomOpenDelegate(databaseBundle.version, databaseBundle.identityHash) {
+    override fun onCreate(connection: SQLiteConnection) {}
+    override fun onPreMigrate(connection: SQLiteConnection) {}
+    override fun onPostMigrate(connection: SQLiteConnection) {}
+    override fun onOpen(connection: SQLiteConnection) {}
+
+    override fun dropAllTables(connection: SQLiteConnection) {
+        error("Can't drop all tables during a test.")
+    }
+}
+
+private class CreateOpenDelegate(
+    val databaseBundle: DatabaseBundle
+) : TestOpenDelegate(databaseBundle) {
+    private var createAllTables = false
+
+    override fun onOpen(connection: SQLiteConnection) {
+        check(createAllTables) {
+            "Creation of tables didn't occur while creating a new database. A database at the " +
+                "driver configured path likely already exists. Did you forget to delete it?"
+        }
+    }
+
+    override fun onValidateSchema(connection: SQLiteConnection): ValidationResult {
+        error("Validation of schemas should never occur while creating a new database.")
+    }
+
+    override fun createAllTables(connection: SQLiteConnection) {
+        databaseBundle.buildCreateQueries().forEach { createSql ->
+            connection.execSQL(createSql)
+        }
+        createAllTables = true
+    }
+}
+
+private class MigrateOpenDelegate(
+    val databaseBundle: DatabaseBundle,
+    val validateUnknownTables: Boolean
+) : TestOpenDelegate(databaseBundle) {
+    override fun onValidateSchema(connection: SQLiteConnection): ValidationResult {
+        val tables = databaseBundle.entitiesByTableName
+        tables.values.forEach { entity ->
+            when (entity) {
+                is EntityBundle -> {
+                    val expected = entity.toTableInfo()
+                    val found = TableInfo.read(connection, entity.tableName)
+                    if (expected != found) {
+                        return ValidationResult(
+                            isValid = false,
+                            expectedFoundMsg =
+                                """ ${expected.name.trimEnd()}
+                                |
+                                |Expected:
+                                |
+                                |$expected
+                                |
+                                |Found:
+                                |
+                                |$found
+                                """.trimMargin()
+                        )
+                    }
+                }
+                is FtsEntityBundle -> {
+                    val expected = entity.toFtsTableInfo()
+                    val found = FtsTableInfo.read(connection, entity.tableName)
+                    if (expected != found) {
+                        return ValidationResult(
+                            isValid = false,
+                            expectedFoundMsg =
+                                """ ${expected.name.trimEnd()}
+                                |
+                                |Expected:
+                                |
+                                |$expected
+                                |
+                                |Found:
+                                |
+                                |$found
+                                """.trimMargin()
+                        )
+                    }
+                }
+            }
+        }
+        databaseBundle.views.forEach { view ->
+            val expected = view.toViewInfo()
+            val found = ViewInfo.read(connection, view.viewName)
+            if (expected != found) {
+                return ValidationResult(
+                    isValid = false,
+                    expectedFoundMsg =
+                        """ ${expected.name.trimEnd()}
+                        |
+                        |Expected: $expected
+                        |
+                        |Found: $found
+                        """.trimMargin()
+                )
+            }
+        }
+        if (validateUnknownTables) {
+            val expectedTables = buildSet {
+                tables.values.forEach { entity ->
+                    add(entity.tableName)
+                    if (entity is FtsEntityBundle) {
+                        addAll(entity.shadowTableNames)
+                    }
+                }
+            }
+            connection.prepare(
+                """
+                SELECT name FROM sqlite_master
+                WHERE type = 'table' AND name NOT IN (?, ?, ?)
+                """.trimIndent()
+            ).use { statement ->
+                statement.bindText(1, Room.MASTER_TABLE_NAME)
+                statement.bindText(2, "sqlite_sequence")
+                statement.bindText(3, "android_metadata")
+                while (statement.step()) {
+                    val tableName = statement.getText(0)
+                    if (!expectedTables.contains(tableName)) {
+                        return ValidationResult(
+                            isValid = false,
+                            expectedFoundMsg = "Unexpected table $tableName"
+                        )
+                    }
+                }
+            }
+        }
+        return ValidationResult(true, null)
+    }
+
+    override fun createAllTables(connection: SQLiteConnection) {
+        error(
+            "Creation of tables should never occur while validating migrations. Did you forget " +
+                "to first create the database?"
+        )
+    }
+}
diff --git a/room/room-testing/src/jvmMain/kotlin/androidx/room/testing/MigrationTestHelper.jvm.kt b/room/room-testing/src/jvmMain/kotlin/androidx/room/testing/MigrationTestHelper.jvm.kt
new file mode 100644
index 0000000..fa527a3
--- /dev/null
+++ b/room/room-testing/src/jvmMain/kotlin/androidx/room/testing/MigrationTestHelper.jvm.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing
+
+import androidx.room.DatabaseConfiguration
+import androidx.room.RoomDatabase
+import androidx.room.migration.AutoMigrationSpec
+import androidx.room.migration.Migration
+import androidx.room.migration.bundle.SchemaBundle
+import androidx.room.util.findAndInstantiateDatabaseImpl
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteDriver
+import java.nio.file.Path
+import kotlin.io.path.inputStream
+import kotlin.reflect.KClass
+import kotlin.reflect.cast
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * A class that can help test and verify database creation and migration at different versions with
+ * different schemas.
+ *
+ * Common usage of this helper is to create a database at an older version first and then
+ * attempt a migration and validation:
+ * ```
+ * @get:Rule
+ * val migrationTestHelper = MigrationTestHelper(
+ *    schemaDirectoryPath = Path("schemas")
+ *    driver = sqliteDriver,
+ *    databaseClass = PetDatabase::class
+ * )
+ *
+ * @Test
+ * fun migrationTest() {
+ *   // Create the database at version 1
+ *   val newConnection = migrationTestHelper.createDatabase(1)
+ *   // Insert some data that should be preserved
+ *   newConnection.execSQL("INSERT INTO Pet (id, name) VALUES (1, 'Tom')")
+ *   newConnection.close()
+ *
+ *   // Migrate the database to version 2
+ *   val migratedConnection =
+ *       migrationTestHelper.runMigrationsAndValidate(2, listOf(MIGRATION_1_2)))
+ *   migratedConnection.prepare("SELECT * FROM Pet).use { stmt ->
+ *     // Validates data is preserved between migrations.
+ *     assertThat(stmt.step()).isTrue()
+ *     assertThat(stmt.getText(1)).isEqualTo("Tom")
+ *   }
+ *   migratedConnection.close()
+ * }
+ * ```
+ *
+ * The helper relies on exported schemas so [androidx.room.Database.exportSchema] should
+ * be enabled. Schema location should be configured via Room's Gradle Plugin (id 'androidx.room'):
+ * ```
+ * room {
+ *   schemaDirectory("$projectDir/schemas")
+ * }
+ * ```
+ * The [schemaDirectoryPath] must match the exported schema location for this helper to properly
+ * create and validate schemas.
+ *
+ * @param schemaDirectoryPath The schema directory where schema files are exported.
+ * @param databasePath Name of the database.
+ * @param driver A driver that opens connection to a file database. A driver that opens connections
+ * to an in-memory database would be meaningless.
+ * @param databaseClass The [androidx.room.Database] annotated class.
+ * @param databaseFactory An optional factory function to create an instance of the [databaseClass].
+ * Should be the same factory used when building the database via [androidx.room.Room.databaseBuilder].
+ * @param autoMigrationSpecs The list of [androidx.room.ProvidedAutoMigrationSpec] instances
+ * for [androidx.room.AutoMigration]s that require them.
+ */
+actual class MigrationTestHelper(
+    private val schemaDirectoryPath: Path,
+    private val databasePath: Path,
+    private val driver: SQLiteDriver,
+    private val databaseClass: KClass<out RoomDatabase>,
+    databaseFactory: () -> RoomDatabase = {
+        findAndInstantiateDatabaseImpl(databaseClass.java)
+    },
+    private val autoMigrationSpecs: List<AutoMigrationSpec> = emptyList()
+) : TestWatcher() {
+
+    private val databaseInstance = databaseClass.cast(databaseFactory.invoke())
+    private val managedConnections = mutableListOf<SQLiteConnection>()
+
+    /**
+     * Creates the database at the given version.
+     *
+     * Once a database is created it can further validate with [runMigrationsAndValidate].
+     *
+     * @param version The version of the schema at which the database should be created.
+     * @return A database connection of the newly created database.
+     * @throws IllegalStateException If a new database was not created.
+     */
+    actual fun createDatabase(version: Int): SQLiteConnection {
+        val schemaBundle = loadSchema(version)
+        val connection = createDatabaseCommon(
+            schema = schemaBundle.database,
+            configurationFactory = ::createDatabaseConfiguration
+        )
+        managedConnections.add(connection)
+        return connection
+    }
+
+    /**
+     * Runs the given set of migrations on the existing database once created via [createDatabase].
+     *
+     * This function uses the same algorithm that Room performs to choose migrations such that the
+     * [migrations] instances provided must be sufficient to bring the database from current
+     * version to the desired version. If the database contains
+     * [androidx.room.AutoMigration]s, then those are already included in the list of migrations
+     * to execute if necessary. Note that provided manual migrations take precedence over
+     * auto migrations if they overlap in migration paths.
+     *
+     * Once migrations are done, this functions validates the database schema to ensure the
+     * migration performed resulted in the expected schema.
+     *
+     * @param version The final version the database should migrate to.
+     * @param migrations The list of migrations used to attempt the database migration.
+     * @throws IllegalStateException If the schema validation fails.
+     */
+    actual fun runMigrationsAndValidate(
+        version: Int,
+        migrations: List<Migration>,
+    ): SQLiteConnection {
+        val schemaBundle = loadSchema(version)
+        val connection = runMigrationsAndValidateCommon(
+            databaseInstance = databaseInstance,
+            schema = schemaBundle.database,
+            migrations = migrations,
+            autoMigrationSpecs = autoMigrationSpecs,
+            validateUnknownTables = false,
+            configurationFactory = ::createDatabaseConfiguration
+        )
+        managedConnections.add(connection)
+        return connection
+    }
+
+    override fun finished(description: Description?) {
+        super.finished(description)
+        managedConnections.forEach(SQLiteConnection::close)
+    }
+
+    private fun loadSchema(version: Int): SchemaBundle {
+        val databaseFQN = checkNotNull(databaseClass.qualifiedName)
+        val schemaPath = schemaDirectoryPath.resolve(databaseFQN).resolve("$version.json")
+        return schemaPath.inputStream().use { SchemaBundle.deserialize(it) }
+    }
+
+    private fun createDatabaseConfiguration(
+        container: RoomDatabase.MigrationContainer,
+    ) = DatabaseConfiguration(
+        name = databasePath.toString(),
+        migrationContainer = container,
+        callbacks = null,
+        journalMode = RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING,
+        requireMigration = true,
+        allowDestructiveMigrationOnDowngrade = false,
+        migrationNotRequiredFrom = null,
+        typeConverters = emptyList(),
+        autoMigrationSpecs = emptyList(),
+        sqliteDriver = driver,
+        queryCoroutineContext = null
+    )
+}
diff --git a/room/room-testing/src/nativeMain/kotlin/androidx/room/testing/MigrationTestHelper.native.kt b/room/room-testing/src/nativeMain/kotlin/androidx/room/testing/MigrationTestHelper.native.kt
new file mode 100644
index 0000000..c0e69bc
--- /dev/null
+++ b/room/room-testing/src/nativeMain/kotlin/androidx/room/testing/MigrationTestHelper.native.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing
+
+import androidx.room.DatabaseConfiguration
+import androidx.room.RoomDatabase
+import androidx.room.migration.AutoMigrationSpec
+import androidx.room.migration.Migration
+import androidx.room.migration.bundle.SchemaBundle
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.SQLiteDriver
+import kotlin.reflect.KClass
+import kotlin.reflect.cast
+import okio.FileSystem
+import okio.Path.Companion.toPath
+
+/**
+ * A class that can help test and verify database creation and migration at different versions with
+ * different schemas.
+ *
+ * Common usage of this helper is to create a database at an older version first and then
+ * attempt a migration and validation:
+ * ```
+ * private val migrationTestHelper = MigrationTestHelper(
+ *    schemaDirectoryPath = Path("schemas")
+ *    driver = sqliteDriver,
+ *    databaseClass = PetDatabase::class,
+ *    databaseFactory = { PetDatabase::class.instantiateImpl() }
+ * )
+ *
+ * @AfterTest
+ * fun after() {
+ *   migrationTestHelper.finished()
+ * }
+ *
+ * @Test
+ * fun migrationTest() {
+ *   // Create the database at version 1
+ *   val newConnection = migrationTestHelper.createDatabase(1)
+ *   // Insert some data that should be preserved
+ *   newConnection.execSQL("INSERT INTO Pet (id, name) VALUES (1, 'Tom')")
+ *   newConnection.close()
+ *
+ *   // Migrate the database to version 2
+ *   val migratedConnection =
+ *       migrationTestHelper.runMigrationsAndValidate(2, listOf(MIGRATION_1_2)))
+ *   migratedConnection.prepare("SELECT * FROM Pet).use { stmt ->
+ *     // Validates data is preserved between migrations.
+ *     assertThat(stmt.step()).isTrue()
+ *     assertThat(stmt.getText(1)).isEqualTo("Tom")
+ *   }
+ *   migratedConnection.close()
+ * }
+ * ```
+ *
+ * The helper relies on exported schemas so [androidx.room.Database.exportSchema] should
+ * be enabled. Schema location should be configured via Room's Gradle Plugin (id 'androidx.room'):
+ * ```
+ * room {
+ *   schemaDirectory("$projectDir/schemas")
+ * }
+ * ```
+ * The [schemaDirectoryPath] must match the exported schema location for this helper to properly
+ * create and validate schemas.
+ *
+ * @param schemaDirectoryPath The schema directory where schema files are exported.
+ * @param fileName Name of the database.
+ * @param driver A driver that opens connection to a file database. A driver that opens connections
+ * to an in-memory database would be meaningless.
+ * @param databaseClass The [androidx.room.Database] annotated class.
+ * @param databaseFactory The factory function to create an instance of the [databaseClass]. Should
+ * be the same factory used when building the database via [androidx.room.Room.databaseBuilder].
+ * @param autoMigrationSpecs The list of [androidx.room.ProvidedAutoMigrationSpec] instances
+ * for [androidx.room.AutoMigration]s that require them.
+ */
+actual class MigrationTestHelper(
+    private val schemaDirectoryPath: String,
+    private val fileName: String,
+    private val driver: SQLiteDriver,
+    private val databaseClass: KClass<out RoomDatabase>,
+    databaseFactory: () -> RoomDatabase,
+    private val autoMigrationSpecs: List<AutoMigrationSpec> = emptyList()
+) {
+    private val databaseInstance = databaseClass.cast(databaseFactory.invoke())
+    private val managedConnections = mutableListOf<SQLiteConnection>()
+
+    /**
+     * Creates the database at the given version.
+     *
+     * Once a database is created it can further validate with [runMigrationsAndValidate].
+     *
+     * @param version The version of the schema at which the database should be created.
+     * @return A database connection of the newly created database.
+     * @throws IllegalStateException If a new database was not created.
+     */
+    actual fun createDatabase(version: Int): SQLiteConnection {
+        val schemaBundle = loadSchema(version)
+        val connection = createDatabaseCommon(
+            schema = schemaBundle.database,
+            configurationFactory = ::createDatabaseConfiguration
+        )
+        managedConnections.add(connection)
+        return connection
+    }
+
+    /**
+     * Runs the given set of migrations on the existing database once created via [createDatabase].
+     *
+     * This function uses the same algorithm that Room performs to choose migrations such that the
+     * [migrations] instances provided must be sufficient to bring the database from current
+     * version to the desired version. If the database contains
+     * [androidx.room.AutoMigration]s, then those are already included in the list of migrations
+     * to execute if necessary. Note that provided manual migrations take precedence over
+     * auto migrations if they overlap in migration paths.
+     *
+     * Once migrations are done, this functions validates the database schema to ensure the
+     * migration performed resulted in the expected schema.
+     *
+     * @param version The final version the database should migrate to.
+     * @param migrations The list of migrations used to attempt the database migration.
+     * @throws IllegalStateException If the schema validation fails.
+     */
+    actual fun runMigrationsAndValidate(
+        version: Int,
+        migrations: List<Migration>,
+    ): SQLiteConnection {
+        val schemaBundle = loadSchema(version)
+        val connection = runMigrationsAndValidateCommon(
+            databaseInstance = databaseInstance,
+            schema = schemaBundle.database,
+            migrations = migrations,
+            autoMigrationSpecs = autoMigrationSpecs,
+            validateUnknownTables = false,
+            configurationFactory = ::createDatabaseConfiguration
+        )
+        managedConnections.add(connection)
+        return connection
+    }
+
+    fun finished() {
+        managedConnections.forEach(SQLiteConnection::close)
+    }
+
+    private fun loadSchema(version: Int): SchemaBundle {
+        val databaseFQN = checkNotNull(databaseClass.qualifiedName)
+        val schemaPath = schemaDirectoryPath.toPath().resolve(databaseFQN).resolve("$version.json")
+        return FileSystem.SYSTEM.read(schemaPath) { SchemaBundle.deserialize(this) }
+    }
+
+    private fun createDatabaseConfiguration(
+        container: RoomDatabase.MigrationContainer,
+    ) = DatabaseConfiguration(
+        name = fileName,
+        migrationContainer = container,
+        callbacks = null,
+        journalMode = RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING,
+        requireMigration = true,
+        allowDestructiveMigrationOnDowngrade = false,
+        migrationNotRequiredFrom = null,
+        typeConverters = emptyList(),
+        autoMigrationSpecs = emptyList(),
+        sqliteDriver = driver,
+        queryCoroutineContext = null
+    )
+}
diff --git a/security/security-state/OWNERS b/security/security-state/OWNERS
new file mode 100644
index 0000000..68166b1
--- /dev/null
+++ b/security/security-state/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 1432820
+musashi@google.com
+maunik@google.com
+alxu@google.com
+willcoster@google.com
diff --git a/security/security-state/api/current.txt b/security/security-state/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/security/security-state/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/security/security-state/api/res-current.txt b/security/security-state/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/security/security-state/api/res-current.txt
diff --git a/security/security-state/api/restricted_current.txt b/security/security-state/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/security/security-state/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/security/security-state/build.gradle b/security/security-state/build.gradle
new file mode 100644
index 0000000..c6874fe
--- /dev/null
+++ b/security/security-state/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    // Add dependencies here
+}
+
+android {
+    namespace "androidx.security.state"
+}
+
+androidx {
+    name = "androidx.security:security-state"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.SECURITY_STATE
+    inceptionYear = "2024"
+    description = "obtain security state of updateable components."
+}
diff --git a/security/security-state/src/main/java/androidx/security/androidx-security-security-state-documentation.md b/security/security-state/src/main/java/androidx/security/androidx-security-security-state-documentation.md
new file mode 100644
index 0000000..1193a35
--- /dev/null
+++ b/security/security-state/src/main/java/androidx/security/androidx-security-security-state-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+android.security security-state
+
+# Package androidx.security.state
+
+This library makes it easy for developers to obtain detailed security state of updatable components.
diff --git a/settings.gradle b/settings.gradle
index 9058003..491bec2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,3 @@
-import androidx.build.gradle.gcpbuildcache.GcpBuildCache
-import androidx.build.gradle.gcpbuildcache.GcpBuildCacheServiceFactory
 import groovy.transform.Field
 
 pluginManagement {
@@ -107,17 +105,9 @@
 
 def cacheSetting = System.getenv("USE_ANDROIDX_REMOTE_BUILD_CACHE")
 switch (cacheSetting) {
-    case ["true", "uplink"]: // legacy build cache
-        logger.warn("\u001B[31m\nYou are using legacy USE_ANDROIDX_REMOTE_BUILD_CACHE=$cacheSetting " +
-                "type, this cache has been turned down, so you are *not* using a remote cache. " +
-                "Please move to the new cache using http://go/androidx-dev#remote-build-cache\u001B[0m\n")
-        break
-    case "gcp":
-        buildCache {
-            registerBuildCacheService(GcpBuildCache, GcpBuildCacheServiceFactory)
-        }
+    case ["true", "gcp"]:
         settings.buildCache {
-            remote(GcpBuildCache) {
+            remote(androidx.build.gradle.gcpbuildcache.GcpBuildCache) {
                 projectId = "androidx-ge"
                 bucketName = "androidx-gradle-remote-cache"
                 messageOnAuthenticationFailure = "Your GCP Credentials have expired.\n" +
@@ -921,6 +911,7 @@
 includeProject(":security:security-crypto-ktx", [BuildType.MAIN])
 includeProject(":security:security-identity-credential", [BuildType.MAIN])
 includeProject(":security:security-mls", [BuildType.MAIN])
+includeProject(":security:security-state", [BuildType.MAIN])
 includeProject(":sharetarget:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":sharetarget:sharetarget", [BuildType.MAIN])
 includeProject(":slice:slice-benchmark", [BuildType.MAIN])
diff --git a/sharetarget/sharetarget/api/restricted_current.txt b/sharetarget/sharetarget/api/restricted_current.txt
index 950bca2..a481389 100644
--- a/sharetarget/sharetarget/api/restricted_current.txt
+++ b/sharetarget/sharetarget/api/restricted_current.txt
@@ -6,7 +6,7 @@
     method public java.util.List<android.service.chooser.ChooserTarget!>! onGetChooserTargets(android.content.ComponentName!, android.content.IntentFilter!);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ShortcutInfoCompatSaverImpl extends androidx.core.content.pm.ShortcutInfoCompatSaver<com.google.common.util.concurrent.ListenableFuture<java.lang.Void>> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ShortcutInfoCompatSaverImpl extends androidx.core.content.pm.ShortcutInfoCompatSaver<com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>!> {
     method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>! addShortcuts(java.util.List<androidx.core.content.pm.ShortcutInfoCompat!>!);
     method @AnyThread public static androidx.sharetarget.ShortcutInfoCompatSaverImpl! getInstance(android.content.Context!);
     method @WorkerThread public androidx.core.graphics.drawable.IconCompat! getShortcutIcon(String!) throws java.lang.Exception;
diff --git a/slice/slice-remotecallback/api/current.txt b/slice/slice-remotecallback/api/current.txt
index 5eed69e..01a91d4 100644
--- a/slice/slice-remotecallback/api/current.txt
+++ b/slice/slice-remotecallback/api/current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.slice.remotecallback {
 
-  public abstract class RemoteSliceProvider<T extends androidx.slice.remotecallback.RemoteSliceProvider> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackReceiver<T> {
+  public abstract class RemoteSliceProvider<T extends androidx.slice.remotecallback.RemoteSliceProvider> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackReceiver<T!> {
     ctor public RemoteSliceProvider();
     method public T createRemoteCallback(android.content.Context);
   }
diff --git a/slice/slice-remotecallback/api/restricted_current.txt b/slice/slice-remotecallback/api/restricted_current.txt
index aec2a8d..90ba6d5 100644
--- a/slice/slice-remotecallback/api/restricted_current.txt
+++ b/slice/slice-remotecallback/api/restricted_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.slice.remotecallback {
 
-  public abstract class RemoteSliceProvider<T extends androidx.slice.remotecallback.RemoteSliceProvider> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
+  public abstract class RemoteSliceProvider<T extends androidx.slice.remotecallback.RemoteSliceProvider> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T!> androidx.remotecallback.CallbackReceiver<T!> {
     ctor public RemoteSliceProvider();
     method public T createRemoteCallback(android.content.Context);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.remotecallback.RemoteCallback toRemoteCallback(Class<T!>, android.content.Context, String, android.os.Bundle, String);
diff --git a/slice/slice-view/api/current.txt b/slice/slice-view/api/current.txt
index c1cd45f..e3e7cad 100644
--- a/slice/slice-view/api/current.txt
+++ b/slice/slice-view/api/current.txt
@@ -147,7 +147,7 @@
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
   }
 
-  @Deprecated public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
+  @Deprecated public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder!> {
     ctor @Deprecated public SliceAdapter(android.content.Context);
     method @Deprecated public androidx.slice.widget.GridRowView getGridRowView();
     method @Deprecated public int getItemCount();
@@ -176,7 +176,7 @@
     method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
   }
 
-  @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
+  @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice!> {
     method @Deprecated public void goLive();
     method @Deprecated public void parseStream();
   }
@@ -192,7 +192,7 @@
   @Deprecated @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
   }
 
-  @Deprecated public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+  @Deprecated public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice!> android.view.View.OnClickListener {
     ctor @Deprecated public SliceView(android.content.Context!);
     ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?);
     ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?, int);
diff --git a/slice/slice-view/api/restricted_current.txt b/slice/slice-view/api/restricted_current.txt
index 0864f0a..3a64b6f 100644
--- a/slice/slice-view/api/restricted_current.txt
+++ b/slice/slice-view/api/restricted_current.txt
@@ -205,7 +205,7 @@
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
   }
 
-  @Deprecated public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
+  @Deprecated public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder!> {
     ctor @Deprecated public SliceAdapter(android.content.Context);
     method @Deprecated public androidx.slice.widget.GridRowView getGridRowView();
     method @Deprecated public int getItemCount();
@@ -235,7 +235,7 @@
     method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
   }
 
-  @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
+  @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice!> {
     method @Deprecated public void goLive();
     method @Deprecated public void parseStream();
   }
@@ -251,7 +251,7 @@
   @Deprecated @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
   }
 
-  @Deprecated public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+  @Deprecated public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice!> android.view.View.OnClickListener {
     ctor @Deprecated public SliceView(android.content.Context!);
     ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?);
     ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?, int);
diff --git a/sqlite/integration-tests/driver-conformance-test/build.gradle b/sqlite/integration-tests/driver-conformance-test/build.gradle
index 7dfaa55..58a482a 100644
--- a/sqlite/integration-tests/driver-conformance-test/build.gradle
+++ b/sqlite/integration-tests/driver-conformance-test/build.gradle
@@ -31,7 +31,6 @@
 
 androidXMultiplatform {
     android()
-    androidNative()
     ios()
     jvm()
     linux()
diff --git a/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/AndroidSQLiteDriverTest.kt b/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/AndroidSQLiteDriverTest.kt
index 58236fc..88a0989 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/AndroidSQLiteDriverTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/AndroidSQLiteDriverTest.kt
@@ -25,7 +25,7 @@
     override val driverType = TestDriverType.ANDROID_FRAMEWORK
 
     override fun getDriver(): SQLiteDriver {
-        return AndroidSQLiteDriver(":memory:")
+        return AndroidSQLiteDriver()
     }
 
     @Ignore // TODO(b/304297717): Align exception checking test with native.
diff --git a/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt b/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
index 3806d39..1772073 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/androidInstrumentedTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
@@ -24,6 +24,6 @@
     override val driverType = TestDriverType.BUNDLED
 
     override fun getDriver(): SQLiteDriver {
-        return BundledSQLiteDriver(filename = ":memory:")
+        return BundledSQLiteDriver()
     }
 }
diff --git a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseBundledConformanceTest.kt b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseBundledConformanceTest.kt
index 49b362c..b4d96bb 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseBundledConformanceTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseBundledConformanceTest.kt
@@ -23,7 +23,7 @@
 abstract class BaseBundledConformanceTest : BaseConformanceTest() {
     @Test
     fun readSQLiteVersion() {
-        val connection = getDriver().open()
+        val connection = getDriver().open(":memory:")
         try {
             val version = connection.prepare("SELECT sqlite_version()").use {
                 it.step()
diff --git a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
index a4621c1..fe51e5b 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/commonTest/kotlin/androidx/sqlite/driver/test/BaseConformanceTest.kt
@@ -40,7 +40,7 @@
     @Test
     fun openAndCloseConnection() {
         val driver = getDriver()
-        val connection = driver.open()
+        val connection = driver.open(":memory:")
         try {
             val version = connection.prepare("PRAGMA user_version").use { statement ->
                 statement.step()
@@ -55,28 +55,65 @@
     @Test
     fun bindAndReadColumns() = testWithConnection { connection ->
         connection.execSQL(
-            "CREATE TABLE Test(integerCol INTEGER, realCol REAL, textCol TEXT, blobCol BLOB)"
+            """
+            CREATE TABLE Test(
+                integerCol_long INTEGER,
+                integerCol_int INTEGER,
+                integerCol_boolean INTEGER,
+                realCol_double REAL,
+                realCol_float REAL,
+                textCol TEXT,
+                blobCol BLOB
+            )
+            """.trimIndent()
         )
-        connection.prepare(
-            "INSERT INTO Test (integerCol, realCol, textCol, blobCol) VALUES (?, ?, ?, ?)"
+        connection.prepare("""
+            INSERT INTO Test (
+                integerCol_long,
+                integerCol_int,
+                integerCol_boolean,
+                realCol_double,
+                realCol_float,
+                textCol,
+                blobCol
+            ) VALUES (?, ?, ?, ?, ?, ?, ?)
+        """.trimIndent()
         ).use {
             it.bindLong(1, 3)
-            it.bindDouble(2, 7.87)
-            it.bindText(3, "PR")
-            it.bindBlob(4, byteArrayOf(0x0F, 0x12, 0x1B))
+            it.bindInt(2, 22)
+            it.bindBoolean(3, true)
+            it.bindDouble(4, 7.87)
+            it.bindFloat(5, 9.39f)
+            it.bindText(6, "PR")
+            it.bindBlob(7, byteArrayOf(0x0F, 0x12, 0x1B))
             assertThat(it.step()).isFalse() // SQLITE_DONE
         }
         connection.prepare("SELECT * FROM Test").use {
             assertThat(it.step()).isTrue() // SQLITE_ROW
-            assertThat(it.getColumnCount()).isEqualTo(4)
-            assertThat(it.getColumnName(0)).isEqualTo("integerCol")
-            assertThat(it.getColumnName(1)).isEqualTo("realCol")
-            assertThat(it.getColumnName(2)).isEqualTo("textCol")
-            assertThat(it.getColumnName(3)).isEqualTo("blobCol")
+            assertThat(it.getColumnCount()).isEqualTo(7)
+            assertThat(it.getColumnName(0)).isEqualTo("integerCol_long")
+            assertThat(it.getColumnName(1)).isEqualTo("integerCol_int")
+            assertThat(it.getColumnName(2)).isEqualTo("integerCol_boolean")
+            assertThat(it.getColumnName(3)).isEqualTo("realCol_double")
+            assertThat(it.getColumnName(4)).isEqualTo("realCol_float")
+            assertThat(it.getColumnName(5)).isEqualTo("textCol")
+            assertThat(it.getColumnName(6)).isEqualTo("blobCol")
+            assertThat(it.getColumnNames()).containsExactly(
+                "integerCol_long",
+                "integerCol_int",
+                "integerCol_boolean",
+                "realCol_double",
+                "realCol_float",
+                "textCol",
+                "blobCol"
+            ).inOrder()
             assertThat(it.getLong(0)).isEqualTo(3)
-            assertThat(it.getDouble(1)).isEqualTo(7.87)
-            assertThat(it.getText(2)).isEqualTo("PR")
-            assertThat(it.getBlob(3)).isEqualTo(byteArrayOf(0x0F, 0x12, 0x1B))
+            assertThat(it.getInt(1)).isEqualTo(22)
+            assertThat(it.getBoolean(2)).isTrue()
+            assertThat(it.getDouble(3)).isEqualTo(7.87)
+            assertThat(it.getFloat(4)).isEqualTo(9.39f)
+            assertThat(it.getText(5)).isEqualTo("PR")
+            assertThat(it.getBlob(6)).isEqualTo(byteArrayOf(0x0F, 0x12, 0x1B))
             assertThat(it.step()).isFalse() // SQLITE_DONE
         }
     }
@@ -223,7 +260,7 @@
     @Test
     fun useClosedConnection() {
         val driver = getDriver()
-        val connection = driver.open()
+        val connection = driver.open(":memory:")
         connection.close()
         assertFailsWith<SQLiteException> {
             connection.prepare("SELECT * FROM Foo")
@@ -299,7 +336,7 @@
 
     private inline fun testWithConnection(block: (SQLiteConnection) -> Unit) {
         val driver = getDriver()
-        val connection = driver.open()
+        val connection = driver.open(":memory:")
         try {
             block.invoke(connection)
         } finally {
diff --git a/sqlite/integration-tests/driver-conformance-test/src/jvmTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt b/sqlite/integration-tests/driver-conformance-test/src/jvmTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
index 3806d39..1772073 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/jvmTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/jvmTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
@@ -24,6 +24,6 @@
     override val driverType = TestDriverType.BUNDLED
 
     override fun getDriver(): SQLiteDriver {
-        return BundledSQLiteDriver(filename = ":memory:")
+        return BundledSQLiteDriver()
     }
 }
diff --git a/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt b/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
index 3806d39..1772073 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/BundledSQLiteDriverTest.kt
@@ -24,6 +24,6 @@
     override val driverType = TestDriverType.BUNDLED
 
     override fun getDriver(): SQLiteDriver {
-        return BundledSQLiteDriver(filename = ":memory:")
+        return BundledSQLiteDriver()
     }
 }
diff --git a/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/NativeSQLiteDriverTest.kt b/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/NativeSQLiteDriverTest.kt
index 0ef3091..3d09510 100644
--- a/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/NativeSQLiteDriverTest.kt
+++ b/sqlite/integration-tests/driver-conformance-test/src/nativeTest/kotlin/androidx/sqlite/driver/test/NativeSQLiteDriverTest.kt
@@ -24,6 +24,6 @@
     override val driverType = TestDriverType.NATIVE_FRAMEWORK
 
     override fun getDriver(): SQLiteDriver {
-        return NativeSQLiteDriver(":memory:")
+        return NativeSQLiteDriver()
     }
 }
diff --git a/sqlite/sqlite-bundled/api/current.txt b/sqlite/sqlite-bundled/api/current.txt
index a09c7d3..a0f2650 100644
--- a/sqlite/sqlite-bundled/api/current.txt
+++ b/sqlite/sqlite-bundled/api/current.txt
@@ -2,8 +2,8 @@
 package androidx.sqlite.driver.bundled {
 
   public final class BundledSQLiteDriver implements androidx.sqlite.SQLiteDriver {
-    ctor public BundledSQLiteDriver(String filename);
-    method public androidx.sqlite.SQLiteConnection open();
+    ctor public BundledSQLiteDriver();
+    method public androidx.sqlite.SQLiteConnection open(String fileName);
   }
 
 }
diff --git a/sqlite/sqlite-bundled/api/restricted_current.txt b/sqlite/sqlite-bundled/api/restricted_current.txt
index a09c7d3..a0f2650 100644
--- a/sqlite/sqlite-bundled/api/restricted_current.txt
+++ b/sqlite/sqlite-bundled/api/restricted_current.txt
@@ -2,8 +2,8 @@
 package androidx.sqlite.driver.bundled {
 
   public final class BundledSQLiteDriver implements androidx.sqlite.SQLiteDriver {
-    ctor public BundledSQLiteDriver(String filename);
-    method public androidx.sqlite.SQLiteConnection open();
+    ctor public BundledSQLiteDriver();
+    method public androidx.sqlite.SQLiteConnection open(String fileName);
   }
 
 }
diff --git a/sqlite/sqlite-bundled/build.gradle b/sqlite/sqlite-bundled/build.gradle
index c7f512f..8df8903 100644
--- a/sqlite/sqlite-bundled/build.gradle
+++ b/sqlite/sqlite-bundled/build.gradle
@@ -143,7 +143,7 @@
         prepareSqliteSourcesTask.map { it.destinationDirectory }
     )
 
-    // Defince C++ compilation of JNI
+    // Define C++ compilation of JNI
     def jvmArtJniImplementation = createNativeCompilation("jvmArtJniImplementation") {
         configureEachTarget { nativeCompilation ->
             // add JNI headers as sources
@@ -169,7 +169,6 @@
     android() {
         addNativeLibrariesToJniLibs(it, jvmArtJniImplementation)
     }
-    androidNative()
     ios()
     jvm() {
         addNativeLibrariesToResources(it, jvmArtJniImplementation)
diff --git a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
index 8f89ece7..4b0ec4b 100644
--- a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
+++ b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
@@ -25,11 +25,9 @@
  * library.
  */
 // TODO(b/313895287): Explore usability of @FastNative and @CriticalNative for the external functions.
-actual class BundledSQLiteDriver actual constructor(
-    private val filename: String
-) : SQLiteDriver {
-    override fun open(): SQLiteConnection {
-        val address = nativeOpen(filename)
+actual class BundledSQLiteDriver : SQLiteDriver {
+    override fun open(fileName: String): SQLiteConnection {
+        val address = nativeOpen(fileName)
         return BundledSQLiteConnection(address)
     }
 
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
index b13f846..86e016cf 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.kt
@@ -22,4 +22,4 @@
  * A [SQLiteDriver] that uses a bundled version of SQLite included as a native component of this
  * library.
  */
-expect class BundledSQLiteDriver(filename: String) : SQLiteDriver
+expect class BundledSQLiteDriver() : SQLiteDriver
diff --git a/sqlite/sqlite-framework/api/current.txt b/sqlite/sqlite-framework/api/current.txt
index 0b10ff9..cc962ad 100644
--- a/sqlite/sqlite-framework/api/current.txt
+++ b/sqlite/sqlite-framework/api/current.txt
@@ -11,8 +11,8 @@
 package androidx.sqlite.driver {
 
   public final class AndroidSQLiteDriver implements androidx.sqlite.SQLiteDriver {
-    ctor public AndroidSQLiteDriver(String filename);
-    method public androidx.sqlite.SQLiteConnection open();
+    ctor public AndroidSQLiteDriver();
+    method public androidx.sqlite.SQLiteConnection open(String fileName);
   }
 
 }
diff --git a/sqlite/sqlite-framework/api/restricted_current.txt b/sqlite/sqlite-framework/api/restricted_current.txt
index 0b10ff9..cc962ad 100644
--- a/sqlite/sqlite-framework/api/restricted_current.txt
+++ b/sqlite/sqlite-framework/api/restricted_current.txt
@@ -11,8 +11,8 @@
 package androidx.sqlite.driver {
 
   public final class AndroidSQLiteDriver implements androidx.sqlite.SQLiteDriver {
-    ctor public AndroidSQLiteDriver(String filename);
-    method public androidx.sqlite.SQLiteConnection open();
+    ctor public AndroidSQLiteDriver();
+    method public androidx.sqlite.SQLiteConnection open(String fileName);
   }
 
 }
diff --git a/sqlite/sqlite-framework/build.gradle b/sqlite/sqlite-framework/build.gradle
index 9f4c995..0add91b 100644
--- a/sqlite/sqlite-framework/build.gradle
+++ b/sqlite/sqlite-framework/build.gradle
@@ -21,10 +21,11 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
 import androidx.build.PlatformIdentifier
 import androidx.build.Publish
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.konan.target.KonanTarget
+import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
@@ -53,7 +54,6 @@
 
 androidXMultiplatform {
     android()
-    androidNative()
     ios() {
         // Link to sqlite3 available in iOS
         binaries.all {
@@ -119,14 +119,11 @@
                 test.defaultSourceSet {
                     dependsOn(nativeTest)
                 }
-                if (target.konanTarget == KonanTarget.LINUX_X64.INSTANCE) {
+                if (target.konanTarget.family == Family.LINUX) {
                     // For tests in Linux host, statically include androidx's compiled SQLite
                     // via a generated C interop definition
                     createCinteropFromArchiveConfiguration(test, configurations["sqliteSharedArchive"])
-                } else if (
-                    target.konanTarget == KonanTarget.MACOS_X64.INSTANCE ||
-                        target.konanTarget == KonanTarget.MACOS_ARM64.INSTANCE
-                ) {
+                } else if (target.konanTarget.family == Family.OSX) {
                     // For tests in Mac host, link to shared SQLite library included in MacOS
                     test.kotlinOptions.freeCompilerArgs += [
                         "-linker-options", "-lsqlite3"
diff --git a/sqlite/sqlite-framework/src/androidInstrumentedTest/kotlin/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt b/sqlite/sqlite-framework/src/androidInstrumentedTest/kotlin/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt
index 2c3b1c0..e1421dc 100644
--- a/sqlite/sqlite-framework/src/androidInstrumentedTest/kotlin/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt
+++ b/sqlite/sqlite-framework/src/androidInstrumentedTest/kotlin/androidx/sqlite/db/framework/OpenHelperRecoveryTest.kt
@@ -251,6 +251,29 @@
         assertThat(openAttempts).isEqualTo(3)
     }
 
+    @Test
+    fun allowDataLossOnRecovery_onOpenRecursive() {
+        var openHelper: FrameworkSQLiteOpenHelper? = null
+        val badCallback = object : SupportSQLiteOpenHelper.Callback(1) {
+            override fun onCreate(db: SupportSQLiteDatabase) {}
+            override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {}
+            override fun onOpen(db: SupportSQLiteDatabase) {
+                openHelper!!.writableDatabase
+            }
+        }
+        // Use an open helper with a bad callback that will recursively try to open the database
+        // again, this is a user error and it is expected for the exception with the recursive
+        // stacktrace to be thrown and not swallowed.
+        openHelper = FrameworkSQLiteOpenHelper(context, dbName, badCallback, false, true)
+        try {
+            openHelper.writableDatabase
+            fail("Database should have failed to open.")
+        } catch (ex: RuntimeException) {
+            // Expected
+            assertThat(ex.message).contains("getDatabase called recursively")
+        }
+    }
+
     class EmptyCallback(version: Int = 1) : SupportSQLiteOpenHelper.Callback(version) {
         override fun onCreate(db: SupportSQLiteDatabase) {
         }
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.android.kt
index 9ed47c09..08ab1a76 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.android.kt
@@ -170,7 +170,6 @@
                 return getWritableOrReadableDatabase(writable)
             } catch (t: Throwable) {
                 // No good, just try again...
-                super.close()
             }
             try {
                 // Wait before trying to open the DB, ideally enough to account for some slow I/O.
@@ -182,7 +181,6 @@
             val openRetryError: Throwable = try {
                 return getWritableOrReadableDatabase(writable)
             } catch (t: Throwable) {
-                super.close()
                 t
             }
             if (openRetryError is CallbackException) {
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
index 9e4e28e..c9cdfe9 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteDriver.android.kt
@@ -24,11 +24,9 @@
  * A [SQLiteDriver] implemented by [android.database] and that uses the Android's SDK SQLite
  * APIs.
  */
-class AndroidSQLiteDriver(
-    private val filename: String
-) : SQLiteDriver {
-    override fun open(): SQLiteConnection {
-        val database = SQLiteDatabase.openOrCreateDatabase(filename, null)
+class AndroidSQLiteDriver : SQLiteDriver {
+    override fun open(fileName: String): SQLiteConnection {
+        val database = SQLiteDatabase.openOrCreateDatabase(fileName, null)
         return AndroidSQLiteConnection(database)
     }
 }
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
index e9efb3e..4bd45ed 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteDriver.kt
@@ -38,13 +38,11 @@
 //    (b/307917398) more open flags
 //    (b/304295573) busy handler registering
 @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
-class NativeSQLiteDriver(
-    private val filename: String
-) : SQLiteDriver {
-    override fun open(): SQLiteConnection = memScoped {
+class NativeSQLiteDriver : SQLiteDriver {
+    override fun open(fileName: String): SQLiteConnection = memScoped {
         val dbPointer = allocPointerTo<sqlite3>()
         val resultCode = sqlite3_open_v2(
-            filename = filename,
+            filename = fileName,
             ppDb = dbPointer.ptr,
             flags = SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE,
             zVfs = null
diff --git a/sqlite/sqlite/api/current.txt b/sqlite/sqlite/api/current.txt
index 7c8bfc7..0ab19f2 100644
--- a/sqlite/sqlite/api/current.txt
+++ b/sqlite/sqlite/api/current.txt
@@ -7,7 +7,7 @@
   }
 
   public interface SQLiteDriver {
-    method public androidx.sqlite.SQLiteConnection open();
+    method public androidx.sqlite.SQLiteConnection open(String fileName);
   }
 
   public final class SQLiteKt {
@@ -18,16 +18,23 @@
 
   public interface SQLiteStatement {
     method public void bindBlob(int index, byte[] value);
+    method public default void bindBoolean(int index, boolean value);
     method public void bindDouble(int index, double value);
+    method public default void bindFloat(int index, float value);
+    method public default void bindInt(int index, int value);
     method public void bindLong(int index, long value);
     method public void bindNull(int index);
     method public void bindText(int index, String value);
     method public void clearBindings();
     method public void close();
     method public byte[] getBlob(int index);
+    method public default boolean getBoolean(int index);
     method public int getColumnCount();
     method public String getColumnName(int index);
+    method public default java.util.List<java.lang.String> getColumnNames();
     method public double getDouble(int index);
+    method public default float getFloat(int index);
+    method public default int getInt(int index);
     method public long getLong(int index);
     method public String getText(int index);
     method public boolean isNull(int index);
diff --git a/sqlite/sqlite/api/restricted_current.txt b/sqlite/sqlite/api/restricted_current.txt
index 7c8bfc7..0ab19f2 100644
--- a/sqlite/sqlite/api/restricted_current.txt
+++ b/sqlite/sqlite/api/restricted_current.txt
@@ -7,7 +7,7 @@
   }
 
   public interface SQLiteDriver {
-    method public androidx.sqlite.SQLiteConnection open();
+    method public androidx.sqlite.SQLiteConnection open(String fileName);
   }
 
   public final class SQLiteKt {
@@ -18,16 +18,23 @@
 
   public interface SQLiteStatement {
     method public void bindBlob(int index, byte[] value);
+    method public default void bindBoolean(int index, boolean value);
     method public void bindDouble(int index, double value);
+    method public default void bindFloat(int index, float value);
+    method public default void bindInt(int index, int value);
     method public void bindLong(int index, long value);
     method public void bindNull(int index);
     method public void bindText(int index, String value);
     method public void clearBindings();
     method public void close();
     method public byte[] getBlob(int index);
+    method public default boolean getBoolean(int index);
     method public int getColumnCount();
     method public String getColumnName(int index);
+    method public default java.util.List<java.lang.String> getColumnNames();
     method public double getDouble(int index);
+    method public default float getFloat(int index);
+    method public default int getInt(int index);
     method public long getLong(int index);
     method public String getText(int index);
     method public boolean isNull(int index);
diff --git a/sqlite/sqlite/build.gradle b/sqlite/sqlite/build.gradle
index 9a1b291..ce04df8 100644
--- a/sqlite/sqlite/build.gradle
+++ b/sqlite/sqlite/build.gradle
@@ -34,7 +34,6 @@
 
 androidXMultiplatform {
     android()
-    androidNative()
     ios()
     jvm()
     linux()
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
index d4fdf82..b30c8f8 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteDriver.kt
@@ -23,7 +23,8 @@
     /**
      * Opens a new database connection.
      *
+     * @param fileName Name of the database file.
      * @return the database connection.
      */
-    fun open(): SQLiteConnection
+    fun open(fileName: String): SQLiteConnection
 }
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
index e0f62d6..e0b16bd 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
@@ -43,6 +43,16 @@
     fun bindDouble(index: Int, value: Double)
 
     /**
+     * Binds a Float value to this statement at an index.
+     *
+     * @param index the 1-based index of the parameter to bind
+     * @param value the value to bind
+     */
+    fun bindFloat(index: Int, value: Float) {
+        bindDouble(index, value.toDouble())
+    }
+
+    /**
      * Binds a Long value to this statement at an index.
      *
      * @param index the 1-based index of the parameter to bind
@@ -51,6 +61,26 @@
     fun bindLong(index: Int, value: Long)
 
     /**
+     * Binds a Int value to this statement at an index.
+     *
+     * @param index the 1-based index of the parameter to bind
+     * @param value the value to bind
+     */
+    fun bindInt(index: Int, value: Int) {
+        bindLong(index, value.toLong())
+    }
+
+    /**
+     * Binds a Boolean value to this statement at an index.
+     *
+     * @param index the 1-based index of the parameter to bind
+     * @param value the value to bind
+     */
+    fun bindBoolean(index: Int, value: Boolean) {
+        bindLong(index, if (value) 1L else 0L)
+    }
+
+    /**
      * Binds a String value to this statement at an index.
      *
      * @param index the 1-based index of the parameter to bind
@@ -82,6 +112,16 @@
     fun getDouble(index: Int): Double
 
     /**
+     * Returns the value of the column at [index] as a Float.
+     *
+     * @param index the 0-based index of the column
+     * @return the value of the column
+     */
+    fun getFloat(index: Int): Float {
+        return getDouble(index).toFloat()
+    }
+
+    /**
      * Returns the value of the column at [index] as a Long.
      *
      * @param index the 0-based index of the column
@@ -90,6 +130,26 @@
     fun getLong(index: Int): Long
 
     /**
+     * Returns the value of the column at [index] as a Int.
+     *
+     * @param index the 0-based index of the column
+     * @return the value of the column
+     */
+    fun getInt(index: Int): Int {
+        return getLong(index).toInt()
+    }
+
+    /**
+     * Returns the value of the column at [index] as a Boolean.
+     *
+     * @param index the 0-based index of the column
+     * @return the value of the column
+     */
+    fun getBoolean(index: Int): Boolean {
+        return getLong(index) != 0L
+    }
+
+    /**
      * Returns the value of the column at [index] as a String.
      *
      * @param index the 0-based index of the column
@@ -106,7 +166,7 @@
     fun isNull(index: Int): Boolean
 
     /**
-     * Returns the numbers of columns in the result of the statement.
+     * Returns the number of columns in the result of the statement.
      *
      * @return the number of columns
      */
@@ -121,6 +181,15 @@
     fun getColumnName(index: Int): String
 
     /**
+     * Returns the name of the columns in the result of the statement ordered by their index.
+     *
+     * @return the names of the columns
+     */
+    fun getColumnNames(): List<String> {
+       return List(getColumnCount()) { i -> getColumnName(i) }
+    }
+
+    /**
      * Executes the statement and evaluates the next result row if available.
      *
      * A statement is initially prepared and compiled but is not executed until one or more calls
diff --git a/stableaidl/stableaidl-gradle-plugin/build.gradle b/stableaidl/stableaidl-gradle-plugin/build.gradle
index efce547..13fded7 100644
--- a/stableaidl/stableaidl-gradle-plugin/build.gradle
+++ b/stableaidl/stableaidl-gradle-plugin/build.gradle
@@ -31,11 +31,18 @@
 
 apply from: "../../buildSrc/kotlin-dsl-dependency.gradle"
 
+configurations {
+    // Config for plugin classpath to be used during tests
+    testPlugin {
+        canBeConsumed = false
+        canBeResolved = true
+    }
+}
+
 dependencies {
     implementation(findGradleKotlinDsl())
     implementation(gradleApi())
     implementation(libs.androidGradlePluginApi)
-    implementation(libs.androidGradlePluginz) // Needed for BaseExtension, see b/268237729.
     implementation(libs.androidToolsCommon)
     implementation(libs.androidToolsRepository)
     implementation(libs.androidToolsSdkCommon)
@@ -44,6 +51,9 @@
     implementation(libs.guava)
     implementation(libs.kotlinStdlib)
 
+    testPlugin(libs.androidGradlePluginz)
+
+    testImplementation(libs.androidGradlePluginz)
     testImplementation(gradleTestKit())
     testImplementation(project(":internal-testutils-gradle-plugin"))
     testImplementation(libs.androidToolsAnalyticsProtos)
@@ -53,6 +63,10 @@
     testImplementation(libs.truth)
 }
 
+tasks.withType(PluginUnderTestMetadata.class).named("pluginUnderTestMetadata").configure {
+    it.pluginClasspath.from(configurations.testPlugin)
+}
+
 gradlePlugin {
     plugins {
         stableAidl {
diff --git a/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml b/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml
index 6ce5472..24cc85a 100644
--- a/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml
+++ b/stableaidl/stableaidl-gradle-plugin/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
-
-    <issue
-        id="InternalGradleApiUsage"
-        message="Avoid using internal Android Gradle Plugin APIs"
-        errorLine1="import com.android.build.gradle.internal.LoggerWrapper"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/stableaidl/internal/process/GradleProcessExecutor.kt"/>
-    </issue>
+<issues format="6" by="lint 8.4.0-alpha12" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha12)" variant="all" version="8.4.0-alpha12">
 
     <issue
         id="InternalGradleApiUsage"
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlExtensionImpl.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlExtensionImpl.kt
index fa45f3c..ed441f7 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlExtensionImpl.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlExtensionImpl.kt
@@ -21,7 +21,6 @@
 import androidx.stableaidl.tasks.StableAidlCheckApi
 import androidx.stableaidl.tasks.UpdateStableAidlApiTask
 import com.android.build.api.variant.SourceDirectories
-import com.android.build.gradle.internal.tasks.factory.dependsOn
 import java.io.File
 import org.gradle.api.Task
 import org.gradle.api.tasks.TaskProvider
@@ -67,4 +66,14 @@
 
     internal val importSourceDirs = mutableListOf<SourceDirectories.Flat>()
     internal val allTasks = mutableMapOf<String, Set<TaskProvider<*>>>()
+
+    internal fun <T : Task> TaskProvider<out T>.dependsOn(
+        vararg tasks: TaskProvider<out Task>
+    ): TaskProvider<out T> {
+        if (tasks.isEmpty().not()) {
+            configure { it.dependsOn(*tasks) }
+        }
+
+        return this
+    }
 }
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
index 754d5e4..6ea7e33 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/StableAidlPlugin.kt
@@ -21,7 +21,6 @@
 import com.android.build.api.variant.AndroidComponentsExtension
 import com.android.build.api.variant.DslExtension
 import com.android.build.api.variant.Variant
-import com.android.build.gradle.BaseExtension
 import com.android.utils.usLocaleCapitalize
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
@@ -196,23 +195,9 @@
  */
 internal const val SOURCE_TYPE_STABLE_AIDL_IMPORTS = "stableAidlImports"
 
-internal fun SdkComponents.aidl(baseExtension: BaseExtension): Provider<RegularFile> =
-    sdkDirectory.map {
-        it.dir("build-tools").dir(baseExtension.buildToolsVersion).file(
-            if (java.lang.System.getProperty("os.name").startsWith("Windows")) {
-                "aidl.exe"
-            } else {
-                "aidl"
-            }
-        )
-    }
-
-internal fun SdkComponents.aidlFramework(baseExtension: BaseExtension): Provider<RegularFile> =
-    sdkDirectory.map {
-        it.dir("platforms")
-            .dir(baseExtension.compileSdkVersion!!)
-            .file("framework.aidl")
-    }
+internal fun SdkComponents.aidl(): Provider<RegularFile> =
+    @Suppress("UnstableApiUsage")
+    aidl.flatMap { it.executable }
 
 /**
  * Returns the AIDL import directories for the given variant of the project.
diff --git a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/internal/process/GradleProcessExecutor.kt b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/internal/process/GradleProcessExecutor.kt
index 76e13bd..d789156 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/internal/process/GradleProcessExecutor.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/main/java/androidx/stableaidl/internal/process/GradleProcessExecutor.kt
@@ -15,7 +15,7 @@
  */
 package androidx.stableaidl.internal.process
 
-import com.android.build.gradle.internal.LoggerWrapper
+import androidx.stableaidl.internal.LoggerWrapper
 import com.android.ide.common.process.ProcessException
 import com.android.ide.common.process.ProcessExecutor
 import com.android.ide.common.process.ProcessInfo
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
index bd2948f..8ab1650 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -41,6 +42,7 @@
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.EventCondition;
+import androidx.test.uiautomator.StaleObjectException;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
@@ -389,8 +391,7 @@
     public void testHashCode() {
         launchTestActivity(MainActivity.class);
 
-        // Get the same textView object via different methods.
-        // The same object should have the same hash code.
+        // Same object (found w/ different selectors) should have the same hash code.
         UiObject2 textView1 = mDevice.findObject(By.res(TEST_APP, "example_id"));
         UiObject2 textView2 = mDevice.findObject(By.text("TextView with an id"));
         assertEquals(textView1.hashCode(), textView2.hashCode());
@@ -398,6 +399,15 @@
         // Different objects should have different hash codes.
         UiObject2 linearLayout = mDevice.findObject(By.res(TEST_APP, "nested_elements"));
         assertNotEquals(textView1.hashCode(), linearLayout.hashCode());
+
+        // Use cached hash code for stale objects to avoid unnecessary SOEs.
+        int hashCode = textView1.hashCode();
+        mDevice.pressHome();
+        try {
+            assertEquals(hashCode, textView1.hashCode());
+        } catch (StaleObjectException e) {
+            fail("Unexpected StaleObjectException while calculating hash code");
+        }
     }
 
     @Test
diff --git a/test/uiautomator/uiautomator/api/current.txt b/test/uiautomator/uiautomator/api/current.txt
index e6772e0..89cf12f6 100644
--- a/test/uiautomator/uiautomator/api/current.txt
+++ b/test/uiautomator/uiautomator/api/current.txt
@@ -132,7 +132,7 @@
     method public void sendStatus(int, android.os.Bundle);
   }
 
-  public abstract class SearchCondition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.Searchable,U> {
+  public abstract class SearchCondition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.Searchable!,U!> {
     ctor public SearchCondition();
   }
 
@@ -356,7 +356,7 @@
     method public <U> U! wait(androidx.test.uiautomator.UiObject2Condition<U!>, long);
   }
 
-  public abstract class UiObject2Condition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.UiObject2,U> {
+  public abstract class UiObject2Condition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.UiObject2!,U!> {
     ctor public UiObject2Condition();
   }
 
diff --git a/test/uiautomator/uiautomator/api/restricted_current.txt b/test/uiautomator/uiautomator/api/restricted_current.txt
index e6772e0..89cf12f6 100644
--- a/test/uiautomator/uiautomator/api/restricted_current.txt
+++ b/test/uiautomator/uiautomator/api/restricted_current.txt
@@ -132,7 +132,7 @@
     method public void sendStatus(int, android.os.Bundle);
   }
 
-  public abstract class SearchCondition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.Searchable,U> {
+  public abstract class SearchCondition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.Searchable!,U!> {
     ctor public SearchCondition();
   }
 
@@ -356,7 +356,7 @@
     method public <U> U! wait(androidx.test.uiautomator.UiObject2Condition<U!>, long);
   }
 
-  public abstract class UiObject2Condition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.UiObject2,U> {
+  public abstract class UiObject2Condition<U> implements androidx.test.uiautomator.Condition<androidx.test.uiautomator.UiObject2!,U!> {
     ctor public UiObject2Condition();
   }
 
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 74c2c2c..1fa068b 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -136,7 +136,11 @@
 
     @Override
     public int hashCode() {
-        return getAccessibilityNodeInfo().hashCode();
+        try {
+            return getAccessibilityNodeInfo().hashCode();
+        } catch (StaleObjectException e) {
+            return mCachedNode.hashCode();
+        }
     }
 
     /** Recycle this object. */
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index ef661f7..2721d70 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -23,6 +23,8 @@
  */
 import androidx.build.LibraryType
 
+import static org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.*
+
 plugins {
     id("AndroidXPlugin")
 }
@@ -45,7 +47,9 @@
                 api(libs.okio)
             }
         }
-
+        nativeMain {
+            dependsOn(commonMain)
+        }
         jvmMain {
             dependencies {
                 api(libs.kotlinStdlib)
@@ -64,6 +68,13 @@
                 implementation(libs.kotlinTest)
             }
         }
+        targets.all { target ->
+            if (target.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.native) {
+                target.compilations["main"].defaultSourceSet {
+                    dependsOn(nativeMain)
+                }
+            }
+        }
     }
 }
 
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/FileSystem.kt
similarity index 82%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/FileSystem.kt
index 3e91597..d8fb2c1 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/testutils/testutils-datastore/src/commonMain/kotlin/androidx/datastore/FileSystem.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.datastore
+
+import okio.FileSystem
+
+internal expect fun filesystem(): FileSystem
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileSystem.jvm.kt
similarity index 82%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileSystem.jvm.kt
index 3e91597..9b38043 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/testutils/testutils-datastore/src/jvmMain/kotlin/androidx/datastore/FileSystem.jvm.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.datastore
+
+import okio.FileSystem
+
+internal actual fun filesystem(): FileSystem = FileSystem.SYSTEM
diff --git a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt b/testutils/testutils-datastore/src/nativeMain/kotlin/androidx/datastore/FileSystem.native.kt
similarity index 82%
copy from room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
copy to testutils/testutils-datastore/src/nativeMain/kotlin/androidx/datastore/FileSystem.native.kt
index 3e91597..9b38043 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/Placeholder.kt
+++ b/testutils/testutils-datastore/src/nativeMain/kotlin/androidx/datastore/FileSystem.native.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.room.testing
-// empty file to trigger klib creation
-// see: https://youtrack.jetbrains.com/issue/KT-52344
+package androidx.datastore
+
+import okio.FileSystem
+
+internal actual fun filesystem(): FileSystem = FileSystem.SYSTEM
diff --git a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
index 33bee60..c76e452 100644
--- a/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
+++ b/testutils/testutils-macrobenchmark/src/main/java/androidx/testutils/MacrobenchUtils.kt
@@ -72,7 +72,7 @@
         StartupTimingMetric(),
         TraceSectionMetric(
             "StartupTracingInitializer",
-            mode = TraceSectionMetric.Mode.First
+            TraceSectionMetric.Mode.First
         ),
         MemoryUsageMetric(MemoryUsageMetric.Mode.Last)
     )
diff --git a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
index a9bcbad..5af6cb4 100644
--- a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
+++ b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
@@ -18,11 +18,14 @@
 
 package androidx.testutils
 
+import android.annotation.SuppressLint
 import androidx.annotation.IdRes
+import androidx.navigation.ExperimentalSafeArgsApi
 import androidx.navigation.NavDestinationBuilder
 import androidx.navigation.NavDestinationDsl
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.get
+import kotlin.reflect.KClass
 
 /**
  * Construct a new [TestNavigator.Destination]
@@ -37,6 +40,11 @@
 /**
  * Construct a new [TestNavigator.Destination]
  */
+inline fun NavGraphBuilder.test(route: KClass<*>) = test(route) {}
+
+/**
+ * Construct a new [TestNavigator.Destination]
+ */
 @Suppress("DEPRECATION")
 inline fun NavGraphBuilder.test(
     @IdRes id: Int,
@@ -59,6 +67,16 @@
 )
 
 /**
+ * Construct a new [TestNavigator.Destination]
+ */
+inline fun NavGraphBuilder.test(
+    route: KClass<*>,
+    builder: TestNavigatorDestinationBuilder.() -> Unit
+) = destination(
+    TestNavigatorDestinationBuilder(provider[TestNavigator::class], route).apply(builder)
+)
+
+/**
  * DSL for constructing a new [TestNavigator.Destination]
  */
 @NavDestinationDsl
@@ -66,4 +84,7 @@
     @Suppress("DEPRECATION")
     constructor(navigator: TestNavigator, @IdRes id: Int = 0) : super(navigator, id)
     constructor(navigator: TestNavigator, route: String) : super(navigator, route)
+    @OptIn(ExperimentalSafeArgsApi::class)
+    @SuppressLint("NullAnnotationGroup")
+    constructor(navigator: TestNavigator, route: KClass<*>) : super(navigator, route, null)
 }
diff --git a/tv/integration-tests/playground/build.gradle b/tv/integration-tests/playground/build.gradle
index cd284a3..4669d1f 100644
--- a/tv/integration-tests/playground/build.gradle
+++ b/tv/integration-tests/playground/build.gradle
@@ -35,19 +35,10 @@
 
     implementation("androidx.appcompat:appcompat:1.6.1")
 
-    implementation(project(":activity:activity-compose"))
     implementation(project(":compose:material3:material3"))
     implementation(project(":navigation:navigation-compose"))
-    implementation(project(":profileinstaller:profileinstaller"))
     implementation(project(":tv:tv-foundation"))
     implementation(project(":tv:tv-material"))
-
-    // pull latest compose (if build fails in future, just comment the following compose deps until
-    // we fix this package)
-    implementation(project(":compose:animation:animation"))
-    implementation(project(":compose:runtime:runtime"))
-    implementation(project(":compose:ui:ui"))
-    implementation(project(":compose:foundation:foundation-layout"))
 }
 
 androidx {
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
index a7c3da3..9735d55 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
@@ -23,7 +23,6 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.material.icons.Icons
@@ -59,7 +58,7 @@
 
     CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
         Row(Modifier.fillMaxSize()) {
-            Box(modifier = Modifier.height(400.dp)) {
+            Box(modifier = Modifier.weight(1f)) {
                 NavigationDrawer(drawerContent = { Sidebar(direction = direction) }) {
                     CommonBackground()
                 }
@@ -75,7 +74,7 @@
 
     CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
         Row(Modifier.fillMaxSize()) {
-            Box(modifier = Modifier.height(400.dp)) {
+            Box(modifier = Modifier.weight(1f)) {
                 androidx.tv.material3.ModalNavigationDrawer(
                     drawerContent = { Sidebar(direction = direction) },
                     scrimBrush = Brush.verticalGradient(
diff --git a/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt
index fdbb7ae..9b737b2 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt
@@ -16,72 +16,84 @@
 
 package androidx.tv.samples
 
-import android.util.Log
 import androidx.annotation.Sampled
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
 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.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import androidx.tv.material3.Border
+import androidx.tv.material3.ClickableSurfaceDefaults
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.ImmersiveList
+import androidx.tv.material3.Surface
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Sampled
 @Composable
-private fun SampleImmersiveList() {
-    val immersiveListHeight = 300.dp
-    val cardSpacing = 10.dp
-    val cardWidth = 200.dp
-    val cardHeight = 150.dp
-    val backgrounds = listOf(
-        Color.Red,
-        Color.Blue,
-        Color.Magenta,
-    )
+fun SampleImmersiveList() {
+    val items = remember { listOf(Color.Red, Color.Green, Color.Yellow) }
+    val selectedItem = remember { mutableStateOf<Color?>(null) }
 
-    ImmersiveList(
+    // Container
+    Box(
         modifier = Modifier
-            .height(immersiveListHeight + cardHeight / 2)
-            .fillMaxWidth(),
-        background = { index, _ ->
+            .fillMaxWidth()
+            .height(400.dp)
+    ) {
+        val bgColor = selectedItem.value
+
+        // Background
+        if (bgColor != null) {
             Box(
                 modifier = Modifier
-                    .background(backgrounds[index].copy(alpha = 0.3f))
-                    .height(immersiveListHeight)
                     .fillMaxWidth()
-            )
+                    .aspectRatio(20f / 7)
+                    .background(bgColor)
+            ) {}
         }
-    ) {
-        Row(horizontalArrangement = Arrangement.spacedBy(cardSpacing)) {
-            backgrounds.forEachIndexed { index, backgroundColor ->
-                var isFocused by remember { mutableStateOf(false) }
 
-                Box(
+        // Rows
+        LazyRow(
+            modifier = Modifier.align(Alignment.BottomEnd),
+            horizontalArrangement = Arrangement.spacedBy(20.dp),
+            contentPadding = PaddingValues(20.dp),
+        ) {
+            items(items) { color ->
+                Surface(
+                    onClick = { },
                     modifier = Modifier
-                        .background(backgroundColor)
-                        .width(cardWidth)
-                        .height(cardHeight)
-                        .border(5.dp, Color.White.copy(alpha = if (isFocused) 1f else 0.3f))
-                        .onFocusChanged { isFocused = it.isFocused }
-                        .immersiveListItem(index)
-                        .clickable {
-                            Log.d("ImmersiveList", "Item $index was clicked")
-                        }
-                )
+                        .width(200.dp)
+                        .aspectRatio(16f / 9)
+                        .onFocusChanged {
+                            if (it.hasFocus) {
+                                selectedItem.value = color
+                            }
+                        },
+                    colors = ClickableSurfaceDefaults.colors(
+                        containerColor = color,
+                        focusedContainerColor = color,
+                    ),
+                    border = ClickableSurfaceDefaults.border(
+                        focusedBorder = Border(
+                            border = BorderStroke(2.dp, Color.White),
+                            inset = 4.dp,
+                        )
+                    )
+                ) {}
             }
         }
     }
diff --git a/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt
index d09d324..b697996 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt
@@ -19,10 +19,8 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -57,7 +55,6 @@
 
   TabRow(
     selectedTabIndex = selectedTabIndex,
-    separator = { Spacer(modifier = Modifier.width(12.dp)) },
     modifier = Modifier.focusRestorer()
   ) {
     tabs.forEachIndexed { index, tab ->
@@ -89,7 +86,6 @@
 
   TabRow(
     selectedTabIndex = selectedTabIndex,
-    separator = { Spacer(modifier = Modifier.width(12.dp)) },
     indicator = { tabPositions, doesTabRowHaveFocus ->
       TabRowDefaults.UnderlinedIndicator(
         currentTabPosition = tabPositions[selectedTabIndex],
@@ -108,7 +104,7 @@
           Text(
             text = tab,
             fontSize = 12.sp,
-            modifier = Modifier.padding(bottom = 4.dp)
+            modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
           )
         }
       }
@@ -137,7 +133,6 @@
 
   TabRow(
     selectedTabIndex = selectedTabIndex,
-    separator = { Spacer(modifier = Modifier.width(12.dp)) },
     modifier = Modifier.focusRestorer()
   ) {
     tabs.forEachIndexed { index, tab ->
diff --git a/tv/tv-foundation/build.gradle b/tv/tv-foundation/build.gradle
index 69b4909..4ade877 100644
--- a/tv/tv-foundation/build.gradle
+++ b/tv/tv-foundation/build.gradle
@@ -39,7 +39,7 @@
 
     implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
-    def composeVersion = "1.6.3"
+    def composeVersion = "1.6.4"
     api("androidx.annotation:annotation:$composeVersion")
     api("androidx.compose.animation:animation:$composeVersion")
     api("androidx.compose.foundation:foundation:$composeVersion")
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index e29475f..a20cb7d 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -33,13 +33,13 @@
     property public final androidx.tv.material3.Border None;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonBorder {
+  @androidx.compose.runtime.Immutable public final class ButtonBorder {
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonColors {
+  @androidx.compose.runtime.Immutable public final class ButtonColors {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonDefaults {
+  public final class ButtonDefaults {
     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);
     method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
@@ -56,15 +56,15 @@
     field public static final androidx.tv.material3.ButtonDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonGlow {
+  @androidx.compose.runtime.Immutable public final class ButtonGlow {
   }
 
   public final class ButtonKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void OutlinedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void OutlinedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonScale {
+  @androidx.compose.runtime.Immutable public final class ButtonScale {
     field public static final androidx.tv.material3.ButtonScale.Companion Companion;
   }
 
@@ -73,7 +73,7 @@
     property public final androidx.tv.material3.ButtonScale None;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonShape {
+  @androidx.compose.runtime.Immutable public final class ButtonShape {
   }
 
   @androidx.compose.runtime.Immutable public final class CardBorder {
@@ -384,7 +384,7 @@
     property public final androidx.tv.material3.Glow None;
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class IconButtonDefaults {
+  public final class IconButtonDefaults {
     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);
     method public float getLargeButtonSize();
@@ -406,8 +406,8 @@
   }
 
   public final class IconButtonKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, 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.compose.runtime.NonRestartableComposable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, 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.compose.runtime.NonRestartableComposable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
   public final class IconKt {
@@ -786,7 +786,7 @@
     field public static final androidx.tv.material3.NonInteractiveSurfaceDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedButtonDefaults {
+  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);
     method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
@@ -803,7 +803,7 @@
     field public static final androidx.tv.material3.OutlinedButtonDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedIconButtonDefaults {
+  public final class OutlinedIconButtonDefaults {
     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);
     method public float getLargeButtonSize();
@@ -1005,8 +1005,8 @@
 
   public final class TextKt {
     method @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional int textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional int textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, 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.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> getLocalTextStyle();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
@@ -1076,10 +1076,10 @@
     property public final androidx.compose.ui.text.TextStyle titleSmall;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class WideButtonContentColor {
+  @androidx.compose.runtime.Immutable public final class WideButtonContentColor {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class WideButtonDefaults {
+  public final class WideButtonDefaults {
     method @androidx.compose.runtime.Composable public void Background(boolean enabled, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
     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.WideButtonContentColor contentColor(optional long color, optional long focusedColor, optional long pressedColor, optional long disabledColor);
@@ -1090,8 +1090,8 @@
   }
 
   public final class WideButtonKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? subtitle, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? subtitle, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
   }
 
 }
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index e29475f..a20cb7d 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -33,13 +33,13 @@
     property public final androidx.tv.material3.Border None;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonBorder {
+  @androidx.compose.runtime.Immutable public final class ButtonBorder {
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonColors {
+  @androidx.compose.runtime.Immutable public final class ButtonColors {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonDefaults {
+  public final class ButtonDefaults {
     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);
     method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
@@ -56,15 +56,15 @@
     field public static final androidx.tv.material3.ButtonDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonGlow {
+  @androidx.compose.runtime.Immutable public final class ButtonGlow {
   }
 
   public final class ButtonKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void OutlinedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Button(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void OutlinedButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonScale {
+  @androidx.compose.runtime.Immutable public final class ButtonScale {
     field public static final androidx.tv.material3.ButtonScale.Companion Companion;
   }
 
@@ -73,7 +73,7 @@
     property public final androidx.tv.material3.ButtonScale None;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonShape {
+  @androidx.compose.runtime.Immutable public final class ButtonShape {
   }
 
   @androidx.compose.runtime.Immutable public final class CardBorder {
@@ -384,7 +384,7 @@
     property public final androidx.tv.material3.Glow None;
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class IconButtonDefaults {
+  public final class IconButtonDefaults {
     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);
     method public float getLargeButtonSize();
@@ -406,8 +406,8 @@
   }
 
   public final class IconButtonKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, 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.compose.runtime.NonRestartableComposable public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, 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.compose.runtime.NonRestartableComposable public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
   public final class IconKt {
@@ -786,7 +786,7 @@
     field public static final androidx.tv.material3.NonInteractiveSurfaceDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedButtonDefaults {
+  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);
     method public androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
@@ -803,7 +803,7 @@
     field public static final androidx.tv.material3.OutlinedButtonDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedIconButtonDefaults {
+  public final class OutlinedIconButtonDefaults {
     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);
     method public float getLargeButtonSize();
@@ -1005,8 +1005,8 @@
 
   public final class TextKt {
     method @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional int textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional int textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, 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.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional int minLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> getLocalTextStyle();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
@@ -1076,10 +1076,10 @@
     property public final androidx.compose.ui.text.TextStyle titleSmall;
   }
 
-  @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class WideButtonContentColor {
+  @androidx.compose.runtime.Immutable public final class WideButtonContentColor {
   }
 
-  @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public final class WideButtonDefaults {
+  public final class WideButtonDefaults {
     method @androidx.compose.runtime.Composable public void Background(boolean enabled, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
     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.WideButtonContentColor contentColor(optional long color, optional long focusedColor, optional long pressedColor, optional long disabledColor);
@@ -1090,8 +1090,8 @@
   }
 
   public final class WideButtonKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? subtitle, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? subtitle, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
   }
 
 }
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index 472b4f5..90db34f 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -33,7 +33,7 @@
 dependencies {
     api(libs.kotlinStdlib)
 
-    def composeVersion = "1.6.3"
+    def composeVersion = "1.6.4"
     api("androidx.annotation:annotation:$composeVersion")
     api("androidx.compose.animation:animation:$composeVersion")
     api("androidx.compose.foundation:foundation:$composeVersion")
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt
index 68299e8..88440a9 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt
@@ -148,8 +148,8 @@
 
     @Test
     fun painter_noIntrinsicSize_dimensions() {
-        val width = 24.dp
-        val height = 24.dp
+        val width = 20.dp
+        val height = 20.dp
         val painter = ColorPainter(Color.Red)
         val testTag = "testTag"
 
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
index 089607a..1c1044f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
@@ -72,7 +72,6 @@
  * still happen internally.
  * @param content the content of the button
  */
-@ExperimentalTvMaterial3Api
 @NonRestartableComposable
 @Composable
 fun Button(
@@ -146,7 +145,6 @@
  * still happen internally.
  * @param content the content of the button
  */
-@ExperimentalTvMaterial3Api
 @NonRestartableComposable
 @Composable
 fun OutlinedButton(
@@ -181,7 +179,6 @@
     )
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun ButtonImpl(
     onClick: () -> Unit,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonDefaults.kt
index e147851..7c0ee76 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonDefaults.kt
@@ -32,7 +32,6 @@
     val MinHeight = 40.dp
 }
 
-@ExperimentalTvMaterial3Api
 object ButtonDefaults {
     private val ContainerShape = CircleShape
     private val ButtonHorizontalPadding = 16.dp
@@ -54,7 +53,7 @@
     )
 
     /** The default size of the icon when used inside any button. */
-    val IconSize = 18.dp
+    val IconSize = 20.dp
 
     /**
      * The default size of the spacing between an icon and a text when they used inside any button.
@@ -196,7 +195,6 @@
     )
 }
 
-@ExperimentalTvMaterial3Api
 object OutlinedButtonDefaults {
     private val ContainerShape = CircleShape
     private val ButtonHorizontalPadding = 16.dp
@@ -211,7 +209,7 @@
     )
 
     /** The default size of the icon when used inside any button. */
-    val IconSize = 18.dp
+    val IconSize = 20.dp
 
     /**
      * The default size of the spacing between an icon and a text when they used inside any button.
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
index 554dea1..129a288 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
@@ -25,7 +25,6 @@
 /**
  * Defines [Shape] for all TV [Interaction] states of Button.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class ButtonShape internal constructor(
     internal val shape: Shape,
@@ -68,7 +67,6 @@
 /**
  * Defines [Color]s for all TV [Interaction] states of Button.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class ButtonColors internal constructor(
     internal val containerColor: Color,
@@ -124,7 +122,6 @@
 /**
  * Defines [Color]s for all TV [Interaction] states of a WideButton
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class WideButtonContentColor internal constructor(
     internal val contentColor: Color,
@@ -165,7 +162,6 @@
 /**
  * Defines the scale for all TV [Interaction] states of Button.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class ButtonScale internal constructor(
     @FloatRange(from = 0.0) internal val scale: Float,
@@ -221,7 +217,6 @@
 /**
  * Defines [Border] for all TV [Interaction] states of Button.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class ButtonBorder internal constructor(
     internal val border: Border,
@@ -265,7 +260,6 @@
 /**
  * Defines [Glow] for all TV [Interaction] states of Button.
  */
-@ExperimentalTvMaterial3Api
 @Immutable
 class ButtonGlow internal constructor(
     internal val glow: Glow,
@@ -300,7 +294,6 @@
 
 private val WideButtonContainerColor = Color.Transparent
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun ButtonShape.toClickableSurfaceShape(): ClickableSurfaceShape = ClickableSurfaceShape(
     shape = shape,
     focusedShape = focusedShape,
@@ -309,7 +302,6 @@
     focusedDisabledShape = focusedDisabledShape
 )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun ButtonColors.toClickableSurfaceColors(): ClickableSurfaceColors =
     ClickableSurfaceColors(
         containerColor = containerColor,
@@ -322,7 +314,6 @@
         disabledContentColor = disabledContentColor
     )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun WideButtonContentColor.toClickableSurfaceColors(): ClickableSurfaceColors =
     ClickableSurfaceColors(
         containerColor = WideButtonContainerColor,
@@ -335,7 +326,6 @@
         disabledContentColor = disabledContentColor
     )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun ButtonScale.toClickableSurfaceScale() = ClickableSurfaceScale(
     scale = scale,
     focusedScale = focusedScale,
@@ -344,7 +334,6 @@
     focusedDisabledScale = focusedDisabledScale
 )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun ButtonBorder.toClickableSurfaceBorder() = ClickableSurfaceBorder(
     border = border,
     focusedBorder = focusedBorder,
@@ -353,7 +342,6 @@
     focusedDisabledBorder = focusedDisabledBorder
 )
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun ButtonGlow.toClickableSurfaceGlow() = ClickableSurfaceGlow(
     glow = glow,
     focusedGlow = focusedGlow,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt
index 8d8ce6c..4f4f2a5 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt
@@ -36,7 +36,6 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.dp
 
 /**
  * A Material Design icon component that draws [imageVector] using [tint], with a default value
@@ -169,5 +168,4 @@
 private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
 
 // Default icon size, for icons with no intrinsic size information
-// TODO(rvighnesh): change this to IconButtonTokens.IconSize when we introduce IconButton
-private val DefaultIconSizeModifier = Modifier.size(24.dp)
+private val DefaultIconSizeModifier = Modifier.size(IconButtonDefaults.MediumIconSize)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt b/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt
index 790d79b6..8108044 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt
@@ -60,7 +60,6 @@
  * still happen internally.
  * @param content the content of the button, typically an [Icon]
  */
-@ExperimentalTvMaterial3Api
 @NonRestartableComposable
 @Composable
 fun IconButton(
@@ -130,7 +129,6 @@
  * still happen internally.
  * @param content the content of the button, typically an [Icon]
  */
-@ExperimentalTvMaterial3Api
 @NonRestartableComposable
 @Composable
 fun OutlinedIconButton(
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt
index d2a3f17..92efdc2 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.dp
 
-@ExperimentalTvMaterial3Api
 object IconButtonDefaults {
     private val ContainerShape = CircleShape
 
@@ -184,7 +183,6 @@
     )
 }
 
-@ExperimentalTvMaterial3Api
 object OutlinedIconButtonDefaults {
     private val ContainerShape = CircleShape
 
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 1fedcd9..a2edc00 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
@@ -22,6 +22,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.dp
 
@@ -32,7 +33,7 @@
     /**
      * Represents the default shape used by a non-interactive [Surface]
      */
-    val shape: Shape @ReadOnlyComposable @Composable get() = MaterialTheme.shapes.medium
+    val shape: Shape @ReadOnlyComposable @Composable get() = RectangleShape
 
     /**
      * Creates a [NonInteractiveSurfaceColors] that represents the default container & content
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt b/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
index ed819d3..2b19522 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.selection.selectableGroup
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -41,6 +40,7 @@
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.unit.Constraints
@@ -107,17 +107,20 @@
     val scrollState = rememberScrollState()
     var doesTabRowHaveFocus by remember { mutableStateOf(false) }
 
-    CompositionLocalProvider(LocalContentColor provides contentColor) {
-
-        SubcomposeLayout(
-            modifier =
-            modifier
-                .background(containerColor)
-                .clipToBounds()
-                .horizontalScroll(scrollState)
-                .onFocusChanged { doesTabRowHaveFocus = it.hasFocus }
-                .selectableGroup()
-        ) { constraints ->
+    Surface(
+        modifier =
+        modifier
+            .clipToBounds()
+            .horizontalScroll(scrollState)
+            .onFocusChanged { doesTabRowHaveFocus = it.hasFocus }
+            .selectableGroup(),
+        colors = NonInteractiveSurfaceDefaults.colors(
+            containerColor = containerColor,
+            contentColor = contentColor
+        ),
+        shape = RectangleShape,
+    ) {
+        SubcomposeLayout { constraints ->
             // Tab measurables
             val tabMeasurables = subcompose(TabRowSlots.Tabs) {
                 TabRowScopeImpl(doesTabRowHaveFocus).apply {
@@ -147,9 +150,7 @@
 
             val layoutWidth = tabPlaceables.fastSumBy { it.width } +
                 separatorsCount * separatorWidth
-            val layoutHeight = (tabMeasurables.fastMaxOfOrNull {
-                it.maxIntrinsicHeight(Constraints.Infinity)
-            } ?: 0).coerceAtLeast(0)
+            val layoutHeight = tabPlaceables.fastMaxOfOrNull { it.height } ?: 0
 
             // Position the children
             layout(layoutWidth, layoutHeight) {
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
index b773532..6ef08dc 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
@@ -80,13 +80,14 @@
  * @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]. If it is not null, then it must be greater than zero.
+ * @param minLines The minimum height in terms of minimum number of visible lines. It is required
+ * that 1 <= [minLines] <= [maxLines].
  * @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 style style configuration for the text such as color, font, line height etc.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun Text(
     text: String,
@@ -98,11 +99,12 @@
     fontFamily: FontFamily? = null,
     letterSpacing: TextUnit = TextUnit.Unspecified,
     textDecoration: TextDecoration? = null,
-    textAlign: TextAlign = TextAlign.Unspecified,
+    textAlign: TextAlign? = null,
     lineHeight: TextUnit = TextUnit.Unspecified,
     overflow: TextOverflow = TextOverflow.Clip,
     softWrap: Boolean = true,
     maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = 1,
     onTextLayout: (TextLayoutResult) -> Unit = {},
     style: TextStyle = LocalTextStyle.current
 ) {
@@ -120,7 +122,7 @@
                 color = textColor,
                 fontSize = fontSize,
                 fontWeight = fontWeight,
-                textAlign = textAlign,
+                textAlign = textAlign ?: TextAlign.Unspecified,
                 lineHeight = lineHeight,
                 fontFamily = fontFamily,
                 textDecoration = textDecoration,
@@ -130,7 +132,8 @@
         onTextLayout,
         overflow,
         softWrap,
-        maxLines
+        maxLines,
+        minLines
     )
 }
 
@@ -176,6 +179,8 @@
  * @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]. If it is not null, then it must be greater than zero.
+ * @param minLines The minimum height in terms of minimum number of visible lines. It is required
+ * that 1 <= [minLines] <= [maxLines].
  * @param inlineContent a map storing composables that replaces certain ranges of the text, used to
  * insert composables into text layout. See [InlineTextContent].
  * @param onTextLayout callback that is executed when a new text layout is calculated. A
@@ -184,7 +189,6 @@
  * functionality to the text. For example, to draw selection around the text.
  * @param style style configuration for the text such as color, font, line height etc.
  */
-@ExperimentalTvMaterial3Api
 @Composable
 fun Text(
     text: AnnotatedString,
@@ -196,11 +200,12 @@
     fontFamily: FontFamily? = null,
     letterSpacing: TextUnit = TextUnit.Unspecified,
     textDecoration: TextDecoration? = null,
-    textAlign: TextAlign = TextAlign.Unspecified,
+    textAlign: TextAlign? = null,
     lineHeight: TextUnit = TextUnit.Unspecified,
     overflow: TextOverflow = TextOverflow.Clip,
     softWrap: Boolean = true,
     maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = 1,
     inlineContent: Map<String, InlineTextContent> = mapOf(),
     onTextLayout: (TextLayoutResult) -> Unit = {},
     style: TextStyle = LocalTextStyle.current
@@ -218,7 +223,7 @@
                 color = textColor,
                 fontSize = fontSize,
                 fontWeight = fontWeight,
-                textAlign = textAlign,
+                textAlign = textAlign ?: TextAlign.Unspecified,
                 lineHeight = lineHeight,
                 fontFamily = fontFamily,
                 textDecoration = textDecoration,
@@ -229,6 +234,7 @@
         overflow = overflow,
         softWrap = softWrap,
         maxLines = maxLines,
+        minLines = minLines,
         inlineContent = inlineContent
     )
 }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt b/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt
index e59da65..36ec04c 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt
@@ -71,7 +71,6 @@
  * content
  * @param content the content of the button
  */
-@ExperimentalTvMaterial3Api
 @NonRestartableComposable
 @Composable
 fun WideButton(
@@ -144,7 +143,6 @@
  * @param contentPadding the spacing values to apply internally between the container and the
  * content
  */
-@ExperimentalTvMaterial3Api
 @NonRestartableComposable
 @Composable
 fun WideButton(
@@ -222,7 +220,6 @@
     }
 }
 
-@ExperimentalTvMaterial3Api
 @Composable
 private fun WideButtonImpl(
     onClick: () -> Unit,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt
index 07402ff..f4ca41c 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt
@@ -43,7 +43,6 @@
     val VerticalContentGap = 4.dp
 }
 
-@ExperimentalTvMaterial3Api
 object WideButtonDefaults {
     private val HorizontalPadding = 16.dp
     private val VerticalPadding = 10.dp
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt
index 7091c76..49873f8 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt
@@ -34,7 +34,7 @@
     val BodySmallFont = TypefaceTokens.Plain
     val BodySmallLineHeight = 16.0.sp
     val BodySmallSize = 12.sp
-    val BodySmallTracking = 0.4.sp
+    val BodySmallTracking = 0.2.sp
     val BodySmallWeight = TypefaceTokens.WeightRegular
     val DisplayLargeFont = TypefaceTokens.Brand
     val DisplayLargeLineHeight = 64.0.sp
diff --git a/tvprovider/tvprovider/api/current.txt b/tvprovider/tvprovider/api/current.txt
index 1ca7bb2..90bedf9 100644
--- a/tvprovider/tvprovider/api/current.txt
+++ b/tvprovider/tvprovider/api/current.txt
@@ -260,7 +260,7 @@
     method public androidx.tvprovider.media.tv.PreviewProgram.Builder! setWeight(int);
   }
 
-  public final class Program implements java.lang.Comparable<androidx.tvprovider.media.tv.Program> {
+  public final class Program implements java.lang.Comparable<androidx.tvprovider.media.tv.Program!> {
     method public int compareTo(androidx.tvprovider.media.tv.Program);
     method public static androidx.tvprovider.media.tv.Program! fromCursor(android.database.Cursor!);
     method public String![]! getAudioLanguages();
diff --git a/tvprovider/tvprovider/api/restricted_current.txt b/tvprovider/tvprovider/api/restricted_current.txt
index a52b03f..797e575 100644
--- a/tvprovider/tvprovider/api/restricted_current.txt
+++ b/tvprovider/tvprovider/api/restricted_current.txt
@@ -289,7 +289,7 @@
     method public androidx.tvprovider.media.tv.PreviewProgram.Builder! setWeight(int);
   }
 
-  public final class Program implements java.lang.Comparable<androidx.tvprovider.media.tv.Program> {
+  public final class Program implements java.lang.Comparable<androidx.tvprovider.media.tv.Program!> {
     method public int compareTo(androidx.tvprovider.media.tv.Program);
     method public static androidx.tvprovider.media.tv.Program! fromCursor(android.database.Cursor!);
     method public String![]! getAudioLanguages();
diff --git a/vectordrawable/vectordrawable-animated/api/restricted_current.txt b/vectordrawable/vectordrawable-animated/api/restricted_current.txt
index 502a7a7..7368567 100644
--- a/vectordrawable/vectordrawable-animated/api/restricted_current.txt
+++ b/vectordrawable/vectordrawable-animated/api/restricted_current.txt
@@ -42,7 +42,7 @@
     method public static android.animation.Animator! loadAnimator(android.content.Context!, @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ArgbEvaluator implements android.animation.TypeEvaluator<java.lang.Object> {
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ArgbEvaluator implements android.animation.TypeEvaluator<java.lang.Object!> {
     ctor public ArgbEvaluator();
     method public Object! evaluate(float, Object!, Object!);
     method public static androidx.vectordrawable.graphics.drawable.ArgbEvaluator getInstance();
diff --git a/viewpager2/viewpager2/api/1.1.0-beta03.txt b/viewpager2/viewpager2/api/1.1.0-beta03.txt
index c3dd7a2..36c6eb5 100644
--- a/viewpager2/viewpager2/api/1.1.0-beta03.txt
+++ b/viewpager2/viewpager2/api/1.1.0-beta03.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.viewpager2.adapter {
 
-  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder!> implements androidx.viewpager2.adapter.StatefulAdapter {
     ctor public FragmentStateAdapter(androidx.fragment.app.Fragment);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentActivity);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager, androidx.lifecycle.Lifecycle);
diff --git a/viewpager2/viewpager2/api/current.txt b/viewpager2/viewpager2/api/current.txt
index c3dd7a2..36c6eb5 100644
--- a/viewpager2/viewpager2/api/current.txt
+++ b/viewpager2/viewpager2/api/current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.viewpager2.adapter {
 
-  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder!> implements androidx.viewpager2.adapter.StatefulAdapter {
     ctor public FragmentStateAdapter(androidx.fragment.app.Fragment);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentActivity);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager, androidx.lifecycle.Lifecycle);
diff --git a/viewpager2/viewpager2/api/restricted_1.1.0-beta03.txt b/viewpager2/viewpager2/api/restricted_1.1.0-beta03.txt
index 38e9cd9..a98edba 100644
--- a/viewpager2/viewpager2/api/restricted_1.1.0-beta03.txt
+++ b/viewpager2/viewpager2/api/restricted_1.1.0-beta03.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.viewpager2.adapter {
 
-  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder!> implements androidx.viewpager2.adapter.StatefulAdapter {
     ctor public FragmentStateAdapter(androidx.fragment.app.Fragment);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentActivity);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager, androidx.lifecycle.Lifecycle);
diff --git a/viewpager2/viewpager2/api/restricted_current.txt b/viewpager2/viewpager2/api/restricted_current.txt
index 38e9cd9..a98edba 100644
--- a/viewpager2/viewpager2/api/restricted_current.txt
+++ b/viewpager2/viewpager2/api/restricted_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.viewpager2.adapter {
 
-  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+  public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder!> implements androidx.viewpager2.adapter.StatefulAdapter {
     ctor public FragmentStateAdapter(androidx.fragment.app.Fragment);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentActivity);
     ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager, androidx.lifecycle.Lifecycle);
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index 9d1b092..9ac320a 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -42,13 +42,15 @@
     implementation("androidx.compose.ui:ui-util:1.6.0")
     implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
     implementation("androidx.core:core:1.12.0")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
     testImplementation(libs.testRules)
     testImplementation(libs.testRunner)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(libs.kotlinTest)
+    testImplementation(libs.kotlinCoroutinesTest)
+    testImplementation(libs.robolectric)
 
     androidTestImplementation(project(":compose:ui:ui-test"))
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
@@ -56,6 +58,10 @@
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.kotlinTest)
     androidTestImplementation(libs.truth)
+
+    // Includes the wear-sdk jar
+    compileOnly files("../../wear_sdk/wear-sdk.jar")
+    testImplementation(files("../../wear_sdk/wear-sdk.jar"))
 }
 
 android {
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedTestTagTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedTestTagTest.kt
new file mode 100644
index 0000000..de30899
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/CurvedTestTagTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import org.junit.Rule
+import org.junit.Test
+
+class CurvedTestTagTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun curvedBox_supports_testTag() {
+        rule.setContent {
+            CurvedLayout {
+                curvedBox(
+                    modifier = CurvedModifier
+                        .testTag(TEST_TAG)
+                ) {
+                    curvedComposable {}
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun curvedRow_supports_testTag() {
+        rule.setContent {
+            CurvedLayout {
+                curvedRow(
+                    modifier = CurvedModifier
+                        .testTag(TEST_TAG)
+                ) {
+                    curvedComposable {}
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun curvedColumn_supports_testTag() {
+        rule.setContent {
+            CurvedLayout {
+                curvedColumn(
+                    modifier = CurvedModifier
+                        .testTag(TEST_TAG)
+                ) {
+                    curvedComposable {}
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun curvedComposable_supports_testTag() {
+        rule.setContent {
+            CurvedLayout {
+                curvedComposable(
+                    modifier = CurvedModifier
+                        .testTag(TEST_TAG)
+                ) {}
+            }
+        }
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+}
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
index f781f3a..33312b2 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
@@ -16,11 +16,17 @@
 
 package androidx.wear.compose.foundation
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.semantics
 import kotlin.math.abs
 import kotlin.math.atan2
 import kotlin.math.min
@@ -68,6 +74,34 @@
     return histogram
 }
 
+/**
+ * Applies a tag to allow modified curved container element to be found in tests. This is similar to
+ * [Modifier.testTag], but specifically for curved containers.
+ *
+ * This is a convenience method for a [semantics] that sets [SemanticsPropertyReceiver.testTag].
+ * Currently, this supports basic assert operations operations only.
+ *
+ * @param tag The tag to apply to the curved container.
+ */
+public fun CurvedModifier.testTag(
+    tag: String
+) = this.then { child ->
+    TestTagWrapper(child, tag)
+}
+
+private class TestTagWrapper(
+    val child: CurvedChild,
+    val tag: String
+) : BaseCurvedChildWrapper(child) {
+
+    @Composable
+    override fun SubComposition() {
+        Box(modifier = Modifier.testTag(tag)) {
+            super.SubComposition()
+        }
+    }
+}
+
 internal fun checkSpy(dimensions: RadialDimensions, capturedInfo: CapturedInfo) =
     checkCurvedLayoutInfo(dimensions.asCurvedLayoutInfo(), capturedInfo.lastLayoutInfo!!)
 
diff --git a/wear/compose/compose-foundation/src/main/AndroidManifest.xml b/wear/compose/compose-foundation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..601babe
--- /dev/null
+++ b/wear/compose/compose-foundation/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+        <uses-library
+            android:name="wear-sdk"
+            android:required="false"/>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt
index 373784a..dd78f66 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Haptics.kt
@@ -16,6 +16,28 @@
 
 package androidx.wear.compose.foundation.rotary
 
+import android.content.Context
+import android.os.Build
+import android.provider.Settings
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalView
+import com.google.wear.input.WearHapticFeedbackConstants
+import kotlin.math.abs
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.withContext
+
 /**
  * Handles haptics for rotary usage
  */
@@ -36,3 +58,313 @@
      */
     fun handleLimitHaptic(event: UnifiedRotaryEvent, isStart: Boolean)
 }
+
+@Composable
+internal fun rememberRotaryHapticHandler(
+    scrollableState: ScrollableState,
+    hapticsEnabled: Boolean
+): RotaryHapticHandler =
+    if (hapticsEnabled) {
+        // TODO(b/319103162): Add platform haptics once AndroidX updates to Android VanillaIceCream
+        rememberCustomRotaryHapticHandler(scrollableState)
+    } else {
+        rememberDisabledRotaryHapticHandler()
+    }
+
+/**
+ * Remembers custom rotary haptic handler.
+ * @param scrollableState A scrollableState, used to determine whether the end of the scrollable
+ * was reached or not.
+ */
+@Composable
+private fun rememberCustomRotaryHapticHandler(
+    scrollableState: ScrollableState,
+): RotaryHapticHandler {
+    val hapticsProvider = rememberRotaryHapticFeedbackProvider()
+    // Channel to which haptic events will be sent
+    val hapticsChannel: Channel<RotaryHapticsType> = rememberHapticChannel()
+
+    // Throttling events within specified timeframe.
+    // Only first and last events will be received. Check [throttleLatest] function for more info.
+    val throttleThresholdMs: Long = 30
+    // A scroll threshold after which haptic is produced.
+    val hapticsThresholdPx: Long = 50
+
+    LaunchedEffect(hapticsChannel, throttleThresholdMs) {
+        hapticsChannel.receiveAsFlow()
+            .throttleLatest(throttleThresholdMs)
+            .collect { hapticType ->
+                // 'withContext' launches performHapticFeedback in a separate thread,
+                // as otherwise it produces a visible lag (b/219776664)
+                val currentTime = System.currentTimeMillis()
+                debugLog { "Haptics started" }
+                withContext(Dispatchers.Default) {
+                    debugLog {
+                        "Performing haptics, delay: " +
+                            "${System.currentTimeMillis() - currentTime}"
+                    }
+                    hapticsProvider.performHapticFeedback(hapticType)
+                }
+            }
+    }
+    return remember(scrollableState, hapticsChannel, hapticsProvider) {
+        CustomRotaryHapticHandler(scrollableState, hapticsChannel, hapticsThresholdPx)
+    }
+}
+
+@Composable
+private fun rememberRotaryHapticFeedbackProvider(): RotaryHapticFeedbackProvider =
+    LocalView.current.let { view ->
+        remember {
+            val hapticConstants = getCustomRotaryConstants(view)
+            RotaryHapticFeedbackProvider(view, hapticConstants)
+        }
+    }
+
+@VisibleForTesting
+internal fun getCustomRotaryConstants(view: View): HapticConstants =
+    when {
+        // Order here is very important: We want to use WearSDK haptic constants for
+        // all devices having api 34 and up, but for Wear3.5 and Wear 4 constants should be
+        // different for Galaxy watches and other devices.
+        hasWearSDK() -> HapticConstants.WearSDKHapticConstants
+        isGalaxyWatch() -> HapticConstants.GalaxyWatchConstants
+        isWear3_5(view.context) -> HapticConstants.Wear3Point5RotaryHapticConstants
+        isWear4() -> HapticConstants.Wear4RotaryHapticConstants
+        else -> HapticConstants.DisabledHapticConstants
+    }
+
+@VisibleForTesting
+internal sealed class HapticConstants(
+    val scrollFocus: Int?,
+    val scrollTick: Int?,
+    val scrollLimit: Int?
+) {
+    /**
+     * Rotary haptic constants from WearSDK
+     */
+    object WearSDKHapticConstants : HapticConstants(
+        WearHapticFeedbackConstants.getScrollItemFocus(),
+        WearHapticFeedbackConstants.getScrollTick(),
+        WearHapticFeedbackConstants.getScrollLimit()
+    )
+
+    /**
+     * Rotary haptic constants for Galaxy Watch. These constants
+     * are used by Samsung for producing rotary haptics
+     */
+    object GalaxyWatchConstants : HapticConstants(
+        102, 101, 50107
+    )
+
+    /**
+     * Hidden constants from HapticFeedbackConstants.java
+     * API 33, Wear 4
+     */
+    object Wear4RotaryHapticConstants : HapticConstants(
+        19, 18, 20
+    )
+
+    /**
+     * Hidden constants from HapticFeedbackConstants.java
+     * API 30, Wear 3.5
+     */
+    object Wear3Point5RotaryHapticConstants : HapticConstants(
+        10003, 10002, 10003
+    )
+
+    object DisabledHapticConstants : HapticConstants(
+        null, null, null
+    )
+}
+
+@Composable
+private fun rememberHapticChannel() =
+    remember {
+        Channel<RotaryHapticsType>(
+            capacity = 2,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST
+        )
+    }
+
+/**
+ * This class handles haptic feedback based
+ * on the [scrollableState], scrolled pixels and [hapticsThresholdPx].
+ * Haptic is not fired in this class, instead it's sent to [hapticsChannel]
+ * where it'll be performed later.
+ *
+ * @param scrollableState Haptic performed based on this state
+ * @param hapticsChannel Channel to which haptic events will be sent
+ * @param hapticsThresholdPx A scroll threshold after which haptic is produced.
+ */
+private class CustomRotaryHapticHandler(
+    private val scrollableState: ScrollableState,
+    private val hapticsChannel: Channel<RotaryHapticsType>,
+    private val hapticsThresholdPx: Long = 50
+) : RotaryHapticHandler {
+
+    private var overscrollHapticTriggered = false
+    private var currScrollPosition = 0f
+    private var prevHapticsPosition = 0f
+
+    override fun handleScrollHaptic(event: UnifiedRotaryEvent) {
+        if (scrollableState.reachedTheLimit(event.deltaInPixels)) {
+            handleLimitHaptic(event, scrollableState.canScrollBackward)
+        } else {
+            overscrollHapticTriggered = false
+            currScrollPosition += event.deltaInPixels
+            val diff = abs(currScrollPosition - prevHapticsPosition)
+
+            if (diff >= hapticsThresholdPx) {
+                hapticsChannel.trySend(RotaryHapticsType.ScrollTick)
+                prevHapticsPosition = currScrollPosition
+            }
+        }
+    }
+
+    override fun handleSnapHaptic(event: UnifiedRotaryEvent) {
+        if (scrollableState.reachedTheLimit(event.deltaInPixels)) {
+            handleLimitHaptic(event, scrollableState.canScrollBackward)
+        } else {
+            overscrollHapticTriggered = false
+            hapticsChannel.trySend(RotaryHapticsType.ScrollItemFocus)
+        }
+    }
+
+    override fun handleLimitHaptic(event: UnifiedRotaryEvent, isStart: Boolean) {
+        if (!overscrollHapticTriggered) {
+            hapticsChannel.trySend(RotaryHapticsType.ScrollLimit)
+            overscrollHapticTriggered = true
+        }
+    }
+}
+
+/**
+ * Rotary haptic types
+ */
+@JvmInline
+@VisibleForTesting
+internal value class RotaryHapticsType(private val type: Int) {
+    companion object {
+
+        /**
+         * A scroll ticking haptic. Similar to texture haptic - performed each time when
+         * a scrollable content is scrolled by a certain distance
+         */
+        public val ScrollTick: RotaryHapticsType = RotaryHapticsType(1)
+
+        /**
+         * An item focus (snap) haptic. Performed when a scrollable content is snapped
+         * to a specific item.
+         */
+        public val ScrollItemFocus: RotaryHapticsType = RotaryHapticsType(2)
+
+        /**
+         * A limit(overscroll) haptic. Performed when a list reaches the limit
+         * (start or end) and can't scroll further
+         */
+        public val ScrollLimit: RotaryHapticsType = RotaryHapticsType(3)
+    }
+}
+
+/**
+ * Remember disabled haptics handler
+ */
+@Composable
+private fun rememberDisabledRotaryHapticHandler(): RotaryHapticHandler = remember {
+    object : RotaryHapticHandler {
+        override fun handleScrollHaptic(event: UnifiedRotaryEvent) {
+            // Do nothing
+        }
+
+        override fun handleSnapHaptic(event: UnifiedRotaryEvent) {
+            // Do nothing
+        }
+
+        override fun handleLimitHaptic(event: UnifiedRotaryEvent, isStart: Boolean) {
+            // Do nothing
+        }
+    }
+}
+
+/**
+ * Rotary haptic feedback
+ */
+private class RotaryHapticFeedbackProvider(
+    private val view: View,
+    private val hapticConstants: HapticConstants
+) {
+    fun performHapticFeedback(
+        type: RotaryHapticsType,
+    ) {
+        when (type) {
+            RotaryHapticsType.ScrollItemFocus -> {
+                hapticConstants.scrollFocus?.let { view.performHapticFeedback(it) }
+            }
+
+            RotaryHapticsType.ScrollTick -> {
+                hapticConstants.scrollTick?.let { view.performHapticFeedback(it) }
+            }
+
+            RotaryHapticsType.ScrollLimit -> {
+                hapticConstants.scrollLimit?.let { view.performHapticFeedback(it) }
+            }
+        }
+    }
+}
+
+private fun isGalaxyWatch(): Boolean =
+    Build.MANUFACTURER.contains("Samsung", ignoreCase = true) &&
+        Build.MODEL.matches("^SM-R.*\$".toRegex())
+
+private fun isWear3_5(context: Context): Boolean =
+    Build.VERSION.SDK_INT == Build.VERSION_CODES.R && getWearPlatformMrNumber(context) >= 5
+
+private fun isWear4(): Boolean =
+    Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU
+
+private fun hasWearSDK(): Boolean =
+    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+
+private fun getWearPlatformMrNumber(context: Context): Int =
+    Settings.Global
+        .getString(context.contentResolver, WEAR_PLATFORM_MR_NUMBER)?.toIntOrNull() ?: 0
+
+private const val WEAR_PLATFORM_MR_NUMBER: String = "wear_platform_mr_number"
+
+private fun ScrollableState.reachedTheLimit(scrollDelta: Float): Boolean =
+    (scrollDelta > 0 && !canScrollForward) || (scrollDelta < 0 && !canScrollBackward)
+
+/**
+ * Debug logging that can be enabled.
+ */
+private const val DEBUG = false
+
+private inline fun debugLog(generateMsg: () -> String) {
+    if (DEBUG) {
+        println("RotaryHaptics: ${generateMsg()}")
+    }
+}
+
+/**
+ * Throttling events within specified timeframe. Only first and last events will be received.
+ *
+ * For example, a flow emits elements 1 to 30, with a 100ms delay between them:
+ * ```
+ * val flow = flow {
+ *     for (i in 1..30) {
+ *         delay(100)
+ *         emit(i)
+ *     }
+ * }
+ * ```
+ * With timeframe=1000 only those integers will be received: 1, 10, 20, 30 .
+ */
+@VisibleForTesting
+internal fun <T> Flow<T>.throttleLatest(timeframe: Long): Flow<T> =
+    flow {
+        conflate().collect {
+            emit(it)
+            delay(timeframe)
+        }
+    }
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt
index a82086d..5de31b2 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/rotary/Rotary.kt
@@ -28,14 +28,21 @@
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.input.rotary.RotaryInputModifierNode
 import androidx.compose.ui.input.rotary.RotaryScrollEvent
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.util.fastSumBy
 import androidx.compose.ui.util.lerp
@@ -56,6 +63,43 @@
 import kotlinx.coroutines.launch
 
 /**
+ * Abstract class for setting rotary parameters.
+ * Has 2 implementations - [RotaryDefaults.scrollSpec] and [RotaryDefaults.snapSpec].
+ */
+// TODO(b/278705775): make it public once haptics and other code is merged.
+@ExperimentalWearFoundationApi
+internal abstract class RotarySpec internal constructor() {
+    internal abstract val rotaryHandler: RotaryHandler
+}
+
+/**
+ * A modifier which connects rotary events with scrollable.
+ *
+ * This modifier supports rotary scrolling and snapping.
+ * The behaviour is configured by the provided [RotarySpec]:
+ * either provide [RotaryDefaults.scrollSpec] for scrolling with/without fling
+ * or pass [RotaryDefaults.snapSpec] when snap is required.
+ *
+ * @param rotarySpec Specified [RotarySpec] for proper rotary handling.
+ * @param focusRequester Requests the focus for rotary input.
+ * @param reverseDirection Reverse the direction of scrolling if required. Should be aligned with
+ * Scrollable `reverseDirection` parameter.
+ */
+@ExperimentalWearFoundationApi
+// TODO(b/278705775): make it public once haptics and other code is merged.
+internal fun Modifier.rotary(
+    rotarySpec: RotarySpec,
+    focusRequester: FocusRequester,
+    reverseDirection: Boolean = false
+): Modifier =
+    rotaryHandler(
+        rotaryHandler = rotarySpec.rotaryHandler,
+        reverseDirection = reverseDirection,
+    )
+        .focusRequester(focusRequester)
+        .focusable()
+
+/**
  * An adapter which connects scrollableState to a rotary input for snapping scroll actions.
  *
  * This interface defines the essential properties and methods required for a scrollable
@@ -64,7 +108,7 @@
  */
 @ExperimentalWearFoundationApi
 // TODO(b/278705775): make it public once haptics and other code is merged.
-/* public */ internal interface RotaryScrollAdapter {
+internal interface RotaryScrollableAdapter {
 
     /**
      * The scrollable state used for performing scroll actions in response to rotary events.
@@ -92,7 +136,6 @@
      */
     fun currentItemOffset(): Float
 
-    // TODO(b/326239879) Investigate and test whether this method can be removed.
     /**
      * The total number of items within the scrollable in [scrollableState]
      */
@@ -106,6 +149,93 @@
 // TODO(b/278705775): make it public once haptics and other code is merged.
 /* public */ internal object RotaryDefaults {
 
+    /**
+     * Implementation of the [RotarySpec] to define scrolling behaviour with or without fling.
+     * Should be set as a parameter of [rotary] modifier.
+     *
+     * If fling is not required [flingBehavior] should be set as null.
+     * Note: If [flingBehavior] is null, flinging will not happen and the scrollable content will
+     * stop scrolling immediately after the user stops interacting with rotary input.
+     *
+     * @param scrollableState Scrollable state which will be scrolled
+     * while receiving rotary events.
+     * @param flingBehavior An optional fling behavior, which controls flinging behavior
+     * with rotary. If null fling will not happen.
+     * @param hapticFeedbackEnabled Responsible for haptic feedback during rotary
+     * rotation. By default is true.
+     */
+    @Composable
+    fun scrollSpec(
+        scrollableState: ScrollableState,
+        flingBehavior: FlingBehavior? = ScrollableDefaults.flingBehavior(),
+        hapticFeedbackEnabled: Boolean = true
+    ): RotarySpec {
+        val isLowRes = isLowResInput()
+        val viewConfiguration = ViewConfiguration.get(LocalContext.current)
+        val rotaryHaptics: RotaryHapticHandler =
+            rememberRotaryHapticHandler(scrollableState, hapticFeedbackEnabled)
+
+        return object : RotarySpec() {
+            override val rotaryHandler =
+                flingHandler(
+                    scrollableState,
+                    rotaryHaptics,
+                    flingBehavior,
+                    isLowRes,
+                    viewConfiguration
+                )
+        }
+    }
+
+    /**
+     * Implementation of the [RotarySpec] to define snap behavior. Should be set as
+     * a parameter of [rotary] modifier.
+     *
+     * @param rotaryScrollableAdapter A connection between scrollable entities and rotary events.
+     * @param snapOffset An optional offset to be applied when snapping the item.
+     * After the snap the snapped items offset will be [snapOffset].
+     * @param hapticFeedbackEnabled Responsible for haptic feedback during rotary
+     * rotation. By default is true.
+     */
+    @Composable
+    fun snapSpec(
+        rotaryScrollableAdapter: RotaryScrollableAdapter,
+        snapOffset: Int = SNAP_OFFSET,
+        hapticFeedbackEnabled: Boolean = true
+    ): RotarySpec {
+        val isLowRes = isLowResInput()
+        val rotaryHaptics: RotaryHapticHandler =
+            rememberRotaryHapticHandler(
+                rotaryScrollableAdapter.scrollableState,
+                hapticFeedbackEnabled
+            )
+
+        return remember(rotaryScrollableAdapter, rotaryHaptics, snapOffset, isLowRes) {
+            object : RotarySpec() {
+                override val rotaryHandler =
+                    snapHandler(
+                        rotaryScrollableAdapter,
+                        rotaryHaptics,
+                        snapOffset,
+                        THRESHOLD_DIVIDER,
+                        RESISTANCE_FACTOR,
+                        isLowRes
+                    )
+            }
+        }
+    }
+
+    /**
+     * Returns whether the input is Low-res (a bezel) or high-res (a crown/rsb).
+     */
+    @Composable
+    private fun isLowResInput(): Boolean = LocalContext.current.packageManager
+        .hasSystemFeature("android.hardware.rotaryencoder.lowres")
+
+    private const val SNAP_OFFSET: Int = 0
+    private const val THRESHOLD_DIVIDER: Float = 1.5f
+    private const val RESISTANCE_FACTOR: Float = 3f
+
     // These values represent the timeframe for a fling event. A bigger value is assigned
     // to low-res input due to the lower frequency of low-res rotary events.
     internal const val lowResFlingTimeframe: Long = 100L
@@ -116,9 +246,9 @@
  * An implementation of rotary scroll adapter for ScalingLazyColumn
  */
 @OptIn(ExperimentalWearFoundationApi::class)
-internal class ScalingLazyColumnRotaryScrollAdapter(
+internal class ScalingLazyColumnRotaryScrollableAdapter(
     override val scrollableState: ScalingLazyListState
-) : RotaryScrollAdapter {
+) : RotaryScrollableAdapter {
 
     /**
      * Calculates the average item height by averaging the height of visible items.
@@ -189,7 +319,7 @@
  * @return A snap implementation of [RotaryHandler] which is either suitable for low-res or
  * high-res input.
  *
- * @param rotaryScrollAdapter Implementation of [RotaryScrollAdapter], which connects
+ * @param rotaryScrollableAdapter Implementation of [RotaryScrollableAdapter], which connects
  * scrollableState to a rotary input for snapping scroll actions.
  * @param rotaryHaptics Implementation of [RotaryHapticHandler] which handles haptics
  * for rotary usage
@@ -201,7 +331,7 @@
  * @param isLowRes Whether the input is Low-res (a bezel) or high-res(a crown/rsb)
  */
 private fun snapHandler(
-    rotaryScrollAdapter: RotaryScrollAdapter,
+    rotaryScrollableAdapter: RotaryScrollableAdapter,
     rotaryHaptics: RotaryHapticHandler,
     snapOffset: Int,
     maxThresholdDivider: Float,
@@ -213,7 +343,7 @@
             rotaryHaptics = rotaryHaptics,
             snapBehaviourFactory = {
                 RotarySnapHelper(
-                    rotaryScrollAdapter,
+                    rotaryScrollableAdapter,
                     snapOffset,
                 )
             }
@@ -225,24 +355,26 @@
             thresholdBehaviorFactory = {
                 ThresholdBehavior(
                     maxThresholdDivider,
-                    averageItemSize = { rotaryScrollAdapter.averageItemSize() }
+                    averageItemSize = { rotaryScrollableAdapter.averageItemSize() }
                 )
             },
             snapBehaviorFactory = {
                 RotarySnapHelper(
-                    rotaryScrollAdapter,
+                    rotaryScrollableAdapter,
                     snapOffset,
                 )
             },
             scrollBehaviorFactory = {
-                RotaryScrollBehavior(rotaryScrollAdapter.scrollableState)
+                RotaryScrollBehavior(rotaryScrollableAdapter.scrollableState)
             }
         )
     }
 }
 
 /**
- * An abstract class for handling scroll events
+ * An abstract base class for handling scroll events. Has implementations for handling scroll
+ * with/without fling [RotaryScrollHandler] and for handling snap [LowResRotarySnapHandler],
+ * [HighResRotarySnapHandler].
  */
 internal abstract class RotaryHandler {
 
@@ -327,10 +459,10 @@
  * A helper class for snapping with rotary.
  */
 internal class RotarySnapHelper(
-    private val rotaryScrollAdapter: RotaryScrollAdapter,
+    private val rotaryScrollableAdapter: RotaryScrollableAdapter,
     private val snapOffset: Int,
 ) {
-    private var snapTarget: Int = rotaryScrollAdapter.currentItemIndex()
+    private var snapTarget: Int = rotaryScrollableAdapter.currentItemIndex()
     private var sequentialSnap: Boolean = false
 
     private var anim = AnimationState(0f)
@@ -355,10 +487,11 @@
         if (sequentialSnap) {
             snapTarget += moveForElements
         } else {
-            snapTarget = rotaryScrollAdapter.currentItemIndex() + moveForElements
+            snapTarget = rotaryScrollableAdapter.currentItemIndex() + moveForElements
         }
         snapTargetUpdated = true
-        snapTarget = snapTarget.coerceIn(0 until rotaryScrollAdapter.totalItemsCount())
+        snapTarget = snapTarget
+            .coerceIn(0 until rotaryScrollableAdapter.totalItemsCount())
     }
 
     /**
@@ -366,13 +499,13 @@
      */
     suspend fun snapToClosestItem() {
         // Perform the snapping animation
-        rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
+        rotaryScrollableAdapter.scrollableState.scroll(MutatePriority.UserInput) {
             debugLog { "snap to the closest item" }
             var prevPosition = 0f
 
             // Create and execute the snap animation
             AnimationState(0f).animateTo(
-                targetValue = -rotaryScrollAdapter.currentItemOffset(),
+                targetValue = -rotaryScrollableAdapter.currentItemOffset(),
                 animationSpec = tween(durationMillis = 100, easing = FastOutSlowInEasing)
             ) {
                 val animDelta = value - prevPosition
@@ -380,7 +513,7 @@
                 prevPosition = value
             }
             // Update the snap target to ensure consistency
-            snapTarget = rotaryScrollAdapter.currentItemIndex()
+            snapTarget = rotaryScrollableAdapter.currentItemIndex()
         }
     }
 
@@ -393,7 +526,7 @@
      * Returns true if bottom edge was reached
      */
     fun bottomEdgeReached(): Boolean =
-        snapTarget >= rotaryScrollAdapter.totalItemsCount() - 1
+        snapTarget >= rotaryScrollableAdapter.totalItemsCount() - 1
 
     /**
      * Performs snapping to the specified in [updateSnapTarget] element
@@ -401,7 +534,7 @@
     suspend fun snapToTargetItem() {
         if (!sequentialSnap) anim = AnimationState(0f)
 
-        rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
+        rotaryScrollableAdapter.scrollableState.scroll(MutatePriority.UserInput) {
             // If snapTargetUpdated is true -means the target was updated so we
             // need to do snap animation again
             while (snapTargetUpdated) {
@@ -412,12 +545,12 @@
 
                 // First part of animation. Performing it until the target element centered.
                 while (continueFirstScroll) {
-                    latestCenterItem = rotaryScrollAdapter.currentItemIndex()
+                    latestCenterItem = rotaryScrollableAdapter.currentItemIndex()
                     expectedDistance = expectedDistanceTo(snapTarget, snapOffset)
                     debugLog {
                         "expectedDistance = $expectedDistance, " +
                             "scrollableState.centerItemScrollOffset " +
-                            "${rotaryScrollAdapter.currentItemOffset()}"
+                            "${rotaryScrollableAdapter.currentItemOffset()}"
                     }
 
                     continueFirstScroll = false
@@ -441,20 +574,22 @@
                         scrollBy(animDelta)
                         prevPosition = value
 
-                        if (latestCenterItem != rotaryScrollAdapter.currentItemIndex()) {
+                        if (latestCenterItem != rotaryScrollableAdapter.currentItemIndex()) {
                             continueFirstScroll = true
                             cancelAnimation()
                             return@animateTo
                         }
 
-                        debugLog { "centerItemIndex =  ${rotaryScrollAdapter.currentItemIndex()}" }
-                        if (rotaryScrollAdapter.currentItemIndex() == snapTarget) {
+                        debugLog {
+                            "centerItemIndex =  ${rotaryScrollableAdapter.currentItemIndex()}"
+                        }
+                        if (rotaryScrollableAdapter.currentItemIndex() == snapTarget) {
                             debugLog { "Target is near the centre. Cancelling first animation" }
                             debugLog {
                                 "scrollableState.centerItemScrollOffset " +
-                                    "${rotaryScrollAdapter.currentItemOffset()}"
+                                    "${rotaryScrollableAdapter.currentItemOffset()}"
                             }
-                            expectedDistance = -rotaryScrollAdapter.currentItemOffset()
+                            expectedDistance = -rotaryScrollableAdapter.currentItemOffset()
                             continueFirstScroll = false
                             cancelAnimation()
                             return@animateTo
@@ -487,28 +622,28 @@
     }
 
     private fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
-        val averageSize = rotaryScrollAdapter.averageItemSize()
-        val indexesDiff = index - rotaryScrollAdapter.currentItemIndex()
+        val averageSize = rotaryScrollableAdapter.averageItemSize()
+        val indexesDiff = index - rotaryScrollableAdapter.currentItemIndex()
         debugLog { "Average size $averageSize" }
         return (averageSize * indexesDiff) +
-            targetScrollOffset - rotaryScrollAdapter.currentItemOffset()
+            targetScrollOffset - rotaryScrollableAdapter.currentItemOffset()
     }
 }
 
 /**
  * A modifier which handles rotary events.
- * It accepts ScrollHandler as the input - a class that handles the main scroll logic.
+ * It accepts [RotaryHandler] as the input - a class that handles the main scroll logic.
  */
 internal fun Modifier.rotaryHandler(
-    rotaryScrollHandler: RotaryHandler,
+    rotaryHandler: RotaryHandler,
     reverseDirection: Boolean,
     inspectorInfo: InspectorInfo.() -> Unit = debugInspectorInfo {
         name = "rotaryHandler"
-        properties["rotaryScrollHandler"] = rotaryScrollHandler
+        properties["rotaryHandler"] = rotaryHandler
         properties["reverseDirection"] = reverseDirection
     }
 ): Modifier = this then RotaryHandlerElement(
-    rotaryScrollHandler,
+    rotaryHandler,
     reverseDirection,
     inspectorInfo
 )
@@ -949,7 +1084,7 @@
     // Smoothing factor for velocity readings
     private val smoothingConstant: Float = 0.4f,
     private val averageItemSize: () -> Float
-    ) {
+) {
     private val thresholdDividerEasing: Easing = CubicBezierEasing(0.5f, 0.0f, 0.5f, 1.0f)
 
     private val rotaryVelocityTracker = RotaryVelocityTracker()
@@ -1023,18 +1158,18 @@
 }
 
 private data class RotaryHandlerElement(
-    private val rotaryScrollHandler: RotaryHandler,
+    private val rotaryHandler: RotaryHandler,
     private val reverseDirection: Boolean,
     private val inspectorInfo: InspectorInfo.() -> Unit
 ) : ModifierNodeElement<RotaryInputNode>() {
     override fun create(): RotaryInputNode = RotaryInputNode(
-        rotaryScrollHandler,
+        rotaryHandler,
         reverseDirection,
     )
 
     override fun update(node: RotaryInputNode) {
         debugLog { "Update launched!" }
-        node.rotaryScrollHandler = rotaryScrollHandler
+        node.rotaryHandler = rotaryHandler
         node.reverseDirection = reverseDirection
     }
 
@@ -1048,21 +1183,21 @@
 
         other as RotaryHandlerElement
 
-        if (rotaryScrollHandler != other.rotaryScrollHandler) return false
+        if (rotaryHandler != other.rotaryHandler) return false
         if (reverseDirection != other.reverseDirection) return false
 
         return true
     }
 
     override fun hashCode(): Int {
-        var result = rotaryScrollHandler.hashCode()
+        var result = rotaryHandler.hashCode()
         result = 31 * result + reverseDirection.hashCode()
         return result
     }
 }
 
 private class RotaryInputNode(
-    var rotaryScrollHandler: RotaryHandler,
+    var rotaryHandler: RotaryHandler,
     var reverseDirection: Boolean,
 ) : RotaryInputModifierNode, Modifier.Node() {
 
@@ -1077,7 +1212,7 @@
                         "Scroll event received: " +
                             "delta:${it.deltaInPixels}, timestamp:${it.timestamp}"
                     }
-                    rotaryScrollHandler.handleScrollEvent(this, it)
+                    rotaryHandler.handleScrollEvent(this, it)
                 }
         }
     }
diff --git a/wear/compose/compose-foundation/src/test/kotlin/androidx/wear/compose/foundation/rotary/HapticsTest.kt b/wear/compose/compose-foundation/src/test/kotlin/androidx/wear/compose/foundation/rotary/HapticsTest.kt
new file mode 100644
index 0000000..da6eb40
--- /dev/null
+++ b/wear/compose/compose-foundation/src/test/kotlin/androidx/wear/compose/foundation/rotary/HapticsTest.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation.rotary
+
+import android.R
+import android.app.Activity
+import android.provider.Settings
+import android.view.View
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.robolectric.Robolectric
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowBuild
+
+@RunWith(JUnit4::class)
+class ThrottleLatestTest {
+    private lateinit var testChannel: Channel<RotaryHapticsType>
+
+    @Before
+    fun before() {
+        testChannel = Channel(
+            capacity = 10,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST
+        )
+    }
+
+    @Test
+    fun single_event_sent() = runTest {
+        val testFlow = testChannel.receiveAsFlow().throttleLatest(40)
+        val expectedItemsSize = 1
+
+        launch {
+            testChannel.trySend(RotaryHapticsType.ScrollTick)
+            testChannel.close()
+        }
+        val actualItems = testFlow.toList()
+
+        assertEquals(expectedItemsSize, actualItems.size)
+    }
+
+    @Test
+    fun three_events_sent_one_filtered() = runTest {
+        val testFlow = testChannel.receiveAsFlow().throttleLatest(40)
+        val expectedItemsSize = 2
+
+        // Send 3 events, receive 2 because they fall into a single timeframe and only
+        // 1st and last items are returned
+        launch {
+            testChannel.sendEventsWithDelay(RotaryHapticsType.ScrollTick, 3, 10)
+            testChannel.close()
+        }
+        val actualItems = testFlow.toList()
+
+        assertEquals(expectedItemsSize, actualItems.size)
+    }
+
+    @Test
+    fun three_events_sent_none_filtered() = runTest {
+        val testFlow = testChannel.receiveAsFlow().throttleLatest(40)
+        val expectedItemsSize = 3
+        // Sent 3 events, received 3 because delay between events is bigger than a timeframe
+        launch {
+            testChannel.sendEventsWithDelay(RotaryHapticsType.ScrollTick, 3, 50)
+            testChannel.close()
+        }
+        val actualItems = testFlow.toList()
+
+        assertEquals(expectedItemsSize, actualItems.size)
+    }
+
+    @Test
+    fun three_slow_and_five_fast() = runTest {
+        val testFlow = testChannel.receiveAsFlow().throttleLatest(40)
+        val expectedItemsSize = 5
+        launch {
+            // Sent 3 events, received 3 because delay between events is bigger than a timeframe
+            testChannel.sendEventsWithDelay(RotaryHapticsType.ScrollTick, 3, 50)
+            delay(50)
+            // Sent 5 events, received 2 (first and last) because delay between events
+            // was smaller than a timeframe
+            testChannel.sendEventsWithDelay(RotaryHapticsType.ScrollTick, 5, 5)
+            delay(5)
+            testChannel.close()
+        }
+
+        val actualItems = testFlow.toList()
+
+        assertEquals(expectedItemsSize, actualItems.size)
+    }
+
+    private suspend fun Channel<RotaryHapticsType>.sendEventsWithDelay(
+        event: RotaryHapticsType,
+        eventCount: Int,
+        delayMillis: Long
+    ) {
+        for (i in 0 until eventCount) {
+            trySend(event)
+            if (i < eventCount - 1) {
+                delay(delayMillis)
+            }
+        }
+    }
+}
+
+@RunWith(RobolectricTestRunner::class)
+class HapticsTest {
+    @Test
+    @Config(sdk = [33])
+    fun testPixelWatch1Wear4() {
+        ShadowBuild.setManufacturer("Google")
+        ShadowBuild.setModel("Google Pixel Watch")
+
+        assertEquals(HapticConstants.Wear4RotaryHapticConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [30])
+    fun testPixelWatch1Wear35() {
+        ShadowBuild.setManufacturer("Google")
+        ShadowBuild.setModel("Google Pixel Watch")
+        Settings.Global.putString(
+            RuntimeEnvironment.getApplication().contentResolver,
+            "wear_platform_mr_number",
+            "5",
+        )
+
+        assertEquals(HapticConstants.Wear3Point5RotaryHapticConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [33])
+    fun testGenericWear4() {
+        ShadowBuild.setManufacturer("XXX")
+        ShadowBuild.setModel("YYY")
+
+        assertEquals(HapticConstants.Wear4RotaryHapticConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [30])
+    fun testGenericWear35() {
+        ShadowBuild.setManufacturer("XXX")
+        ShadowBuild.setModel("YYY")
+        Settings.Global.putString(
+            RuntimeEnvironment.getApplication().contentResolver,
+            "wear_platform_mr_number",
+            "5",
+        )
+
+        assertEquals(HapticConstants.Wear3Point5RotaryHapticConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [30])
+    fun testGenericWear3() {
+        ShadowBuild.setManufacturer("XXX")
+        ShadowBuild.setModel("YYY")
+
+        assertEquals(HapticConstants.DisabledHapticConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [28])
+    fun testGenericWear2() {
+        ShadowBuild.setManufacturer("XXX")
+        ShadowBuild.setModel("YYY")
+
+        assertEquals(HapticConstants.DisabledHapticConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [33])
+    fun testGalaxyWatchClassic() {
+        ShadowBuild.setManufacturer("Samsung")
+        // Galaxy Watch4 Classic
+        ShadowBuild.setModel("SM-R890")
+
+        assertEquals(HapticConstants.GalaxyWatchConstants, getHapticConstants())
+    }
+
+    @Test
+    @Config(sdk = [33])
+    fun testGalaxyWatch() {
+        ShadowBuild.setManufacturer("Samsung")
+        // Galaxy Watch 5 Pro
+        ShadowBuild.setModel("SM-R925")
+
+        assertEquals(HapticConstants.GalaxyWatchConstants, getHapticConstants())
+    }
+
+    private fun getHapticConstants(): HapticConstants {
+        val activity = Robolectric.buildActivity(Activity::class.java).get()
+        val view = activity.findViewById<View>(R.id.content)
+
+        return getCustomRotaryConstants(view)
+    }
+}
diff --git a/wear/compose/compose-material-core/build.gradle b/wear/compose/compose-material-core/build.gradle
index 921a918..7f7f700 100644
--- a/wear/compose/compose-material-core/build.gradle
+++ b/wear/compose/compose-material-core/build.gradle
@@ -44,7 +44,7 @@
     implementation("androidx.compose.material:material-ripple:1.6.0")
     implementation("androidx.compose.ui:ui-util:1.6.0")
     implementation(project(":wear:compose:compose-foundation"))
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
     androidTestImplementation(project(":compose:ui:ui-test"))
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
index d1c11d8..1c523f1 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
@@ -1122,6 +1122,7 @@
     shape = shape,
     toggleControlWidth = selectionControlWidth,
     toggleControlHeight = selectionControlHeight,
+    labelSpacerSize = 0.dp,
     ripple = EmptyIndication,
 )
 
@@ -1172,6 +1173,7 @@
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
     shape = shape,
+    labelSpacerSize = 0.dp,
     ripple = EmptyIndication,
 )
 
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
index 397cd10..792bd00 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
@@ -188,6 +188,7 @@
     shape: Shape,
     toggleControlWidth: Dp,
     toggleControlHeight: Dp,
+    labelSpacerSize: Dp,
     ripple: Indication
 ) {
     // One and only one of toggleControl and selectionControl should be provided.
@@ -226,7 +227,8 @@
         ToggleButtonIcon(content = icon)
         Labels(
             label = label,
-            secondaryLabel = secondaryLabel
+            secondaryLabel = secondaryLabel,
+            spacerSize = labelSpacerSize
         )
         Spacer(
             modifier = Modifier.size(
@@ -311,6 +313,7 @@
     clickInteractionSource: MutableInteractionSource?,
     contentPadding: PaddingValues,
     shape: Shape,
+    labelSpacerSize: Dp,
     ripple: Indication
 ) {
     val (startPadding, endPadding) = contentPadding.splitHorizontally()
@@ -341,6 +344,7 @@
             Labels(
                 label = label,
                 secondaryLabel = secondaryLabel,
+                spacerSize = labelSpacerSize
             )
             Spacer(
                 modifier = Modifier
@@ -375,7 +379,7 @@
 
         Box(
             modifier =
-                boxModifier
+            boxModifier
                 .fillMaxHeight()
                 .drawWithCache {
                     onDrawWithContent {
@@ -409,11 +413,13 @@
 @Composable
 private fun RowScope.Labels(
     label: @Composable RowScope.() -> Unit,
-    secondaryLabel: @Composable (RowScope.() -> Unit)?
+    secondaryLabel: @Composable (RowScope.() -> Unit)?,
+    spacerSize: Dp = 0.dp
 ) {
     Column(modifier = Modifier.weight(1.0f)) {
         Row(content = label)
         if (secondaryLabel != null) {
+            Spacer(modifier = Modifier.size(spacerSize))
             Row(content = secondaryLabel)
         }
     }
diff --git a/wear/compose/compose-material/build.gradle b/wear/compose/compose-material/build.gradle
index 924b9f2..04c1634 100644
--- a/wear/compose/compose-material/build.gradle
+++ b/wear/compose/compose-material/build.gradle
@@ -43,7 +43,7 @@
     implementation(project(":compose:material:material-ripple"))
     implementation("androidx.compose.ui:ui-util:1.6.0")
     implementation(project(":wear:compose:compose-material-core"))
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
     implementation("androidx.lifecycle:lifecycle-common:2.7.0")
 
     androidTestImplementation(project(":compose:ui:ui-test"))
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
index 3c52ce4..f0911f0 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
@@ -158,6 +158,7 @@
     shape = shape,
     toggleControlHeight = TOGGLE_CONTROL_HEIGHT,
     toggleControlWidth = TOGGLE_CONTROL_WIDTH,
+    labelSpacerSize = 0.dp,
     ripple = rippleOrFallbackImplementation()
 )
 
@@ -276,6 +277,7 @@
     shape = shape,
     toggleControlHeight = TOGGLE_CONTROL_HEIGHT,
     toggleControlWidth = TOGGLE_CONTROL_WIDTH,
+    labelSpacerSize = 0.dp,
     ripple = rippleOrFallbackImplementation()
 )
 
@@ -402,6 +404,7 @@
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
     shape = shape,
+    labelSpacerSize = 0.dp,
     ripple = rippleOrFallbackImplementation()
 )
 
@@ -525,6 +528,7 @@
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
     shape = shape,
+    labelSpacerSize = 0.dp,
     ripple = rippleOrFallbackImplementation()
 )
 
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TouchExplorationStateProvider.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TouchExplorationStateProvider.kt
index 7e3e7444..665daf1 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TouchExplorationStateProvider.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/TouchExplorationStateProvider.kt
@@ -28,9 +28,9 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 
 /**
  * A functional interface for providing the state of touch exploration services. It is strongly
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index 6ba5202..955646f 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -44,7 +44,7 @@
     implementation(project(":compose:material:material-ripple"))
     implementation("androidx.compose.ui:ui-util:1.6.0")
     implementation(project(":wear:compose:compose-material-core"))
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
     androidTestImplementation(project(":compose:ui:ui-test"))
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
index daf7691..2dc5958 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
@@ -782,7 +782,8 @@
     if (label != null) {
         ButtonImpl(
             onClick = onClick,
-            modifier = modifier.compactButtonModifier()
+            modifier = modifier
+                .compactButtonModifier()
                 .padding(ButtonDefaults.CompactButtonTapTargetPadding),
             secondaryLabel = null,
             icon = icon,
@@ -801,7 +802,8 @@
         // content. We use the base simple single slot Button under the covers.
         ButtonImpl(
             onClick = onClick,
-            modifier = modifier.compactButtonModifier()
+            modifier = modifier
+                .compactButtonModifier()
                 .width(ButtonDefaults.IconOnlyCompactButtonWidth)
                 .padding(ButtonDefaults.CompactButtonTapTargetPadding),
             enabled = enabled,
@@ -1451,7 +1453,8 @@
 
 @Composable
 private fun Modifier.buttonSizeModifier(): Modifier =
-    this.defaultMinSize(minHeight = ButtonDefaults.Height)
+    this
+        .defaultMinSize(minHeight = ButtonDefaults.Height)
         .height(IntrinsicSize.Min)
 
 @Composable
@@ -1557,13 +1560,14 @@
                     )
                 )
                 if (secondaryLabel != null && secondaryLabelFont != null) {
-                   Row(
-                       content = provideScopeContent(
-                           colors.secondaryContentColor(enabled),
-                           secondaryLabelFont,
-                           secondaryLabel
-                       )
-                   )
+                    Spacer(modifier = Modifier.size(2.dp))
+                    Row(
+                        content = provideScopeContent(
+                            colors.secondaryContentColor(enabled),
+                            secondaryLabelFont,
+                            secondaryLabel
+                        )
+                    )
                 }
             }
         }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
index be6073c..70dbab71 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
@@ -500,6 +500,7 @@
             disabledUnselectedSplitContainerColor = disabledUnselectedSplitContainerColor
         )
 
+    internal val LabelSpacerSize = 2.dp
     private val HorizontalPadding = 14.dp
     private val VerticalPadding = 6.dp
 
@@ -1084,6 +1085,7 @@
     Column(modifier = Modifier.weight(1.0f)) {
         Row(content = label)
         if (secondaryLabel != null) {
+            Spacer(modifier = Modifier.size(RadioButtonDefaults.LabelSpacerSize))
             Row(content = secondaryLabel)
         }
     }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt
index 5155500..f88b805 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt
@@ -242,8 +242,8 @@
 }
 
 private val RippleAlpha: RippleAlpha = RippleAlpha(
-    pressedAlpha = 0.12f,
-    focusedAlpha = 0.12f,
+    pressedAlpha = 0.10f,
+    focusedAlpha = 0.10f,
     draggedAlpha = 0.16f,
     hoveredAlpha = 0.08f
 )
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
index 3436d0a..f48561e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
@@ -142,6 +142,7 @@
         shape = shape,
         toggleControlWidth = TOGGLE_CONTROL_WIDTH,
         toggleControlHeight = TOGGLE_CONTROL_HEIGHT,
+        labelSpacerSize = ToggleButtonDefaults.LabelSpacerSize,
         ripple = rippleOrFallbackImplementation()
     )
 
@@ -259,6 +260,7 @@
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
     shape = shape,
+    labelSpacerSize = ToggleButtonDefaults.LabelSpacerSize,
     ripple = rippleOrFallbackImplementation()
 )
 
@@ -425,17 +427,18 @@
             disabledUncheckedSplitContainerColor = disabledUncheckedSplitContainerColor
         )
 
-    private val ChipHorizontalPadding = 14.dp
-    private val ChipVerticalPadding = 6.dp
+    internal val LabelSpacerSize = 2.dp
+    private val HorizontalPadding = 14.dp
+    private val VerticalPadding = 6.dp
 
     /**
      * The default content padding used by [ToggleButton]
      */
     val ContentPadding: PaddingValues = PaddingValues(
-        start = ChipHorizontalPadding,
-        top = ChipVerticalPadding,
-        end = ChipHorizontalPadding,
-        bottom = ChipVerticalPadding
+        start = HorizontalPadding,
+        top = VerticalPadding,
+        end = HorizontalPadding,
+        bottom = VerticalPadding
     )
 
     private val ColorScheme.defaultToggleButtonColors: ToggleButtonColors
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index 991acdb..bcf5a0e 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -41,7 +41,7 @@
     implementation(libs.kotlinStdlib)
     implementation("androidx.navigation:navigation-common:2.6.0")
     implementation("androidx.navigation:navigation-compose:2.6.0")
-    implementation("androidx.profileinstaller:profileinstaller:1.3.0")
+    implementation("androidx.profileinstaller:profileinstaller:1.3.1")
 
     androidTestImplementation(project(":compose:test-utils"))
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
diff --git a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
index 360df14..64a5666 100644
--- a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
+++ b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
@@ -19,6 +19,7 @@
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
@@ -94,7 +95,7 @@
 
         // Click to move to next destination then swipe to dismiss.
         rule.onNodeWithText(START).performClick()
-        rule.onNodeWithTag(TEST_TAG).performTouchInput({ swipeRight() })
+        rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
 
         // Should now display "start".
         rule.onNodeWithText(START).assertExists()
@@ -195,23 +196,28 @@
                     composable(START) {
                         screenId.value = START
                         var toggle by rememberSaveable { mutableStateOf(false) }
-                        Column {
-                            ToggleButton(
-                                checked = toggle,
-                                onCheckedChange = {
-                                    toggle = !toggle
-                                },
-                                content = { Text(text = if (toggle) "On" else "Off") },
-                                modifier = Modifier.testTag("ToggleButton"),
-                            )
-                            Button(
-                                onClick = { navController.navigate(NEXT) },
-                            ) {
-                                Text("Go")
+                        Box(
+                            modifier = Modifier.fillMaxSize(),
+                            contentAlignment = Alignment.Center
+                        ) {
+                            Column {
+                                ToggleButton(
+                                    checked = toggle,
+                                    onCheckedChange = {
+                                        toggle = !toggle
+                                    },
+                                    content = { Text(text = if (toggle) "On" else "Off") },
+                                    modifier = Modifier.testTag("ToggleButton"),
+                                )
+                                Button(
+                                    onClick = { navController.navigate(NEXT) },
+                                ) {
+                                    Text("Go")
+                                }
                             }
                         }
                     }
-                    composable("next") {
+                    composable(NEXT) {
                         screenId.value = NEXT
                         CompactChip(
                             onClick = {},
@@ -246,7 +252,9 @@
                         holder.SaveableStateProvider(START) {
                             var toggle by rememberSaveable { mutableStateOf(false) }
                             Column(
-                                modifier = Modifier.fillMaxSize().padding(horizontal = 20.dp),
+                                modifier = Modifier
+                                    .fillMaxSize()
+                                    .padding(horizontal = 20.dp),
                                 verticalArrangement = Arrangement.Center,
                                 horizontalAlignment = Alignment.CenterHorizontally
                             ) {
@@ -271,7 +279,9 @@
                         holder.SaveableStateProvider(NEXT) {
                             var counter by rememberSaveable { mutableStateOf(0) }
                             Column(
-                                modifier = Modifier.fillMaxSize().padding(horizontal = 20.dp),
+                                modifier = Modifier
+                                    .fillMaxSize()
+                                    .padding(horizontal = 20.dp),
                                 verticalArrangement = Arrangement.Center,
                                 horizontalAlignment = Alignment.CenterHorizontally
                             ) {
@@ -448,13 +458,23 @@
             userSwipeEnabled = userSwipeEnabled
         ) {
             composable(START) {
-                CompactChip(
-                    onClick = { navController.navigate(NEXT) },
-                    label = { Text(text = START) }
-                )
+                Box(
+                    modifier = Modifier.fillMaxSize(),
+                    contentAlignment = Alignment.Center
+                ) {
+                    CompactChip(
+                        onClick = { navController.navigate(NEXT) },
+                        label = { Text(text = START) }
+                    )
+                }
             }
             composable("next") {
-                Text(NEXT)
+                Box(
+                    modifier = Modifier.fillMaxSize(),
+                    contentAlignment = Alignment.Center
+                ) {
+                    Text(NEXT)
+                }
             }
         }
     }
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
index 7a0c478..9a3ef1a 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
@@ -58,7 +58,6 @@
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.focused
 import androidx.compose.ui.semantics.semantics
@@ -67,6 +66,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.wear.compose.integration.demos.common.rsbScroll
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.Icon
diff --git a/wear/compose/integration-tests/macrobenchmark/build.gradle b/wear/compose/integration-tests/macrobenchmark/build.gradle
index dd9f82f..1b3a97c 100644
--- a/wear/compose/integration-tests/macrobenchmark/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark/build.gradle
@@ -27,8 +27,6 @@
     namespace "androidx.wear.compose.integration.macrobenchmark"
     targetProjectPath = ":wear:compose:integration-tests:macrobenchmark-target"
     experimentalProperties["android.experimental.self-instrumenting"] = true
-
-    testOptions.animationsDisabled = false
 }
 
 // Create a release build type and make sure it's the only one enabled.
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/CompositionMetric.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/CompositionMetric.kt
index 784061a..5ea1fc5 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/CompositionMetric.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/CompositionMetric.kt
@@ -25,7 +25,7 @@
 @OptIn(ExperimentalMetricApi::class)
 internal class CompositionMetric(private val composable: String) : TraceMetric() {
     @OptIn(ExperimentalMetricApi::class, ExperimentalPerfettoTraceProcessorApi::class)
-    override fun getResult(
+    override fun getMeasurements(
         captureInfo: CaptureInfo,
         traceSession: PerfettoTraceProcessor.Session
     ): List<Measurement> {
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
index 7a52322..dce428c 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
@@ -66,7 +66,12 @@
             // Setting a gesture margin is important otherwise gesture nav is triggered.
             swipeToDismissBox.setGestureMargin(device.displayWidth / 5)
             repeat(3) {
-                swipeToDismissBox.swipe(Direction.RIGHT, 0.75f, SWIPE_SPEED)
+                swipeToDismissBox.swipe(Direction.RIGHT, 1f, SWIPE_SPEED)
+                // Sleeping the current thread for sometime before swiping again. This is required
+                // for cuttlefish_wear emulator as swipes are not completed when performed
+                // repeatedly. See b/328016250 for more details.
+                // TODO(b/329837878): Remove the sleep once infra improves
+                Thread.sleep(500)
                 device.waitForIdle()
             }
         }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java
index 72e0f87..b467aa7 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java
@@ -54,6 +54,11 @@
         @Override
         @UiThread
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic boolean node that gets value from the state. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicType.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicType.java
index b0babff..3a1cc92 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicType.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicType.java
@@ -64,4 +64,13 @@
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @VisibleForTesting
     int getDynamicNodeCount();
+
+    /**
+     * Returns the cost of dynamic nodes that this dynamic type contains. See {@link
+     * DynamicDataNode#getCost()} for more details on node cost.
+     */
+    @UiThread
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @VisibleForTesting
+    int getDynamicNodeCost();
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicTypeImpl.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicTypeImpl.java
index 4481752..33a7b2e 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicTypeImpl.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoundDynamicTypeImpl.java
@@ -84,6 +84,11 @@
     }
 
     @Override
+    public int getDynamicNodeCost() {
+        return mNodes.stream().mapToInt(DynamicDataNode::getCost).sum();
+    }
+
+    @Override
     public void close() {
         if (Looper.getMainLooper().isCurrentThread()) {
             closeInternal();
@@ -106,6 +111,6 @@
         mNodes.stream()
                 .filter(n -> n instanceof DynamicDataSourceNode)
                 .forEach(n -> ((DynamicDataSourceNode<?>) n).destroy());
-        mDynamicDataNodesQuotaManager.releaseQuota(getDynamicNodeCount());
+        mDynamicDataNodesQuotaManager.releaseQuota(getDynamicNodeCost());
     }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ColorNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ColorNodes.java
index c2a1d58..afc44fe 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ColorNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ColorNodes.java
@@ -35,8 +35,7 @@
         private final DynamicTypeValueReceiverWithPreUpdate<Integer> mDownstream;
 
         FixedColorNode(
-                FixedColor protoNode,
-                DynamicTypeValueReceiverWithPreUpdate<Integer> downstream) {
+                FixedColor protoNode, DynamicTypeValueReceiverWithPreUpdate<Integer> downstream) {
             this.mValue = protoNode.getArgb();
             this.mDownstream = downstream;
         }
@@ -55,6 +54,11 @@
 
         @Override
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic color node that gets value from the platform source. */
@@ -120,6 +124,11 @@
         public void destroy() {
             mQuotaAwareAnimator.stopAnimator();
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 
     /** Dynamic color node that gets animatable value from dynamic source. */
@@ -203,5 +212,10 @@
         public DynamicTypeValueReceiverWithPreUpdate<Integer> getInputCallback() {
             return mInputCallback;
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ConditionalOpNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ConditionalOpNode.java
index d0884d1..0817f67 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ConditionalOpNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/ConditionalOpNode.java
@@ -190,4 +190,9 @@
             mDownstream.onData(mLastFalseValue);
         }
     }
+
+    @Override
+    public int getCost() {
+        return DEFAULT_NODE_COST;
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DurationNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DurationNodes.java
index d59ecea..92e907f 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DurationNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DurationNodes.java
@@ -54,6 +54,11 @@
 
         @Override
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic duration node that gets the duration between two time instants. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
index 5bcb602..41d8206 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
@@ -76,6 +76,11 @@
         this.mTransformer = transformer;
     }
 
+    @Override
+    public int getCost() {
+        return DEFAULT_NODE_COST;
+    }
+
     private class UpstreamCallback<T> implements DynamicTypeValueReceiverWithPreUpdate<T> {
         private boolean mUpstreamPreUpdated = false;
 
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataNode.java
index d845acb..34e38f7 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataNode.java
@@ -16,6 +16,8 @@
 
 package androidx.wear.protolayout.expression.pipeline;
 
+import androidx.annotation.RestrictTo;
+
 /**
  * Node within a dynamic data pipeline.
  *
@@ -56,4 +58,17 @@
  *
  * @param <O> The data type that this node yields.
  */
-interface DynamicDataNode<O> {}
+interface DynamicDataNode<O> {
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    int DEFAULT_NODE_COST = 1;
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    int FIXED_NODE_COST = 0;
+
+    /**
+     * Returns the cost of this node. This value is used to estimate performance impact of a node.
+     * By default, most nodes have a cost of {@link DynamicDataNode#DEFAULT_NODE_COST}.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    int getCost();
+}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataSourceNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataSourceNode.java
index 441ee6d..1a8b456 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataSourceNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataSourceNode.java
@@ -28,8 +28,8 @@
 interface DynamicDataSourceNode<T> extends DynamicDataNode<T> {
     /**
      * Called on all source nodes before {@link DynamicDataSourceNode#init()} is called on any node.
-     * This should generally only call {@link
-     * DynamicTypeValueReceiverWithPreUpdate#onPreUpdate()} on all downstream nodes.
+     * This should generally only call {@link DynamicTypeValueReceiverWithPreUpdate#onPreUpdate()}
+     * on all downstream nodes.
      */
     @UiThread
     void preInit();
@@ -44,4 +44,9 @@
     /** Destroy this node. This should cause it to unbind from any data sources. */
     @UiThread
     void destroy();
+
+    @Override
+    default int getCost() {
+        return DEFAULT_NODE_COST;
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java
index 160d4bc..0024b26 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataTransformNode.java
@@ -75,4 +75,9 @@
     public DynamicTypeValueReceiverWithPreUpdate<I> getIncomingCallback() {
         return mCallback;
     }
+
+    @Override
+    public int getCost() {
+        return DEFAULT_NODE_COST;
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
index 8e73af5..6229642 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
@@ -117,9 +117,9 @@
                 public boolean tryAcquireQuota(int quota) {
                     return true;
                 }
+
                 @Override
-                public void releaseQuota(int quota) {
-                }
+                public void releaseQuota(int quota) {}
             };
 
     @NonNull
@@ -389,7 +389,7 @@
     public BoundDynamicType bind(@NonNull DynamicTypeBindingRequest request)
             throws EvaluationException {
         BoundDynamicTypeImpl boundDynamicType = request.callBindOn(this);
-        if (!mDynamicTypesQuotaManager.tryAcquireQuota(boundDynamicType.getDynamicNodeCount())) {
+        if (!mDynamicTypesQuotaManager.tryAcquireQuota(boundDynamicType.getDynamicNodeCost())) {
             throw new EvaluationException(
                     "Dynamic type expression limit reached. Try making the dynamic type expression"
                             + " shorter or reduce the number of dynamic type expressions.");
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
index 333eec4..9ec6b85 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
@@ -64,6 +64,11 @@
         @Override
         @UiThread
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic float node that gets value from the state. */
@@ -183,6 +188,11 @@
         public void destroy() {
             mQuotaAwareAnimator.stopAnimator();
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 
     /** Dynamic float node that gets animatable value from dynamic source. */
@@ -265,6 +275,11 @@
         public DynamicTypeValueReceiverWithPreUpdate<Float> getInputCallback() {
             return mInputCallback;
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 
     private static boolean isValid(Float value) {
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/InstantNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/InstantNodes.java
index c79546b..aa3a242 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/InstantNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/InstantNodes.java
@@ -53,6 +53,11 @@
 
         @Override
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic Instant node that gets value from the platform source. */
@@ -97,6 +102,11 @@
                 mEpochTimePlatformDataSource.unregisterForData(mDownstream);
             }
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 
     /** Dynamic Instant node that gets value from the state. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
index 64e8513..e32fd70 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
@@ -75,6 +75,11 @@
         @Override
         @UiThread
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic integer node that gets value from the platform source. */
@@ -288,6 +293,11 @@
         public void destroy() {
             mQuotaAwareAnimator.stopAnimator();
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 
     /** Dynamic int32 node that gets animatable value from dynamic source. */
@@ -370,6 +380,11 @@
         public DynamicTypeValueReceiverWithPreUpdate<Integer> getInputCallback() {
             return mInputCallback;
         }
+
+        @Override
+        public int getCost() {
+            return DEFAULT_NODE_COST;
+        }
     }
 
     /** Dynamic integer node that gets date-time part from a zoned date-time. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
index 98da705..d232317 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateSourceNode.java
@@ -104,4 +104,9 @@
 
         return new PlatformDataKey<T>(namespace, key);
     }
+
+    @Override
+    public int getCost() {
+        return DEFAULT_NODE_COST;
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StringNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StringNodes.java
index 1978b04..fa65764 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StringNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StringNodes.java
@@ -56,6 +56,11 @@
         @Override
         @UiThread
         public void destroy() {}
+
+        @Override
+        public int getCost() {
+            return FIXED_NODE_COST;
+        }
     }
 
     /** Dynamic string node that gets a value from integer. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
index dca1d9e..0abb008 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
@@ -28,6 +28,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.wear.protolayout.expression.AppDataKey;
 import androidx.wear.protolayout.expression.DynamicBuilders;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
 import androidx.wear.protolayout.expression.PlatformDataKey;
@@ -54,7 +55,7 @@
         ArrayList<Boolean> results = new ArrayList<>();
         DynamicTypeBindingRequest request = createSingleNodeDynamicBoolRequest(results);
         BoundDynamicType boundDynamicType = evaluator.bind(request);
-        assertThat(boundDynamicType.getDynamicNodeCount()).isEqualTo(1);
+        assertThat(boundDynamicType.getDynamicNodeCost()).isEqualTo(1);
     }
 
     @Test
@@ -114,7 +115,7 @@
         boundDynamicType1.close();
         // Retry binding request2
         BoundDynamicType boundDynamicType2 = evaluator.bind(request2);
-        assertThat(boundDynamicType2.getDynamicNodeCount()).isEqualTo(1);
+        assertThat(boundDynamicType2.getDynamicNodeCost()).isEqualTo(1);
     }
 
     @Test
@@ -155,10 +156,14 @@
     @NonNull
     private static DynamicTypeBindingRequest createSingleNodeDynamicBoolRequest(
             ArrayList<Boolean> results) {
+        return createDynamicBoolRequest(DynamicBool.from(new AppDataKey<>("key")), results);
+    }
+
+    @NonNull
+    private static DynamicTypeBindingRequest createDynamicBoolRequest(
+            DynamicBuilders.DynamicBool dynamicBool, ArrayList<Boolean> results) {
         return DynamicTypeBindingRequest.forDynamicBool(
-                DynamicBool.constant(false),
-                new MainThreadExecutor(),
-                new AddToListCallback<>(results));
+                dynamicBool, new MainThreadExecutor(), new AddToListCallback<>(results));
     }
 
     @NonNull
diff --git a/wear/protolayout/protolayout-expression/api/current.txt b/wear/protolayout/protolayout-expression/api/current.txt
index f309942..bc3520f9 100644
--- a/wear/protolayout/protolayout-expression/api/current.txt
+++ b/wear/protolayout/protolayout-expression/api/current.txt
@@ -61,7 +61,7 @@
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setReverseRepeatOverride(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
   }
 
-  public final class AppDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T> {
+  public final class AppDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T!> {
     ctor public AppDataKey(String);
   }
 
@@ -334,7 +334,7 @@
   @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface ExperimentalProtoLayoutExtensionApi {
   }
 
-  public final class PlatformDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T> {
+  public final class PlatformDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T!> {
     ctor public PlatformDataKey(String, String);
   }
 
@@ -389,7 +389,7 @@
   public final class VersionBuilders {
   }
 
-  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class VersionBuilders.VersionInfo implements java.lang.Comparable<androidx.wear.protolayout.expression.VersionBuilders.VersionInfo> {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class VersionBuilders.VersionInfo implements java.lang.Comparable<androidx.wear.protolayout.expression.VersionBuilders.VersionInfo!> {
     method public int compareTo(androidx.wear.protolayout.expression.VersionBuilders.VersionInfo);
     method public int getMajor();
     method public int getMinor();
diff --git a/wear/protolayout/protolayout-expression/api/restricted_current.txt b/wear/protolayout/protolayout-expression/api/restricted_current.txt
index f309942..bc3520f9 100644
--- a/wear/protolayout/protolayout-expression/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression/api/restricted_current.txt
@@ -61,7 +61,7 @@
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setReverseRepeatOverride(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
   }
 
-  public final class AppDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T> {
+  public final class AppDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T!> {
     ctor public AppDataKey(String);
   }
 
@@ -334,7 +334,7 @@
   @SuppressCompatibility @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface ExperimentalProtoLayoutExtensionApi {
   }
 
-  public final class PlatformDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T> {
+  public final class PlatformDataKey<T extends androidx.wear.protolayout.expression.DynamicBuilders.DynamicType> extends androidx.wear.protolayout.expression.DynamicDataKey<T!> {
     ctor public PlatformDataKey(String, String);
   }
 
@@ -389,7 +389,7 @@
   public final class VersionBuilders {
   }
 
-  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class VersionBuilders.VersionInfo implements java.lang.Comparable<androidx.wear.protolayout.expression.VersionBuilders.VersionInfo> {
+  @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class VersionBuilders.VersionInfo implements java.lang.Comparable<androidx.wear.protolayout.expression.VersionBuilders.VersionInfo!> {
     method public int compareTo(androidx.wear.protolayout.expression.VersionBuilders.VersionInfo);
     method public int getMajor();
     method public int getMinor();
diff --git a/wear/protolayout/protolayout-lint/build.gradle b/wear/protolayout/protolayout-lint/build.gradle
index dfb3ae5..32a2997 100644
--- a/wear/protolayout/protolayout-lint/build.gradle
+++ b/wear/protolayout/protolayout-lint/build.gradle
@@ -30,6 +30,7 @@
 
 dependencies {
     compileOnly(libs.androidLintApi)
+    compileOnly(libs.androidLintChecks)
     compileOnly(libs.kotlinStdlib)
 
     testImplementation(libs.kotlinStdlib)
diff --git a/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt b/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt
index d47fe85..44e2353 100644
--- a/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt
+++ b/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt
@@ -25,7 +25,12 @@
 class ProtoLayoutIssueRegistry : IssueRegistry() {
     override val api = 14
     override val minApi = CURRENT_API
-    override val issues = listOf(ProtoLayoutMinSchemaDetector.ISSUE)
+    override val issues =
+        listOf(
+            ProtoLayoutMinSchemaDetector.ISSUE,
+            ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE,
+            ResponsiveLayoutDetector.EDGE_CONTENT_LAYOUT_ISSUE
+        )
     override val vendor =
         Vendor(
             feedbackUrl = "https://issuetracker.google.com/issues/new?component=1112273",
diff --git a/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ResponsiveLayoutDetector.kt b/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ResponsiveLayoutDetector.kt
new file mode 100644
index 0000000..aab7991
--- /dev/null
+++ b/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ResponsiveLayoutDetector.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.lint
+
+import com.android.tools.lint.checks.DataFlowAnalyzer
+import com.android.tools.lint.client.api.JavaEvaluator
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.ConstantEvaluator
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.getParentOfType
+import org.jetbrains.uast.skipParenthesizedExprUp
+
+private const val PRIMARY_LAYOUT_BUILDER =
+    "androidx.wear.protolayout.material.layouts.PrimaryLayout.Builder"
+
+private const val EDGE_CONTENT_LAYOUT_BUILDER =
+    "androidx.wear.protolayout.material.layouts.EdgeContentLayout.Builder"
+
+private const val RESPONSIVE_SETTER_NAME = "setResponsiveContentInsetEnabled"
+
+// TODO(b/328785945): Improve edge cases like different scope for calls.
+class ResponsiveLayoutDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableConstructorTypes(): List<String> =
+        listOf(PRIMARY_LAYOUT_BUILDER, EDGE_CONTENT_LAYOUT_BUILDER)
+
+    override fun visitConstructor(
+        context: JavaContext,
+        node: UCallExpression,
+        constructor: PsiMethod
+    ) {
+        val containingClass = constructor.containingClass
+        val evaluator = context.evaluator
+        var issue: Issue? = null
+        var message = ""
+        var quickFix: LintFix? = null
+
+        if (isPrimaryLayout(evaluator, containingClass)) {
+            val potentialQuickFix = tryFindResponsiveSetter(context, node)
+            if (potentialQuickFix != null) {
+                issue = PRIMARY_LAYOUT_ISSUE
+                message =
+                    """
+                        PrimaryLayout used, but responsiveness isn't set: Please call
+                        `$RESPONSIVE_SETTER_NAME(true)` for the best results across
+                        different screen sizes.
+                    """
+                        .trimIndent()
+                quickFix = potentialQuickFix
+            }
+        }
+
+        if (isEdgeContentLayout(evaluator, containingClass)) {
+            val potentialQuickFix = tryFindResponsiveSetter(context, node)
+            if (potentialQuickFix != null) {
+                issue = EDGE_CONTENT_LAYOUT_ISSUE
+                message =
+                    """
+                            EdgeContentLayout used, but responsiveness isn't set: Please call
+                            `$RESPONSIVE_SETTER_NAME(true)` for the best results across
+                            different screen sizes and update to the looks of layout.
+                        """
+                        .trimIndent()
+                quickFix = potentialQuickFix
+            }
+        }
+
+        if (issue != null) {
+            context.report(
+                issue,
+                node,
+                context.getCallLocation(node, includeReceiver = true, includeArguments = false),
+                message,
+                quickFix,
+            )
+        }
+    }
+
+    private fun isEdgeContentLayout(evaluator: JavaEvaluator, containingClass: PsiClass?) =
+        evaluator.extendsClass(containingClass, EDGE_CONTENT_LAYOUT_BUILDER)
+
+    private fun isPrimaryLayout(evaluator: JavaEvaluator, containingClass: PsiClass?) =
+        evaluator.extendsClass(containingClass, PRIMARY_LAYOUT_BUILDER)
+
+    /** Tries to find a {@link #RESPONSIVE_SETTER_NAME} and provides a quick fix if not found. */
+    private fun tryFindResponsiveSetter(context: JavaContext, node: UCallExpression): LintFix? {
+        var foundResponsive = false
+        var foundFalseResponsive: UCallExpression? = null
+
+        val visitor =
+            object : DataFlowAnalyzer(setOf(node)) {
+                override fun receiver(call: UCallExpression) {
+                    if (call.methodName != RESPONSIVE_SETTER_NAME) {
+                        return
+                    }
+
+                    if (call.valueArgumentCount != 1) {
+                        return
+                    }
+
+                    val argValue = ConstantEvaluator.evaluate(context, call.valueArguments[0])
+
+                    if (argValue is Boolean && argValue) {
+                        // Found, everything is correct for now.
+                        foundResponsive = true
+                    } else {
+                        if (foundResponsive) {
+                            // We found a later call that called it with false, so the true
+                            // version will be overridden, which should still report.
+                            foundResponsive = false
+                        }
+                        // Since we found the wrong one, we need to provide a quick fix with
+                        // replacement value.
+                        foundFalseResponsive = call
+                    }
+                }
+            }
+
+        // Find a method/class/file (kt) where this expression is defined and visit all calls
+        // to see if responsiveness is called.
+        (node.getParentOfType(UCallExpression::class.java)
+                ?: node.getParentOfType(UMethod::class.java)
+                ?: node.getParentOfType(UClass::class.java)
+                ?: skipParenthesizedExprUp(node.uastParent))
+            ?.accept(visitor)
+
+        if (foundResponsive && foundFalseResponsive == null) {
+            return null
+        }
+
+        return if (foundFalseResponsive == null)
+            fix()
+                .replace()
+                .name("Call $RESPONSIVE_SETTER_NAME(true) on layouts")
+                .range(context.getLocation(node))
+                .end()
+                .with(".$RESPONSIVE_SETTER_NAME(true)")
+                .build()
+        else
+            fix()
+                .replace()
+                .name("Call $RESPONSIVE_SETTER_NAME(true) on layouts")
+                .range(
+                    context.getCallLocation(
+                        foundFalseResponsive!!,
+                        includeReceiver = false,
+                        includeArguments = true
+                    )
+                )
+                .pattern("(.*)")
+                .with("$RESPONSIVE_SETTER_NAME(true)")
+                .reformat(true)
+                .build()
+    }
+
+    companion object {
+        @JvmField
+        val PRIMARY_LAYOUT_ISSUE =
+            Issue.create(
+                id = "ProtoLayoutPrimaryLayoutResponsive",
+                briefDescription =
+                    "ProtoLayout Material PrimaryLayout should be used with responsive behaviour" +
+                        "to ensure the best behaviour across different screen sizes and locales.",
+                explanation =
+                    """
+            It is highly recommended to use the latest setResponsiveInsetEnabled(true) when you're
+            using the ProtoLayout's PrimaryLayout.
+
+            This is will take care of all inner padding to ensure that content of labels and bottom
+            chip doesn't go off the screen (especially with different locales).
+            """,
+                category = Category.CORRECTNESS,
+                priority = 5,
+                severity = Severity.WARNING,
+                androidSpecific = true,
+                implementation =
+                    Implementation(ResponsiveLayoutDetector::class.java, Scope.JAVA_FILE_SCOPE),
+            )
+
+        @JvmField
+        val EDGE_CONTENT_LAYOUT_ISSUE =
+            Issue.create(
+                id = "ProtoLayoutEdgeContentLayoutResponsive",
+                briefDescription =
+                    "ProtoLayout Material EdgeContentLayout should be used with responsive" +
+                        "behaviour to ensure the best behaviour across different screen sizes and" +
+                        "locales.",
+                explanation =
+                    """
+            It is highly recommended to use the latest setResponsiveInsetEnabled(true) when you're
+            using the ProtoLayout's EdgeContentLayout.
+
+            This is will take care of all outer margins and inner padding to ensure that content of
+            labels doesn't go off the screen (especially with different locales) and that primary
+            label is placed in the consistent place.
+            """,
+                category = Category.CORRECTNESS,
+                priority = 5,
+                severity = Severity.WARNING,
+                androidSpecific = true,
+                implementation =
+                    Implementation(ResponsiveLayoutDetector::class.java, Scope.JAVA_FILE_SCOPE),
+            )
+    }
+}
diff --git a/wear/protolayout/protolayout-lint/src/test/java/EdgeContentLayoutResponsiveDetectorTest.kt b/wear/protolayout/protolayout-lint/src/test/java/EdgeContentLayoutResponsiveDetectorTest.kt
new file mode 100644
index 0000000..a3f13a4
--- /dev/null
+++ b/wear/protolayout/protolayout-lint/src/test/java/EdgeContentLayoutResponsiveDetectorTest.kt
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.wear.protolayout.lint
+
+import androidx.wear.protolayout.lint.ResponsiveLayoutDetector.Companion.EDGE_CONTENT_LAYOUT_ISSUE
+import androidx.wear.protolayout.lint.ResponsiveLayoutDetector.Companion.PRIMARY_LAYOUT_ISSUE
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class EdgeContentLayoutResponsiveDetectorTest : LintDetectorTest() {
+    override fun getDetector() = ResponsiveLayoutDetector()
+
+    override fun getIssues() = mutableListOf(PRIMARY_LAYOUT_ISSUE, EDGE_CONTENT_LAYOUT_ISSUE)
+
+    private val deviceParametersStub =
+        java(
+            """
+                package androidx.wear.protolayout;
+                public class DeviceParameters {}
+            """
+                .trimIndent()
+        )
+
+    private val edgeContentLayoutStub =
+        java(
+            """
+                package androidx.wear.protolayout.material.layouts;
+
+                import androidx.wear.protolayout.DeviceParameters;
+
+                public class EdgeContentLayout {
+                    public static class Builder {
+                        public Builder(DeviceParameters deviceParameters) {}
+                        public Builder() {}
+
+                        public Builder setResponsiveContentInsetEnabled(boolean enabled) {
+                            return this;
+                        }
+
+                        public EdgeContentLayout build() {
+                            return new EdgeContentLayout();
+                        }
+                    }
+                }
+            """
+                .trimIndent()
+        )
+
+    @Test
+    fun `edgeContentLayout with responsiveness doesn't report`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                edgeContentLayoutStub,
+                kotlin(
+                    """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.EdgeContentLayout
+
+                        val layout = EdgeContentLayout.Builder(null)
+                                .setResponsiveContentInsetEnabled(true)
+                                .build()
+
+                        class Bar {
+                         val layout = EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .build()
+
+                            fun build() {
+                                val l = EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true)
+                                return l.build()
+                            }
+
+                            fun update() {
+                                return EdgeContentLayout.Builder()
+                                .setResponsiveContentInsetEnabled(true)
+                            }
+
+                            fun build2() {
+                                update().build()
+                            }
+
+                            fun callRandom() {
+                              random(EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(true))
+                            }
+                            fun random(val l: EdgeContentLayout.Builder) {}
+
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(EDGE_CONTENT_LAYOUT_ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun `edgeContentLayout without responsiveness requires and fixes setter`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                edgeContentLayoutStub,
+                kotlin(
+                        """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.EdgeContentLayout
+
+                        val layout = EdgeContentLayout.Builder(null)
+                                .setResponsiveContentInsetEnabled(false)
+                                .build()
+
+                        class Bar {
+                         val layout = EdgeContentLayout.Builder(null)
+                                .build()
+
+                         val layoutFalse = EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false)
+                                .build()
+
+                            fun buildFalse() {
+                                val l = EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false)
+                                return l.build()
+                            }
+
+                            fun update() {
+                                val enabled = false
+                                EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(enabled)
+                            }
+
+                            fun build() {
+                                update().build()
+                            }
+
+                            fun build2() {
+                                return EdgeContentLayout.Builder().build()
+                            }
+
+                            fun doubleFalse() {
+                                EdgeContentLayout.Builder()
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .setResponsiveContentInsetEnabled(false)
+                            }
+
+                            fun callRandom() {
+                              random(EdgeContentLayout.Builder())
+                            }
+                            fun random(val l: EdgeContentLayout.Builder) {}
+
+                            fun condition(val cond: Boolean) {
+                                val e = EdgeContentLayout.Builder()
+                                if (cond) {
+                                  e.setResponsiveContentInsetEnabled(false)
+                                } else {
+                                  e.setResponsiveContentInsetEnabled(true)
+                                }
+                            }
+                        }
+                    """
+                    )
+                    .indented()
+            )
+            // To confirm they are not mixed up.
+            .issues(EDGE_CONTENT_LAYOUT_ISSUE, PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expect(
+                """
+                    src/foo/Bar.kt:4: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                    val layout = EdgeContentLayout.Builder(null)
+                                 ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:9: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                     val layout = EdgeContentLayout.Builder(null)
+                                  ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:12: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                     val layoutFalse = EdgeContentLayout.Builder(null)
+                                       ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:17: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            val l = EdgeContentLayout.Builder(null)
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:24: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(enabled)
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:32: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            return EdgeContentLayout.Builder().build()
+                                   ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:36: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            EdgeContentLayout.Builder()
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:42: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                          random(EdgeContentLayout.Builder())
+                                 ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:47: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            val e = EdgeContentLayout.Builder()
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 9 warnings
+                """
+                    .trimIndent()
+            )
+            .expectFixDiffs(
+                """
+                    Fix for src/foo/Bar.kt line 4: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -5 +5
+                    -         .setResponsiveContentInsetEnabled(false)
+                    +         .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 9: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -9 +9
+                    -  val layout = EdgeContentLayout.Builder(null)
+                    +  val layout = EdgeContentLayout.Builder(null).setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 12: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -13 +13
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 17: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -18 +18
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 24: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -24 +24
+                    -         EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(enabled)
+                    +         EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 32: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -32 +32
+                    -         return EdgeContentLayout.Builder().build()
+                    +         return EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(true).build()
+                    Fix for src/foo/Bar.kt line 36: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -38 +38
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 42: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -42 +42
+                    -       random(EdgeContentLayout.Builder())
+                    +       random(EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(true))
+                    Fix for src/foo/Bar.kt line 47: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -49 +49
+                    -           e.setResponsiveContentInsetEnabled(false)
+                    +           e.setResponsiveContentInsetEnabled(true)
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun `edgeContentLayout with responsiveness doesn't report (Java)`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                edgeContentLayoutStub,
+                java(
+                    """
+                        package foo;
+                        import androidx.wear.protolayout.material.layouts.EdgeContentLayout;
+
+                        class Bar {
+                            EdgeContentLayout layout = new EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .build();
+
+                            EdgeContentLayout build() {
+                                EdgeContentLayout l = new EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true);
+                                return l.build();
+                            }
+
+                            EdgeContentLayout update() {
+                                return new EdgeContentLayout.Builder()
+                                    .setResponsiveContentInsetEnabled(true);
+                            }
+
+                            EdgeContentLayout build2() {
+                                update().build();
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(EDGE_CONTENT_LAYOUT_ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun `edgeContentLayout without responsiveness requires and fixes setter (Java)`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                edgeContentLayoutStub,
+                java(
+                    """
+                        package foo;
+                        import androidx.wear.protolayout.material.layouts.EdgeContentLayout;
+
+                        class Bar {
+                            EdgeContentLayout layout = new EdgeContentLayout.Builder(null)
+                                .build();
+
+                            EdgeContentLayout layoutFalse = new EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false)
+                                .build();
+
+                            EdgeContentLayout buildFalse() {
+                                EdgeContentLayout l = new EdgeContentLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false);
+                                return l.build();
+                            }
+
+                            void update() {
+                                boolean enabled = false;
+                                new EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(enabled);
+                            }
+
+                            void build() {
+                                update().build();
+                            }
+
+                            EdgeContentLayout build2() {
+                                return new EdgeContentLayout.Builder().build();
+                            }
+
+                            void doubleFalse() {
+                                new EdgeContentLayout.Builder()
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .setResponsiveContentInsetEnabled(false);
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(EDGE_CONTENT_LAYOUT_ISSUE)
+            .run()
+            .expect(
+                """
+                    src/foo/Bar.java:5: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                        EdgeContentLayout layout = new EdgeContentLayout.Builder(null)
+                                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:8: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                        EdgeContentLayout layoutFalse = new EdgeContentLayout.Builder(null)
+                                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:13: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            EdgeContentLayout l = new EdgeContentLayout.Builder(null)
+                                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:20: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            new EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(enabled);
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:28: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            return new EdgeContentLayout.Builder().build();
+                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:32: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                            new EdgeContentLayout.Builder()
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 6 warnings
+                """
+                    .trimIndent()
+            )
+            .expectFixDiffs(
+                """
+                    Fix for src/foo/Bar.java line 5: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -5 +5
+                    -     EdgeContentLayout layout = new EdgeContentLayout.Builder(null)
+                    +     EdgeContentLayout layout = new EdgeContentLayout.Builder(null).setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.java line 8: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -9 +9
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.java line 13: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -14 +14
+                    -             .setResponsiveContentInsetEnabled(false);
+                    +             .setResponsiveContentInsetEnabled(true);
+                    Fix for src/foo/Bar.java line 20: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -20 +20
+                    -         new EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(enabled);
+                    +         new EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(true);
+                    Fix for src/foo/Bar.java line 28: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -28 +28
+                    -         return new EdgeContentLayout.Builder().build();
+                    +         return new EdgeContentLayout.Builder().setResponsiveContentInsetEnabled(true).build();
+                    Fix for src/foo/Bar.java line 32: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -34 +34
+                    -             .setResponsiveContentInsetEnabled(false);
+                    +             .setResponsiveContentInsetEnabled(true);
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun `edgeContentLayout false report`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                edgeContentLayoutStub,
+                kotlin(
+                    """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.EdgeContentLayout
+
+
+                        fun doubleTrue() {
+                            EdgeContentLayout.Builder()
+                                .setResponsiveContentInsetEnabled(false)
+                                .setResponsiveContentInsetEnabled(true)
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(EDGE_CONTENT_LAYOUT_ISSUE)
+            .run()
+            .expect(
+                """
+                    src/foo/test.kt:6: Warning: EdgeContentLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes and update to the looks of layout. [ProtoLayoutEdgeContentLayoutResponsive]
+                        EdgeContentLayout.Builder()
+                        ~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """
+                    .trimIndent()
+            )
+            .expectFixDiffs(
+                """
+                    Fix for src/foo/test.kt line 6: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -7 +7
+                    -         .setResponsiveContentInsetEnabled(false)
+                    @@ -9 +8
+                    +         .setResponsiveContentInsetEnabled(true)
+                """
+                    .trimIndent()
+            )
+    }
+}
diff --git a/wear/protolayout/protolayout-lint/src/test/java/PrimaryLayoutResponsiveDetectorTest.kt b/wear/protolayout/protolayout-lint/src/test/java/PrimaryLayoutResponsiveDetectorTest.kt
new file mode 100644
index 0000000..7de1c93
--- /dev/null
+++ b/wear/protolayout/protolayout-lint/src/test/java/PrimaryLayoutResponsiveDetectorTest.kt
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.wear.protolayout.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PrimaryLayoutResponsiveDetectorTest : LintDetectorTest() {
+    override fun getDetector() = ResponsiveLayoutDetector()
+
+    override fun getIssues() = mutableListOf(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+
+    private val deviceParametersStub =
+        java(
+            """
+                package androidx.wear.protolayout;
+                public class DeviceParameters {}
+            """
+                .trimIndent()
+        )
+
+    private val primaryLayoutStub =
+        java(
+            """
+                package androidx.wear.protolayout.material.layouts;
+
+                import androidx.wear.protolayout.DeviceParameters;
+
+                public class PrimaryLayout {
+                    public static class Builder {
+                        public Builder(DeviceParameters deviceParameters) {}
+                        public Builder() {}
+
+                        public Builder setResponsiveContentInsetEnabled(boolean enabled) {
+                            return this;
+                        }
+
+                        public PrimaryLayout build() {
+                            return new PrimaryLayout();
+                        }
+                    }
+                }
+            """
+                .trimIndent()
+        )
+
+    @Test
+    fun `primaryLayout with responsiveness doesn't report`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                primaryLayoutStub,
+                kotlin(
+                    """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.PrimaryLayout
+
+                        val layout = PrimaryLayout.Builder(null)
+                                .setResponsiveContentInsetEnabled(true)
+                                .build()
+
+                        class Bar {
+                         val layout = PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .build()
+
+                            fun build() {
+                                val l = PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true)
+                                return l.build()
+                            }
+
+                            fun update() {
+                                return PrimaryLayout.Builder()
+                                .setResponsiveContentInsetEnabled(true)
+                            }
+
+                            fun build2() {
+                                update().build()
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun `primaryLayout without responsiveness requires and fixes setter`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                primaryLayoutStub,
+                kotlin(
+                        """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.PrimaryLayout
+
+                        val layout = PrimaryLayout.Builder(null)
+                                .setResponsiveContentInsetEnabled(false)
+                                .build()
+
+                        class Bar {
+                         val layout = PrimaryLayout.Builder(null)
+                                .build()
+
+                         val layoutFalse = PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false)
+                                .build()
+
+                            fun buildFalse() {
+                                val l = PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false)
+                                return l.build()
+                            }
+
+                            fun update() {
+                                val enabled = false
+                                PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled)
+                            }
+
+                            fun build() {
+                                update().build()
+                            }
+
+                            fun build2() {
+                                return PrimaryLayout.Builder().build()
+                            }
+
+                            fun doubleFalse() {
+                                PrimaryLayout.Builder()
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .setResponsiveContentInsetEnabled(false)
+                            }
+                        }
+                    """
+                    )
+                    .indented()
+            )
+            .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expect(
+                """
+                    src/foo/Bar.kt:4: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                    val layout = PrimaryLayout.Builder(null)
+                                 ~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:9: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                     val layout = PrimaryLayout.Builder(null)
+                                  ~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:12: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                     val layoutFalse = PrimaryLayout.Builder(null)
+                                       ~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:17: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            val l = PrimaryLayout.Builder(null)
+                                    ~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:24: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled)
+                            ~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:32: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            return PrimaryLayout.Builder().build()
+                                   ~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.kt:36: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            PrimaryLayout.Builder()
+                            ~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 7 warnings
+                """
+                    .trimIndent()
+            )
+            .expectFixDiffs(
+                """
+                    Fix for src/foo/Bar.kt line 4: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -5 +5
+                    -         .setResponsiveContentInsetEnabled(false)
+                    +         .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 9: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -9 +9
+                    -  val layout = PrimaryLayout.Builder(null)
+                    +  val layout = PrimaryLayout.Builder(null).setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 12: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -13 +13
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 17: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -18 +18
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 24: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -24 +24
+                    -         PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled)
+                    +         PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.kt line 32: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -32 +32
+                    -         return PrimaryLayout.Builder().build()
+                    +         return PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true).build()
+                    Fix for src/foo/Bar.kt line 36: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -38 +38
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun `primaryLayout with responsiveness doesn't (Java) report`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                primaryLayoutStub,
+                java(
+                    """
+                        package foo;
+                        import androidx.wear.protolayout.material.layouts.PrimaryLayout;
+
+                        class Bar {
+                            PrimaryLayout layout = new PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .build();
+
+                            PrimaryLayout build() {
+                                PrimaryLayout l = new PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(true);
+                                return l.build();
+                            }
+
+                            PrimaryLayout update() {
+                                return new PrimaryLayout.Builder()
+                                    .setResponsiveContentInsetEnabled(true);
+                            }
+
+                            PrimaryLayout build2() {
+                                update().build();
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun `primaryLayout without responsiveness requires and fixes setter (Java)`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                primaryLayoutStub,
+                java(
+                    """
+                        package foo;
+                        import androidx.wear.protolayout.material.layouts.PrimaryLayout;
+
+                        class Bar {
+                            PrimaryLayout layout = new PrimaryLayout.Builder(null)
+                                .build();
+
+                            PrimaryLayout layoutFalse = new PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false)
+                                .build();
+
+                            PrimaryLayout buildFalse() {
+                                PrimaryLayout l = new PrimaryLayout.Builder(null)
+                                    .setResponsiveContentInsetEnabled(false);
+                                return l.build();
+                            }
+
+                            void update() {
+                                boolean enabled = false;
+                                new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled);
+                            }
+
+                            void build() {
+                                update().build();
+                            }
+
+                            PrimaryLayout build2() {
+                                return new PrimaryLayout.Builder().build();
+                            }
+
+                            void doubleFalse() {
+                                new PrimaryLayout.Builder()
+                                    .setResponsiveContentInsetEnabled(true)
+                                    .setResponsiveContentInsetEnabled(false);
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expect(
+                """
+                    src/foo/Bar.java:5: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                        PrimaryLayout layout = new PrimaryLayout.Builder(null)
+                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:8: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                        PrimaryLayout layoutFalse = new PrimaryLayout.Builder(null)
+                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:13: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            PrimaryLayout l = new PrimaryLayout.Builder(null)
+                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:20: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled);
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:28: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            return new PrimaryLayout.Builder().build();
+                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/foo/Bar.java:32: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                            new PrimaryLayout.Builder()
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 6 warnings
+                """
+                    .trimIndent()
+            )
+            .expectFixDiffs(
+                """
+                    Fix for src/foo/Bar.java line 5: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -5 +5
+                    -     PrimaryLayout layout = new PrimaryLayout.Builder(null)
+                    +     PrimaryLayout layout = new PrimaryLayout.Builder(null).setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.java line 8: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -9 +9
+                    -             .setResponsiveContentInsetEnabled(false)
+                    +             .setResponsiveContentInsetEnabled(true)
+                    Fix for src/foo/Bar.java line 13: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -14 +14
+                    -             .setResponsiveContentInsetEnabled(false);
+                    +             .setResponsiveContentInsetEnabled(true);
+                    Fix for src/foo/Bar.java line 20: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -20 +20
+                    -         new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(enabled);
+                    +         new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true);
+                    Fix for src/foo/Bar.java line 28: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -28 +28
+                    -         return new PrimaryLayout.Builder().build();
+                    +         return new PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true).build();
+                    Fix for src/foo/Bar.java line 32: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -34 +34
+                    -             .setResponsiveContentInsetEnabled(false);
+                    +             .setResponsiveContentInsetEnabled(true);
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun `primaryLayout with responsiveness false positive reports`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                primaryLayoutStub,
+                kotlin(
+                    """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.PrimaryLayout
+
+                        class Bar {
+                            fun create(): PrimaryLayout.Builder {
+                              return PrimaryLayout.Builder()
+                            }
+                            fun build() {
+                              create().setResponsiveContentInsetEnabled(true).build()
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expect(
+                """
+                    src/foo/Bar.kt:6: Warning: PrimaryLayout used, but responsiveness isn't set: Please call
+                    setResponsiveContentInsetEnabled(true) for the best results across
+                    different screen sizes. [ProtoLayoutPrimaryLayoutResponsive]
+                          return PrimaryLayout.Builder()
+                                 ~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """
+                    .trimIndent()
+            )
+            .expectFixDiffs(
+                """
+                    Fix for src/foo/Bar.kt line 6: Call setResponsiveContentInsetEnabled(true) on layouts:
+                    @@ -6 +6
+                    -       return PrimaryLayout.Builder()
+                    +       return PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true)
+                """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun `primaryLayout with responsiveness false doesn't report`() {
+        lint()
+            .files(
+                deviceParametersStub,
+                primaryLayoutStub,
+                kotlin(
+                    """
+                        package foo
+                        import androidx.wear.protolayout.material.layouts.PrimaryLayout
+
+                        class Bar {
+                            fun create(): PrimaryLayout.Builder {
+                              return PrimaryLayout.Builder().setResponsiveContentInsetEnabled(true)
+                            }
+                            fun build() {
+                              create().setResponsiveContentInsetEnabled(false).build()
+                            }
+                        }
+                    """
+                        .trimIndent()
+                )
+            )
+            .issues(ResponsiveLayoutDetector.PRIMARY_LAYOUT_ISSUE)
+            .run()
+            .expectClean()
+    }
+}
diff --git a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/Chip.java b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/Chip.java
index 2f2a5d5..97cb0f0 100644
--- a/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/Chip.java
+++ b/wear/protolayout/protolayout-material-core/src/main/java/androidx/wear/protolayout/materialcore/Chip.java
@@ -345,8 +345,15 @@
             if (mWidth instanceof DpProp) {
                 return dp(max(((DpProp) mWidth).getValue(), mMinTappableSquareLength.getValue()));
             } else if (mWidth instanceof WrappedDimensionProp) {
+                WrappedDimensionProp widthWrap = ((WrappedDimensionProp) mWidth);
                 return new WrappedDimensionProp.Builder()
-                        .setMinimumSize(mMinTappableSquareLength)
+                        .setMinimumSize(
+                                dp(
+                                        max(
+                                                widthWrap.getMinimumSize() != null
+                                                        ? widthWrap.getMinimumSize().getValue()
+                                                        : 0,
+                                                mMinTappableSquareLength.getValue())))
                         .build();
             } else {
                 return mWidth;
@@ -389,6 +396,13 @@
                 return mCustomContent;
             }
 
+            if (mPrimaryLabelContent == null
+                    && mSecondaryLabelContent == null
+                    && mIconContent != null) {
+                // Icon only variant of chip.
+                return mIconContent;
+            }
+
             Column.Builder column =
                     new Column.Builder()
                             .setHorizontalAlignment(HORIZONTAL_ALIGN_START)
@@ -483,7 +497,12 @@
         if (!getMetadataTag().equals(METADATA_TAG_ICON)) {
             return null;
         }
-        return ((Row) mElement.getContents().get(0)).getContents().get(0);
+        // TODO(b/330165026): Refactor to use bit in the metadata tag like layouts do, instead of
+        // relying on the null here. The primary label can be null in case of icon only CompactChip.
+        LayoutElement topLevel = mElement.getContents().get(0);
+        return topLevel instanceof Row
+                ? ((Row) mElement.getContents().get(0)).getContents().get(0)
+                : topLevel;
     }
 
     @Nullable
@@ -496,6 +515,11 @@
         // In any other case, text (either primary or primary + label) must be present.
         Column content;
         if (metadataTag.equals(METADATA_TAG_ICON)) {
+            if (!(mElement.getContents().get(0) instanceof Row)) {
+                // This is icon only Chip, no label.
+                return null;
+            }
+
             content =
                     (Column)
                             ((Box)
diff --git a/wear/protolayout/protolayout-material/api/current.txt b/wear/protolayout/protolayout-material/api/current.txt
index 38c33fb..fda934c 100644
--- a/wear/protolayout/protolayout-material/api/current.txt
+++ b/wear/protolayout/protolayout-material/api/current.txt
@@ -138,15 +138,20 @@
     method public static androidx.wear.protolayout.material.CompactChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
     method public androidx.wear.protolayout.material.ChipColors getChipColors();
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+    method public androidx.wear.protolayout.TypeBuilders.StringProp? getContentDescription();
     method public String? getIconContent();
     method public String getText();
   }
 
   public static final class CompactChip.Builder {
+    ctor public CompactChip.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.CompactChip build();
     method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method public androidx.wear.protolayout.material.CompactChip.Builder setContentDescription(androidx.wear.protolayout.TypeBuilders.StringProp);
+    method public androidx.wear.protolayout.material.CompactChip.Builder setContentDescription(CharSequence);
     method public androidx.wear.protolayout.material.CompactChip.Builder setIconContent(String);
+    method public androidx.wear.protolayout.material.CompactChip.Builder setTextContent(String);
   }
 
   public class ProgressIndicatorColors {
@@ -198,6 +203,7 @@
     method public static androidx.wear.protolayout.material.TitleChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
     method public androidx.wear.protolayout.material.ChipColors getChipColors();
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+    method public androidx.wear.protolayout.TypeBuilders.StringProp? getContentDescription();
     method public int getHorizontalAlignment();
     method public String? getIconContent();
     method public String getText();
@@ -208,6 +214,8 @@
     ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.TitleChip build();
     method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method public androidx.wear.protolayout.material.TitleChip.Builder setContentDescription(androidx.wear.protolayout.TypeBuilders.StringProp);
+    method public androidx.wear.protolayout.material.TitleChip.Builder setContentDescription(CharSequence);
     method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
     method public androidx.wear.protolayout.material.TitleChip.Builder setIconContent(String);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
diff --git a/wear/protolayout/protolayout-material/api/restricted_current.txt b/wear/protolayout/protolayout-material/api/restricted_current.txt
index 38c33fb..fda934c 100644
--- a/wear/protolayout/protolayout-material/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material/api/restricted_current.txt
@@ -138,15 +138,20 @@
     method public static androidx.wear.protolayout.material.CompactChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
     method public androidx.wear.protolayout.material.ChipColors getChipColors();
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+    method public androidx.wear.protolayout.TypeBuilders.StringProp? getContentDescription();
     method public String? getIconContent();
     method public String getText();
   }
 
   public static final class CompactChip.Builder {
+    ctor public CompactChip.Builder(android.content.Context, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.CompactChip build();
     method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method public androidx.wear.protolayout.material.CompactChip.Builder setContentDescription(androidx.wear.protolayout.TypeBuilders.StringProp);
+    method public androidx.wear.protolayout.material.CompactChip.Builder setContentDescription(CharSequence);
     method public androidx.wear.protolayout.material.CompactChip.Builder setIconContent(String);
+    method public androidx.wear.protolayout.material.CompactChip.Builder setTextContent(String);
   }
 
   public class ProgressIndicatorColors {
@@ -198,6 +203,7 @@
     method public static androidx.wear.protolayout.material.TitleChip? fromLayoutElement(androidx.wear.protolayout.LayoutElementBuilders.LayoutElement);
     method public androidx.wear.protolayout.material.ChipColors getChipColors();
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
+    method public androidx.wear.protolayout.TypeBuilders.StringProp? getContentDescription();
     method public int getHorizontalAlignment();
     method public String? getIconContent();
     method public String getText();
@@ -208,6 +214,8 @@
     ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.TitleChip build();
     method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
+    method public androidx.wear.protolayout.material.TitleChip.Builder setContentDescription(androidx.wear.protolayout.TypeBuilders.StringProp);
+    method public androidx.wear.protolayout.material.TitleChip.Builder setContentDescription(CharSequence);
     method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
     method public androidx.wear.protolayout.material.TitleChip.Builder setIconContent(String);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
diff --git a/wear/protolayout/protolayout-material/build.gradle b/wear/protolayout/protolayout-material/build.gradle
index 8215bb4..1e5c30f 100644
--- a/wear/protolayout/protolayout-material/build.gradle
+++ b/wear/protolayout/protolayout-material/build.gradle
@@ -38,6 +38,9 @@
     implementation(project(path: ":wear:protolayout:protolayout-proto", configuration: "shadow"))
     implementation("androidx.annotation:annotation-experimental:1.4.0")
 
+    lintChecks(project(":wear:protolayout:protolayout-lint"))
+    lintPublish(project(":wear:protolayout:protolayout-lint"))
+
 
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testCore)
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
index 92ec2f7..a5c592e 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
@@ -218,6 +218,12 @@
                         .build());
         testCases.put(
                 "compactchip_custom_default_golden" + goldenSuffix,
+                new CompactChip.Builder(context, clickable, deviceParameters)
+                        .setTextContent("Action")
+                        .setChipColors(new ChipColors(Color.YELLOW, Color.BLACK))
+                        .build());
+        testCases.put(
+                "compactchip_custom_default_deprecated_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Action", clickable, deviceParameters)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLACK))
                         .build());
@@ -243,6 +249,11 @@
                         .setIconContent(ICON_ID_SMALL)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLACK))
                         .build());
+        testCases.put(
+                "compactchip_icon_only_golden" + goldenSuffix,
+                new CompactChip.Builder(context, clickable, deviceParameters)
+                        .setIconContent(ICON_ID_SMALL)
+                        .build());
 
         testCases.put(
                 "titlechip_default_golden" + goldenSuffix,
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
index c0854ac..2b144b2 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
@@ -103,6 +103,7 @@
         @NonNull private final Context mContext;
         @Nullable private LayoutElement mCustomContent;
         @Nullable private String mImageResourceId = null;
+        private boolean mIsIconOnly = false;
         @Nullable private String mPrimaryLabel = null;
         @Nullable private String mSecondaryLabel = null;
         @Nullable private StringProp mContentDescription = null;
@@ -201,6 +202,7 @@
         @NonNull
         public Builder setPrimaryLabelContent(@NonNull String primaryLabel) {
             this.mPrimaryLabel = primaryLabel;
+            this.mIsIconOnly = false;
             this.mCustomContent = null;
             return this;
         }
@@ -246,6 +248,7 @@
         @NonNull
         public Builder setSecondaryLabelContent(@NonNull String secondaryLabel) {
             this.mSecondaryLabel = secondaryLabel;
+            this.mIsIconOnly = false;
             this.mCustomContent = null;
             return this;
         }
@@ -264,6 +267,20 @@
         }
 
         /**
+         * Sets the content of the {@link Chip} to be *only* icon. Any previously added custom
+         * content will be overridden. Provided icon will be tinted to the given content color from
+         * {@link ChipColors}. This icon should be image with chosen alpha channel and not an actual
+         * image. Should be used only for creating a {@link CompactChip}.
+         */
+        @NonNull
+        Builder setIconOnlyContent(@NonNull String imageResourceId) {
+            this.mImageResourceId = imageResourceId;
+            this.mIsIconOnly = true;
+            this.mCustomContent = null;
+            return this;
+        }
+
+        /**
          * Sets the colors for the {@link Chip}. If set, {@link ChipColors#getBackgroundColor()}
          * will be used for the background of the button, {@link ChipColors#getContentColor()} for
          * main text, {@link ChipColors#getSecondaryContentColor()} for label text and {@link
@@ -356,6 +373,24 @@
         @OptIn(markerClass = ProtoLayoutExperimental.class)
         @SuppressWarnings("deprecation")
         private void setCorrectContent() {
+            if (mImageResourceId != null) {
+                Image icon =
+                        new Image.Builder()
+                                .setResourceId(mImageResourceId)
+                                .setWidth(mIconSize)
+                                .setHeight(mIconSize)
+                                .setColorFilter(
+                                        new ColorFilter.Builder()
+                                                .setTint(mChipColors.getIconColor())
+                                                .build())
+                                .build();
+                mCoreBuilder.setIconContent(icon);
+
+                if (mIsIconOnly) {
+                    return;
+                }
+            }
+
             Text mainTextElement =
                     new Text.Builder(mContext, checkNotNull(mPrimaryLabel))
                             .setTypography(mPrimaryLabelTypography)
@@ -379,20 +414,6 @@
                                 .build();
                 mCoreBuilder.setSecondaryLabelContent(labelTextElement);
             }
-
-            if (mImageResourceId != null) {
-                Image icon =
-                        new Image.Builder()
-                                .setResourceId(mImageResourceId)
-                                .setWidth(mIconSize)
-                                .setHeight(mIconSize)
-                                .setColorFilter(
-                                        new ColorFilter.Builder()
-                                                .setTint(mChipColors.getIconColor())
-                                                .build())
-                                .build();
-                mCoreBuilder.setIconContent(icon);
-            }
         }
 
         private int getCorrectMaxLines() {
@@ -435,10 +456,14 @@
                 iconTintColor = checkNotNull(checkNotNull(icon.getColorFilter()).getTint());
             }
 
-            contentColor = checkNotNull(getPrimaryLabelContentObject()).getColor();
-            Text label = getSecondaryLabelContentObject();
-            if (label != null) {
-                secondaryContentColor = label.getColor();
+
+            Text maybePrimaryLabel = getPrimaryLabelContentObject();
+            if (maybePrimaryLabel != null) {
+                contentColor = checkNotNull(maybePrimaryLabel).getColor();
+                Text label = getSecondaryLabelContentObject();
+                if (label != null) {
+                    secondaryContentColor = label.getColor();
+                }
             }
         }
 
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
index d5cfbe1..0441297 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/ChipDefaults.java
@@ -37,6 +37,11 @@
     @NonNull
     public static final DpProp COMPACT_HEIGHT = dp(32);
 
+    /** The default minimum width for standard {@link CompactChip} */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public static final DpProp COMPACT_MIN_WIDTH = dp(52);
+
     /** The minimum size of tappable target area. */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
index 9787fa6..40ac894 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
@@ -16,14 +16,17 @@
 
 package androidx.wear.protolayout.material;
 
+import static androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp;
 import static androidx.wear.protolayout.DimensionBuilders.wrap;
 import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER;
 import static androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_START;
 import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_HEIGHT;
 import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_HORIZONTAL_PADDING;
 import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_ICON_SIZE;
+import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_MIN_WIDTH;
 import static androidx.wear.protolayout.material.ChipDefaults.COMPACT_PRIMARY_COLORS;
 import static androidx.wear.protolayout.materialcore.Helper.checkNotNull;
+import static androidx.wear.protolayout.materialcore.Helper.staticString;
 
 import android.content.Context;
 
@@ -35,6 +38,7 @@
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
 import androidx.wear.protolayout.ModifiersBuilders.Clickable;
+import androidx.wear.protolayout.TypeBuilders.StringProp;
 import androidx.wear.protolayout.expression.Fingerprint;
 import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.proto.LayoutElementProto;
@@ -79,14 +83,15 @@
     /** Builder class for {@link androidx.wear.protolayout.material.CompactChip}. */
     public static final class Builder implements LayoutElement.Builder {
         @NonNull private final Context mContext;
-        @NonNull private final String mText;
+        @Nullable private String mText;
         @NonNull private final Clickable mClickable;
         @NonNull private final DeviceParameters mDeviceParameters;
         @NonNull private ChipColors mChipColors = COMPACT_PRIMARY_COLORS;
         @Nullable private String mIconResourceId = null;
+        @Nullable private StringProp mContentDescription = null;
 
         /**
-         * Creates a builder for the {@link CompactChip} with associated action and the given text
+         * Creates a builder for the {@link CompactChip} with associated action and the given text.
          *
          * @param context The application's context.
          * @param text The text to be displayed in this compact chip.
@@ -106,6 +111,32 @@
         }
 
         /**
+         * Creates a builder for the {@link CompactChip} with associated action. Please add text,
+         * icon or both content with {@link #setTextContent} and {@link #setIconContent}.
+         *
+         * @param context The application's context.
+         * @param clickable Associated {@link Clickable} for click events. When the CompactChip is
+         *     clicked it will fire the associated action.
+         * @param deviceParameters The device parameters used for styling text.
+         */
+        public Builder(
+                @NonNull Context context,
+                @NonNull Clickable clickable,
+                @NonNull DeviceParameters deviceParameters) {
+            this.mContext = context;
+            this.mClickable = clickable;
+            this.mDeviceParameters = deviceParameters;
+        }
+
+        /** Sets the text for the {@link CompactChip}. */
+        @SuppressWarnings("MissingGetterMatchingBuilder") // Exists as getText
+        @NonNull
+        public Builder setTextContent(@NonNull String text) {
+            this.mText = text;
+            return this;
+        }
+
+        /**
          * Sets the colors for the {@link CompactChip}. If set, {@link
          * ChipColors#getBackgroundColor()} will be used for the background of the button and {@link
          * ChipColors#getContentColor()} for the text. If not set, {@link
@@ -128,33 +159,82 @@
             return this;
         }
 
+        /**
+         * Sets the static content description for the {@link CompactChip}. It is highly recommended
+         * to provide this for chip containing an icon.
+         */
+        @NonNull
+        public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+            return setContentDescription(staticString(contentDescription.toString()));
+        }
+
+        /**
+         * Sets the content description for the {@link CompactChip}. It is highly recommended to
+         * provide this for chip containing an icon.
+         *
+         * <p>While this field is statically accessible from 1.0, it's only bindable since version
+         * 1.2 and renderers supporting version 1.2 will use the dynamic value (if set).
+         */
+        @NonNull
+        public Builder setContentDescription(@NonNull StringProp contentDescription) {
+            this.mContentDescription = contentDescription;
+            return this;
+        }
+
+
         /** Constructs and returns {@link CompactChip} with the provided content and look. */
         @NonNull
         @Override
         @OptIn(markerClass = ProtoLayoutExperimental.class)
         public CompactChip build() {
+            if (mText == null && mIconResourceId == null) {
+                throw new IllegalArgumentException("At least one of text or icon must be set.");
+            }
+
             Chip.Builder chipBuilder =
                     new Chip.Builder(mContext, mClickable, mDeviceParameters)
                             .setChipColors(mChipColors)
-                            .setContentDescription(mText)
+                            .setContentDescription(
+                                    mContentDescription == null
+                                            ? staticString(mText == null ? "" : mText)
+                                            : mContentDescription)
                             .setHorizontalAlignment(getCorrectHorizontalAlignment())
-                            .setWidth(wrap())
+                            .setWidth(resolveWidth())
                             .setHeight(COMPACT_HEIGHT)
                             .setMaxLines(1)
-                            .setHorizontalPadding(COMPACT_HORIZONTAL_PADDING)
-                            .setPrimaryLabelContent(mText)
-                            .setPrimaryLabelTypography(Typography.TYPOGRAPHY_CAPTION1)
-                            .setIsPrimaryLabelScalable(false);
+                            .setHorizontalPadding(COMPACT_HORIZONTAL_PADDING);
+
+            if (mText != null) {
+                chipBuilder
+                        .setPrimaryLabelContent(mText)
+                        .setPrimaryLabelTypography(Typography.TYPOGRAPHY_CAPTION1)
+                        .setIsPrimaryLabelScalable(false);
+            }
 
             if (mIconResourceId != null) {
-                chipBuilder.setIconContent(mIconResourceId).setIconSize(COMPACT_ICON_SIZE);
+                if (mText != null) {
+                    chipBuilder.setIconContent(mIconResourceId);
+                } else {
+                    chipBuilder.setIconOnlyContent(mIconResourceId);
+                }
+                chipBuilder.setIconSize(COMPACT_ICON_SIZE);
             }
 
             return new CompactChip(chipBuilder.build());
         }
 
+        private WrappedDimensionProp resolveWidth() {
+            // Min width applies to icon only CompactChip.
+            return mText == null
+                    // Icon only CompactChip.
+                    ? new WrappedDimensionProp.Builder().setMinimumSize(COMPACT_MIN_WIDTH).build()
+                    : wrap();
+        }
+
         private int getCorrectHorizontalAlignment() {
-            return mIconResourceId == null ? HORIZONTAL_ALIGN_CENTER : HORIZONTAL_ALIGN_START;
+            return mIconResourceId == null || mText == null
+                    ? HORIZONTAL_ALIGN_CENTER
+                    : HORIZONTAL_ALIGN_START;
         }
     }
 
@@ -170,7 +250,12 @@
         return mElement.getChipColors();
     }
 
-    /** Returns text content of this Chip. */
+    /**
+     * Returns text content of this Chip if it was set. If the text content wasn't set (either with
+     * {@link Builder#setTextContent} or constructor, this method will throw.
+     *
+     * @throws NullPointerException when no text content was set to the chip.
+     */
     @NonNull
     public String getText() {
         return checkNotNull(mElement.getPrimaryLabelContent());
@@ -203,6 +288,12 @@
         return coreChip == null ? null : new CompactChip(new Chip(coreChip));
     }
 
+    /** Returns content description of this CompactChip. */
+    @Nullable
+    public StringProp getContentDescription() {
+        return mElement.getContentDescription();
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
index 25de00b..92140a6 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
@@ -24,6 +24,7 @@
 import static androidx.wear.protolayout.material.ChipDefaults.TITLE_HORIZONTAL_PADDING;
 import static androidx.wear.protolayout.material.ChipDefaults.TITLE_PRIMARY_COLORS;
 import static androidx.wear.protolayout.materialcore.Helper.checkNotNull;
+import static androidx.wear.protolayout.materialcore.Helper.staticString;
 
 import android.content.Context;
 
@@ -38,6 +39,7 @@
 import androidx.wear.protolayout.LayoutElementBuilders.HorizontalAlignment;
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
 import androidx.wear.protolayout.ModifiersBuilders.Clickable;
+import androidx.wear.protolayout.TypeBuilders.StringProp;
 import androidx.wear.protolayout.expression.Fingerprint;
 import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.proto.LayoutElementProto;
@@ -89,6 +91,7 @@
         @NonNull private final DeviceParameters mDeviceParameters;
         @NonNull private ChipColors mChipColors = TITLE_PRIMARY_COLORS;
         @HorizontalAlignment private int mHorizontalAlign = HORIZONTAL_ALIGN_UNDEFINED;
+        @Nullable private StringProp mContentDescription = null;
 
         // Indicates that the width isn't set, so it will be automatically set by Chip.Builder
         // constructor.
@@ -166,6 +169,28 @@
             return this;
         }
 
+        /**
+         * Sets the static content description for the {@link TitleChip}. It is highly recommended
+         * to provide this for chip containing an icon.
+         */
+        @NonNull
+        public Builder setContentDescription(@NonNull CharSequence contentDescription) {
+            return setContentDescription(staticString(contentDescription.toString()));
+        }
+
+        /**
+         * Sets the content description for the {@link TitleChip}. It is highly recommended to
+         * provide this for chip containing an icon.
+         *
+         * <p>While this field is statically accessible from 1.0, it's only bindable since version
+         * 1.2 and renderers supporting version 1.2 will use the dynamic value (if set).
+         */
+        @NonNull
+        public Builder setContentDescription(@NonNull StringProp contentDescription) {
+            this.mContentDescription = contentDescription;
+            return this;
+        }
+
         /** Constructs and returns {@link TitleChip} with the provided content and look. */
         @NonNull
         @Override
@@ -174,7 +199,10 @@
             Chip.Builder chipBuilder =
                     new Chip.Builder(mContext, mClickable, mDeviceParameters)
                             .setChipColors(mChipColors)
-                            .setContentDescription(mText)
+                            .setContentDescription(
+                                    mContentDescription == null
+                                            ? staticString(mText)
+                                            : mContentDescription)
                             .setHeight(TITLE_HEIGHT)
                             .setMaxLines(1)
                             .setHorizontalPadding(TITLE_HORIZONTAL_PADDING)
@@ -255,6 +283,12 @@
         return coreChip == null ? null : new TitleChip(new Chip(coreChip));
     }
 
+    /** Returns content description of this TitleChip. */
+    @Nullable
+    public StringProp getContentDescription() {
+        return mElement.getContentDescription();
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/CompactChipTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/CompactChipTest.java
index 173cce2..81025a7 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/CompactChipTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/CompactChipTest.java
@@ -22,15 +22,19 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.content.Context;
 import android.graphics.Color;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.protolayout.ActionBuilders.LaunchAction;
+import androidx.wear.protolayout.ColorBuilders.ColorProp;
 import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters;
 import androidx.wear.protolayout.LayoutElementBuilders.Box;
 import androidx.wear.protolayout.LayoutElementBuilders.Column;
@@ -59,31 +63,64 @@
     @Test
     public void testCompactChipDefault() {
         CompactChip compactChip =
-                new CompactChip.Builder(CONTEXT, MAIN_TEXT, CLICKABLE, DEVICE_PARAMETERS).build();
+                new CompactChip.Builder(CONTEXT, CLICKABLE, DEVICE_PARAMETERS)
+                        .setTextContent(MAIN_TEXT)
+                        .build();
 
         assertChip(compactChip, ChipDefaults.COMPACT_PRIMARY_COLORS, /* iconId= */ null);
+        assertThat(compactChip.getText()).isEqualTo(MAIN_TEXT);
     }
 
     @Test
     public void testCompactChipCustomColor() {
         CompactChip compactChip =
-                new CompactChip.Builder(CONTEXT, MAIN_TEXT, CLICKABLE, DEVICE_PARAMETERS)
+                new CompactChip.Builder(CONTEXT, CLICKABLE, DEVICE_PARAMETERS)
+                        .setTextContent(MAIN_TEXT)
                         .setChipColors(COLORS)
                         .build();
 
         assertChip(compactChip, COLORS, /* iconId= */ null);
+        assertThat(compactChip.getText()).isEqualTo(MAIN_TEXT);
     }
 
     @Test
     public void testCompactChipIconCustomColor() {
         String iconId = "icon_id";
+        String description = "This is CompactChip with icon and text";
         CompactChip compactChip =
-                new CompactChip.Builder(CONTEXT, MAIN_TEXT, CLICKABLE, DEVICE_PARAMETERS)
+                new CompactChip.Builder(CONTEXT, CLICKABLE, DEVICE_PARAMETERS)
                         .setChipColors(COLORS)
+                        .setTextContent(MAIN_TEXT)
                         .setIconContent(iconId)
+                        .setContentDescription(description)
                         .build();
 
-        assertChip(compactChip, COLORS, /* iconId= */ iconId);
+        assertChip(compactChip, COLORS, /* iconId= */ iconId, description);
+        assertThat(compactChip.getText()).isEqualTo(MAIN_TEXT);
+    }
+
+    @Test
+    public void testCompactChipIconOnly() {
+        String iconId = "icon_id";
+        String description = "This is CompactChip with icon only";
+        ColorProp defaultColor = new ColorProp.Builder(0).build();
+        CompactChip compactChip =
+                new CompactChip.Builder(CONTEXT, CLICKABLE, DEVICE_PARAMETERS)
+                        .setChipColors(COLORS)
+                        .setIconContent(iconId)
+                        .setContentDescription(description)
+                        .build();
+
+        assertChip(
+                compactChip,
+                new ChipColors(
+                        COLORS.getBackgroundColor(),
+                        COLORS.getIconColor(),
+                        defaultColor,
+                        defaultColor),
+                iconId,
+                description);
+        assertThrows(NullPointerException.class, compactChip::getText);
     }
 
     @Test
@@ -118,28 +155,43 @@
 
     private void assertChip(
             CompactChip actualCompactChip, ChipColors colors, @Nullable String iconId) {
-        assertChipIsEqual(actualCompactChip, colors, iconId);
-        assertFromLayoutElementChipIsEqual(actualCompactChip, colors, iconId);
+        assertChip(actualCompactChip, colors, iconId, /* contentDescription= */ MAIN_TEXT);
+    }
+
+    private void assertChip(
+            CompactChip actualCompactChip,
+            ChipColors colors,
+            @Nullable String iconId,
+            @NonNull String contentDescription) {
+        assertChipIsEqual(actualCompactChip, colors, iconId, contentDescription);
+        assertFromLayoutElementChipIsEqual(actualCompactChip, colors, iconId, contentDescription);
         assertThat(CompactChip.fromLayoutElement(actualCompactChip)).isEqualTo(actualCompactChip);
     }
 
     private void assertChipIsEqual(
-            CompactChip actualCompactChip, ChipColors colors, @Nullable String iconId) {
+            CompactChip actualCompactChip,
+            ChipColors colors,
+            @Nullable String iconId,
+            @NonNull String contentDescription) {
         String expectedTag = iconId == null ? METADATA_TAG_TEXT : METADATA_TAG_ICON;
         assertThat(actualCompactChip.getMetadataTag()).isEqualTo(expectedTag);
         assertThat(actualCompactChip.getClickable().toProto()).isEqualTo(CLICKABLE.toProto());
         assertThat(areChipColorsEqual(actualCompactChip.getChipColors(), colors)).isTrue();
-        assertThat(actualCompactChip.getText()).isEqualTo(MAIN_TEXT);
         assertThat(actualCompactChip.getIconContent()).isEqualTo(iconId);
+        assertThat(actualCompactChip.getContentDescription().getValue())
+                .isEqualTo(contentDescription);
     }
 
     private void assertFromLayoutElementChipIsEqual(
-            CompactChip chip, ChipColors colors, @Nullable String iconId) {
+            CompactChip chip,
+            ChipColors colors,
+            @Nullable String iconId,
+            @NonNull String contentDescription) {
         Box box = new Box.Builder().addContent(chip).build();
 
         CompactChip newChip = CompactChip.fromLayoutElement(box.getContents().get(0));
 
         assertThat(newChip).isNotNull();
-        assertChipIsEqual(newChip, colors, iconId);
+        assertChipIsEqual(newChip, colors, iconId, contentDescription);
     }
 }
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TitleChipTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TitleChipTest.java
index 3463ba3..4570ab7 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TitleChipTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TitleChipTest.java
@@ -75,13 +75,16 @@
     @Test
     public void testTitleChipCustom() {
         DpProp width = dp(150);
+        String description = "Test description";
         TitleChip titleChip =
                 new TitleChip.Builder(CONTEXT, MAIN_TEXT, CLICKABLE, DEVICE_PARAMETERS)
                         .setChipColors(COLORS)
                         .setWidth(width)
+                        .setContentDescription(description)
                         .build();
 
         assertChip(titleChip, COLORS, width, /* iconId= */ null);
+        assertThat(titleChip.getContentDescription().getValue()).isEqualTo(description);
     }
 
     @Test
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/PrimaryLayoutTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/PrimaryLayoutTest.java
index 303637a..8700295 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/PrimaryLayoutTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/layouts/PrimaryLayoutTest.java
@@ -56,7 +56,8 @@
             new DeviceParameters.Builder().setScreenWidthDp(192).setScreenHeightDp(192).build();
     private static final LayoutElement CONTENT = new Box.Builder().build();
     private static final CompactChip PRIMARY_CHIP =
-            new CompactChip.Builder(CONTEXT, "Compact", CLICKABLE, DEVICE_PARAMETERS).build();
+            new CompactChip.Builder(CONTEXT, "Compact", CLICKABLE, DEVICE_PARAMETERS)
+                    .build();
     private static final Text PRIMARY_LABEL = new Text.Builder(CONTEXT, "Primary label").build();
     private static final Text SECONDARY_LABEL =
             new Text.Builder(CONTEXT, "Secondary label").build();
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/NodeInfo.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/NodeInfo.java
index 84a7180..d9b90e4 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/NodeInfo.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/NodeInfo.java
@@ -25,7 +25,6 @@
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArraySet;
-import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.expression.pipeline.BoundDynamicType;
 import androidx.wear.protolayout.expression.pipeline.DynamicTypeBindingRequest;
 import androidx.wear.protolayout.expression.pipeline.QuotaManager;
@@ -33,6 +32,7 @@
 import androidx.wear.protolayout.proto.ModifiersProto.AnimatedVisibility;
 import androidx.wear.protolayout.proto.TriggerProto.Trigger;
 import androidx.wear.protolayout.proto.TriggerProto.Trigger.InnerCase;
+import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.renderer.dynamicdata.PositionIdTree.TreeNode;
 
 import java.util.ArrayList;
@@ -148,7 +148,9 @@
     @VisibleForTesting
     @SuppressWarnings("RestrictTo")
     int size() {
-        return mActiveBoundTypes.stream().mapToInt(BoundDynamicType::getDynamicNodeCount).sum();
+        return mActiveBoundTypes.stream()
+                .mapToInt(BoundDynamicType::getDynamicNodeCount)
+                .sum();
     }
 
     /** Play the animation with the given trigger type. */
@@ -241,10 +243,10 @@
                         + mResolvedAvds.stream().filter(avd -> avd.mDrawable.isRunning()).count());
     }
 
-    /** Returns how many expression nodes evaluated. */
+    /** Returns the cost of evaluated expression nodes. */
     @VisibleForTesting
-    public int getExpressionNodesCount() {
-        return mActiveBoundTypes.stream().mapToInt(BoundDynamicType::getDynamicNodeCount).sum();
+    public int getExpressionDynamicNodesCost() {
+        return mActiveBoundTypes.stream().mapToInt(BoundDynamicType::getDynamicNodeCost).sum();
     }
 
     /** Stores the {@link AnimatedVisibility} associated with this node. */
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java
index 5579a88..8a933ec 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipeline.java
@@ -40,7 +40,6 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
 import androidx.collection.ArraySet;
-import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.expression.PlatformDataKey;
 import androidx.wear.protolayout.expression.pipeline.BoundDynamicType;
 import androidx.wear.protolayout.expression.pipeline.DynamicTypeBindingRequest;
@@ -65,6 +64,7 @@
 import androidx.wear.protolayout.proto.ModifiersProto.ExitTransition;
 import androidx.wear.protolayout.proto.TriggerProto.Trigger;
 import androidx.wear.protolayout.proto.TypesProto.BoolProp;
+import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.renderer.dynamicdata.NodeInfo.ResolvedAvd;
 
 import com.google.common.collect.ImmutableList;
@@ -1161,11 +1161,11 @@
                         .sum();
     }
 
-    /** Returns How many dynamic data nodes exist in the pipeline. */
+    /** Returns the cost of nodes existing in the pipeline. */
     @VisibleForTesting
-    public int getDynamicExpressionsNodesCount() {
+    public int getDynamicExpressionsNodesCost() {
         return mPositionIdTree.getAllNodes().stream()
-                .mapToInt(NodeInfo::getExpressionNodesCount)
+                .mapToInt(NodeInfo::getExpressionDynamicNodesCost)
                 .sum();
     }
 
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
index befefb4..096c2aa 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
@@ -41,7 +41,6 @@
 import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.expression.AppDataKey;
 import androidx.wear.protolayout.expression.DynamicBuilders;
 import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
@@ -73,7 +72,6 @@
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedColor;
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat;
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedInt32;
-import androidx.wear.protolayout.expression.proto.FixedProto.FixedString;
 import androidx.wear.protolayout.proto.ColorProto.ColorProp;
 import androidx.wear.protolayout.proto.DimensionProto.DegreesProp;
 import androidx.wear.protolayout.proto.DimensionProto.DpProp;
@@ -85,6 +83,7 @@
 import androidx.wear.protolayout.proto.TriggerProto.OnVisibleTrigger;
 import androidx.wear.protolayout.proto.TriggerProto.Trigger;
 import androidx.wear.protolayout.proto.TriggerProto.Trigger.InnerCase;
+import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline.PipelineMaker;
 import androidx.wear.protolayout.renderer.inflater.DefaultAndroidSeekableAnimatedImageResourceByResIdResolver;
 import androidx.wear.protolayout.renderer.inflater.ResourceResolvers.ResourceAccessException;
@@ -841,7 +840,7 @@
         String staticValue = "static";
 
         AtomicReference<String> currentValue = new AtomicReference<>();
-        FixedQuotaManagerImpl quotaManager = new FixedQuotaManagerImpl(/* quotaCap= */ 3);
+        FixedQuotaManagerImpl quotaManager = new FixedQuotaManagerImpl(/* quotaCap= */ 2);
 
         ProtoLayoutDynamicDataPipeline pipeline =
                 new ProtoLayoutDynamicDataPipeline(
@@ -872,7 +871,7 @@
         makePipelineForDynamicString(
                 pipeline, dynamicString, staticValue, "posId", currentValue::set);
         pipeline.initNewLayout();
-        expect.that(pipeline.getDynamicExpressionsNodesCount()).isEqualTo(3);
+        expect.that(pipeline.getDynamicExpressionsNodesCost()).isEqualTo(2);
         // No quota left
         expect.that(quotaManager.getRemainingQuota()).isEqualTo(0);
         expect.that(currentValue.get()).isEqualTo(expectedOutput);
@@ -880,8 +879,6 @@
 
     @Test
     public void newLayout_noExpressionNodesQuota_useStaticData() {
-
-        String dynamicValue = "dynamic";
         String staticValue = "static";
         AtomicReference<String> currentValue = new AtomicReference<>();
         FixedQuotaManagerImpl quotaManager = new FixedQuotaManagerImpl(/* quotaCap= */ 0);
@@ -894,7 +891,8 @@
 
         DynamicString dynamicString =
                 DynamicString.newBuilder()
-                        .setFixed(FixedString.newBuilder().setValue(dynamicValue).build())
+                        .setInt32FormatOp(
+                                Int32FormatOp.newBuilder().setInput(fixedDynamicInt32(1)).build())
                         .build();
 
         makePipelineForDynamicString(
@@ -909,7 +907,7 @@
     public void newLayout_removeNodeInfo_releaseQuota() {
 
         int quota = 8;
-        DynamicBool expressionWith4Nodes = buildBoolExpressionWithFixedNumberOfNodes(4);
+        DynamicBool expressionWith4Nodes = buildBoolExpressionWithFixedNumberOfNodes(5);
         FixedQuotaManagerImpl quotaManager = new FixedQuotaManagerImpl(quota);
 
         ProtoLayoutDynamicDataPipeline pipeline =
@@ -945,8 +943,10 @@
         String nodeInfo2 = "posId2.1";
         String nodeInfo3 = "posId3.1";
         int quota = 8;
-        DynamicBool expressionWith5Nodes = buildBoolExpressionWithFixedNumberOfNodes(5);
-        DynamicBool expressionWith1Nodes = buildBoolExpressionWithFixedNumberOfNodes(1);
+        // Cost = 5
+        DynamicBool expressionWith5Nodes = buildBoolExpressionWithFixedNumberOfNodes(6);
+        // Cost = 1
+        DynamicBool expressionWith1Nodes = buildBoolExpressionWithFixedNumberOfNodes(2);
         FixedQuotaManagerImpl quotaManager = new FixedQuotaManagerImpl(quota);
 
         ProtoLayoutDynamicDataPipeline pipeline =
@@ -956,7 +956,7 @@
                         new FixedQuotaManagerImpl(MAX_VALUE),
                         quotaManager);
 
-        // Adding an expressions with 5 dynamic nodes to nodeInfo1.
+        // Adding an expressions cost = 4 to nodeInfo1.
         makePipelineForDynamicBool(pipeline, expressionWith5Nodes, nodeInfo1);
         pipeline.initNewLayout();
 
@@ -975,11 +975,11 @@
 
         // Remove nodeInfo1 and add nodeInfo3. nodeInfo2 still in the pipeline.
         pipeline.mPositionIdTree.removeChildNodesFor(parentOfNode1);
-        // Adding an expressions with 1 dynamic node to nodeInfo3.
+        // Adding an expressions cost = 1 to nodeInfo3.
         makePipelineForDynamicBool(pipeline, expressionWith1Nodes, nodeInfo3);
 
         pipeline.initNewLayout();
-        // Now the pipeline will have a total expressionNodesCount of 6 = 5 + 1 nodeInfo2 (failed to
+        // Now the pipeline will have a total expression cost of 6 = 5 + 1 nodeInfo2 (failed to
         // bound previously) and nodeInfo3(new) should be able to bound
         expect.that(quotaManager.getRemainingQuota()).isEqualTo(2);
         expect.that(pipeline.mPositionIdTree.get(nodeInfo3).getFailedBindingRequest().size())
@@ -992,8 +992,11 @@
     public void newLayout_multipleBound_noEnoughDynamicNodesQuota_satisfyOnlyFewBounds() {
 
         int quota = 11;
-        DynamicBool expressionWith12Nodes = buildBoolExpressionWithFixedNumberOfNodes(12);
+        // Cost = 12
+        DynamicBool expressionWith12Nodes = buildBoolExpressionWithFixedNumberOfNodes(13);
+        // Cost = 3
         DynamicBool expressionWith4Nodes = buildBoolExpressionWithFixedNumberOfNodes(4);
+        // Cost = 0
         DynamicBool expressionWith1Nodes = buildBoolExpressionWithFixedNumberOfNodes(1);
 
         FixedQuotaManagerImpl quotaManager = new FixedQuotaManagerImpl(quota);
@@ -1013,7 +1016,7 @@
 
         pipeline.initNewLayout();
 
-        // expressionWith12Nodes related BoundType should file to bind.
+        // expressionWith12Nodes related BoundType should fail to bind.
         expect.that(
                         pipeline.mPositionIdTree
                                 .findFirst((node) -> node.getPosId().equals("posId1.0"))
@@ -1062,6 +1065,10 @@
                 .build();
     }
 
+    /**
+     * If count is equal to 1, this returns a FixedBool. Otherwise, it returns a dynamic expression
+     * containing one FixedBool and (count-1) NotBoolOp nodes.
+     */
     private static DynamicBool buildBoolExpressionWithFixedNumberOfNodes(int count) {
         if (count < 1) {
             throw new IllegalArgumentException();
diff --git a/wear/tiles/tiles-renderer/api/current.txt b/wear/tiles/tiles-renderer/api/current.txt
index d4f5f16..8b0c98e 100644
--- a/wear/tiles/tiles-renderer/api/current.txt
+++ b/wear/tiles/tiles-renderer/api/current.txt
@@ -46,11 +46,27 @@
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, @StyleRes int, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor public TileRenderer(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
+    ctor public TileRenderer(androidx.wear.tiles.renderer.TileRenderer.Config);
     method @Deprecated public android.view.View? inflate(android.view.ViewGroup);
     method public com.google.common.util.concurrent.ListenableFuture<android.view.View!> inflateAsync(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
     method public void setState(java.util.Map<androidx.wear.protolayout.expression.AppDataKey<?>!,androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue<?>!>);
   }
 
+  public static final class TileRenderer.Config {
+    method public java.util.concurrent.Executor getLoadActionExecutor();
+    method public java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!> getLoadActionListener();
+    method public java.util.Map<androidx.wear.protolayout.expression.pipeline.PlatformDataProvider!,java.util.Set<androidx.wear.protolayout.expression.PlatformDataKey<?>!>!> getPlatformDataProviders();
+    method public int getTilesTheme();
+    method public android.content.Context getUiContext();
+  }
+
+  public static final class TileRenderer.Config.Builder {
+    ctor public TileRenderer.Config.Builder(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
+    method public androidx.wear.tiles.renderer.TileRenderer.Config.Builder addPlatformDataProvider(androidx.wear.protolayout.expression.pipeline.PlatformDataProvider, androidx.wear.protolayout.expression.PlatformDataKey<?>!...);
+    method public androidx.wear.tiles.renderer.TileRenderer.Config build();
+    method public androidx.wear.tiles.renderer.TileRenderer.Config.Builder setTilesTheme(@StyleRes int);
+  }
+
   @Deprecated public static interface TileRenderer.LoadActionListener {
     method @Deprecated public void onClick(androidx.wear.tiles.StateBuilders.State);
   }
diff --git a/wear/tiles/tiles-renderer/api/restricted_current.txt b/wear/tiles/tiles-renderer/api/restricted_current.txt
index d4f5f16..8b0c98e 100644
--- a/wear/tiles/tiles-renderer/api/restricted_current.txt
+++ b/wear/tiles/tiles-renderer/api/restricted_current.txt
@@ -46,11 +46,27 @@
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor @Deprecated public TileRenderer(android.content.Context, androidx.wear.tiles.LayoutElementBuilders.Layout, @StyleRes int, androidx.wear.tiles.ResourceBuilders.Resources, java.util.concurrent.Executor, androidx.wear.tiles.renderer.TileRenderer.LoadActionListener);
     ctor public TileRenderer(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
+    ctor public TileRenderer(androidx.wear.tiles.renderer.TileRenderer.Config);
     method @Deprecated public android.view.View? inflate(android.view.ViewGroup);
     method public com.google.common.util.concurrent.ListenableFuture<android.view.View!> inflateAsync(androidx.wear.protolayout.LayoutElementBuilders.Layout, androidx.wear.protolayout.ResourceBuilders.Resources, android.view.ViewGroup);
     method public void setState(java.util.Map<androidx.wear.protolayout.expression.AppDataKey<?>!,androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue<?>!>);
   }
 
+  public static final class TileRenderer.Config {
+    method public java.util.concurrent.Executor getLoadActionExecutor();
+    method public java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!> getLoadActionListener();
+    method public java.util.Map<androidx.wear.protolayout.expression.pipeline.PlatformDataProvider!,java.util.Set<androidx.wear.protolayout.expression.PlatformDataKey<?>!>!> getPlatformDataProviders();
+    method public int getTilesTheme();
+    method public android.content.Context getUiContext();
+  }
+
+  public static final class TileRenderer.Config.Builder {
+    ctor public TileRenderer.Config.Builder(android.content.Context, java.util.concurrent.Executor, java.util.function.Consumer<androidx.wear.protolayout.StateBuilders.State!>);
+    method public androidx.wear.tiles.renderer.TileRenderer.Config.Builder addPlatformDataProvider(androidx.wear.protolayout.expression.pipeline.PlatformDataProvider, androidx.wear.protolayout.expression.PlatformDataKey<?>!...);
+    method public androidx.wear.tiles.renderer.TileRenderer.Config build();
+    method public androidx.wear.tiles.renderer.TileRenderer.Config.Builder setTilesTheme(@StyleRes int);
+  }
+
   @Deprecated public static interface TileRenderer.LoadActionListener {
     method @Deprecated public void onClick(androidx.wear.tiles.StateBuilders.State);
   }
diff --git a/wear/tiles/tiles-renderer/lint-baseline.xml b/wear/tiles/tiles-renderer/lint-baseline.xml
index 8294d8d..0177db4 100644
--- a/wear/tiles/tiles-renderer/lint-baseline.xml
+++ b/wear/tiles/tiles-renderer/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+<issues format="6" by="lint 8.4.0-alpha12" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha12)" variant="all" version="8.4.0-alpha12">
 
     <issue
         id="UnspecifiedRegisterReceiverFlag"
@@ -174,6 +174,96 @@
 
     <issue
         id="RestrictedApiAndroidX"
+        message="Layout can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="        @Nullable private final LayoutElementProto.Layout mLayout;"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Resources can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="        @Nullable private final ResourceProto.Resources mResources;"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Layout can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="                @Nullable LayoutElementProto.Layout layout,"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Resources can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="                @Nullable ResourceProto.Resources resources) {"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Layout can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="        public LayoutElementProto.Layout getLayout() {"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Resources can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="        public ResourceProto.Resources getResources() {"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Layout can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="            @Nullable private LayoutElementProto.Layout mLayout;"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Resources can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="            @Nullable private ResourceProto.Resources mResources;"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Layout can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="            public Builder setLayout(@NonNull LayoutElementProto.Layout layout) {"
+        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="Resources can only be accessed from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
+        errorLine1="            public Builder setResources(@NonNull ResourceProto.Resources resources) {"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/wear/tiles/renderer/TileRenderer.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
         message="Timeline.toProto can only be called from within the same library group (referenced groupId=`androidx.wear.protolayout` from groupId=`androidx.wear.tiles`)"
         errorLine1="        mCache = new TilesTimelineCacheInternal(timeline.toProto());"
         errorLine2="                                                         ~~~~~~~">
diff --git a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
index c9dcd7b..763c021 100644
--- a/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
+++ b/wear/tiles/tiles-renderer/src/androidTest/java/androidx/wear/tiles/renderer/test/TileRendererGoldenTest.java
@@ -41,6 +41,7 @@
 import androidx.wear.protolayout.proto.ResourceProto.Resources;
 import androidx.wear.protolayout.protobuf.ByteString;
 import androidx.wear.tiles.renderer.TileRenderer;
+import androidx.wear.tiles.renderer.TileRenderer.Config;
 
 import com.google.protobuf.TextFormat;
 
@@ -236,9 +237,11 @@
 
         TileRenderer renderer =
                 new TileRenderer(
-                        appContext,
-                        ContextCompat.getMainExecutor(getApplicationContext()),
-                        i -> {});
+                        new Config.Builder(
+                                        appContext,
+                                        ContextCompat.getMainExecutor(getApplicationContext()),
+                                        i -> {})
+                                .build());
 
         View firstChild =
                 renderer.inflateAsync(
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
index 82e1946..01a5cab 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
@@ -19,6 +19,7 @@
 import static androidx.core.util.Preconditions.checkNotNull;
 
 import android.content.Context;
+import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -30,6 +31,8 @@
 import androidx.wear.protolayout.StateBuilders;
 import androidx.wear.protolayout.expression.AppDataKey;
 import androidx.wear.protolayout.expression.DynamicDataBuilders.DynamicDataValue;
+import androidx.wear.protolayout.expression.PlatformDataKey;
+import androidx.wear.protolayout.expression.pipeline.PlatformDataProvider;
 import androidx.wear.protolayout.expression.pipeline.StateStore;
 import androidx.wear.protolayout.proto.LayoutElementProto;
 import androidx.wear.protolayout.proto.ResourceProto;
@@ -38,12 +41,15 @@
 import androidx.wear.tiles.TileService;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -89,9 +95,9 @@
      * @param resources The resources for the Tile.
      * @param loadActionExecutor Executor for {@code loadActionListener}.
      * @param loadActionListener Listener for clicks that will cause the contents to be reloaded.
-     * @deprecated Use {@link #TileRenderer(Context, Executor, Consumer)} which accepts Layout and
-     *     Resources in {@link #inflateAsync(LayoutElementBuilders.Layout,
-     *     ResourceBuilders.Resources, ViewGroup)} method.
+     * @deprecated Use {@link #TileRenderer(Config)} which accepts Layout and Resources in {@link
+     *     #inflateAsync(LayoutElementBuilders.Layout, ResourceBuilders.Resources, ViewGroup)}
+     *     method.
      */
     @Deprecated
     public TileRenderer(
@@ -106,7 +112,8 @@
                 loadActionExecutor,
                 toStateConsumer(loadActionListener),
                 layout.toProto(),
-                resources.toProto());
+                resources.toProto(),
+                /* platformDataProviders */ null);
     }
 
     /**
@@ -119,9 +126,9 @@
      * @param resources The resources for the Tile.
      * @param loadActionExecutor Executor for {@code loadActionListener}.
      * @param loadActionListener Listener for clicks that will cause the contents to be reloaded.
-     * @deprecated Use {@link #TileRenderer(Context, Executor, Consumer)} which accepts Layout and
-     *     Resources in {@link #inflateAsync(LayoutElementBuilders.Layout,
-     *     ResourceBuilders.Resources, ViewGroup)} method.
+     * @deprecated Use {@link #TileRenderer(Config)} which accepts Layout and Resources in {@link
+     *     #inflateAsync(LayoutElementBuilders.Layout, ResourceBuilders.Resources, ViewGroup)}
+     *     method.
      */
     @Deprecated
     public TileRenderer(
@@ -137,10 +144,15 @@
                 loadActionExecutor,
                 toStateConsumer(loadActionListener),
                 layout.toProto(),
-                resources.toProto());
+                resources.toProto(),
+                /* platformDataProviders */ null);
     }
 
     /**
+     * Constructor for {@link TileRenderer}.
+     *
+     * <p>It is recommended to use the new {@link #TileRenderer(Config)} constructor instead.
+     *
      * @param uiContext A {@link Context} suitable for interacting with the UI.
      * @param loadActionExecutor Executor for {@code loadActionListener}.
      * @param loadActionListener Listener for clicks that will cause the contents to be reloaded.
@@ -155,7 +167,24 @@
                 loadActionExecutor,
                 loadActionListener,
                 /* layout= */ null,
-                /* resources= */ null);
+                /* resources= */ null,
+                /* platformDataProviders */ null);
+    }
+
+    /**
+     * Constructor for {@link TileRenderer}.
+     *
+     * @param config A {@link Config} to create a {@link TileRenderer} instance.
+     */
+    public TileRenderer(@NonNull Config config) {
+        this(
+                config.getUiContext(),
+                config.getTilesTheme(),
+                config.getLoadActionExecutor(),
+                config.getLoadActionListener(),
+                /* layout= */ null,
+                /* resources= */ null,
+                config.getPlatformDataProviders());
     }
 
     private TileRenderer(
@@ -164,7 +193,8 @@
             @NonNull Executor loadActionExecutor,
             @NonNull Consumer<StateBuilders.State> loadActionListener,
             @Nullable LayoutElementProto.Layout layout,
-            @Nullable ResourceProto.Resources resources) {
+            @Nullable ResourceProto.Resources resources,
+            @Nullable Map<PlatformDataProvider, Set<PlatformDataKey<?>>> platformDataProviders) {
 
         this.mLayout = layout;
         this.mResources = resources;
@@ -186,6 +216,13 @@
         if (tilesTheme != 0) {
             config.setProtoLayoutTheme(new ProtoLayoutThemeImpl(uiContext, tilesTheme));
         }
+        if (platformDataProviders != null) {
+            for (Map.Entry<PlatformDataProvider, Set<PlatformDataKey<?>>> entry :
+                    platformDataProviders.entrySet()) {
+                config.addPlatformDataProvider(entry.getKey(),
+                        entry.getValue().toArray(new PlatformDataKey[]{}));
+            }
+        }
         this.mInstance = new ProtoLayoutViewInstance(config.build());
     }
 
@@ -271,4 +308,127 @@
         ListenableFuture<Void> result = mInstance.renderAndAttach(layout, resources, parent);
         return FluentFuture.from(result).transform(ignored -> parent.getChildAt(0), mUiExecutor);
     }
+
+    /** Config class for {@link TileRenderer}. */
+    public static final class Config {
+        @NonNull private final Context mUiContext;
+        @NonNull private final Executor mLoadActionExecutor;
+        @NonNull private final Consumer<StateBuilders.State> mLoadActionListener;
+
+        @StyleRes int mTilesTheme;
+
+        @NonNull
+        private final Map<PlatformDataProvider, Set<PlatformDataKey<?>>> mPlatformDataProviders;
+
+        Config(
+                @NonNull Context uiContext,
+                @NonNull Executor loadActionExecutor,
+                @NonNull Consumer<StateBuilders.State> loadActionListener,
+                @StyleRes int tilesTheme,
+                @NonNull Map<PlatformDataProvider, Set<PlatformDataKey<?>>> platformDataProviders) {
+            this.mUiContext = uiContext;
+            this.mLoadActionExecutor = loadActionExecutor;
+            this.mLoadActionListener = loadActionListener;
+            this.mTilesTheme = tilesTheme;
+            this.mPlatformDataProviders = platformDataProviders;
+        }
+
+        /** Returns the {@link Context} suitable for interacting with the UI. */
+        @NonNull
+        public Context getUiContext() {
+            return mUiContext;
+        }
+
+        /** Returns the {@link Executor} for {@code loadActionListener}. */
+        @NonNull
+        public Executor getLoadActionExecutor() {
+            return mLoadActionExecutor;
+        }
+
+        /** Returns the Listener for clicks that will cause the contents to be reloaded. */
+        @NonNull
+        public Consumer<StateBuilders.State> getLoadActionListener() {
+            return mLoadActionListener;
+        }
+
+        /**
+         * Returns the theme to use for this Tile instance. This can be used to customise things
+         * like the default font family. Defaults to zero (default theme) if not specified by {@link
+         * Builder#setTilesTheme(int)}.
+         */
+        public int getTilesTheme() {
+            return mTilesTheme;
+        }
+
+        /** Returns the platform data providers that will be registered for this Tile instance. */
+        @NonNull
+        public Map<PlatformDataProvider, Set<PlatformDataKey<?>>> getPlatformDataProviders() {
+            return Collections.unmodifiableMap(mPlatformDataProviders);
+        }
+
+        /** Builder class for {@link Config}. */
+        public static final class Builder {
+            @NonNull private final Context mUiContext;
+            @NonNull private final Executor mLoadActionExecutor;
+            @NonNull private final Consumer<StateBuilders.State> mLoadActionListener;
+
+            @StyleRes int mTilesTheme = 0; // Default theme.
+
+            @NonNull
+            private final Map<PlatformDataProvider, Set<PlatformDataKey<?>>>
+                    mPlatformDataProviders = new ArrayMap<>();
+
+            /**
+             * Builder for the {@link Config} class.
+             *
+             * @param uiContext A {@link Context} suitable for interacting with the UI.
+             * @param loadActionExecutor Executor for {@code loadActionListener}.
+             * @param loadActionListener Listener for clicks that will cause the contents to be
+             *     reloaded.
+             */
+            public Builder(
+                    @NonNull Context uiContext,
+                    @NonNull Executor loadActionExecutor,
+                    @NonNull Consumer<StateBuilders.State> loadActionListener) {
+                this.mUiContext = uiContext;
+                this.mLoadActionExecutor = loadActionExecutor;
+                this.mLoadActionListener = loadActionListener;
+            }
+
+            /**
+             * Sets the theme to use for this Tile instance. This can be used to customise things
+             * like the default font family. If not set, zero (default theme) will be used.
+             */
+            @NonNull
+            public Builder setTilesTheme(@StyleRes int tilesTheme) {
+                mTilesTheme = tilesTheme;
+                return this;
+            }
+
+            /**
+             * Adds a {@link PlatformDataProvider} that will be registered for
+             * the given {@code supportedKeys}. Adding the same {@link PlatformDataProvider} several
+             * times will override previous entries instead of adding multiple entries.
+             */
+            @NonNull
+            public Builder addPlatformDataProvider(
+                    @NonNull PlatformDataProvider platformDataProvider,
+                    @NonNull PlatformDataKey<?>... supportedKeys) {
+                this.mPlatformDataProviders.put(
+                        platformDataProvider, ImmutableSet.copyOf(supportedKeys));
+                return this;
+            }
+
+            /** Builds {@link Config} object. */
+            @NonNull
+            public Config build() {
+                return new Config(
+                        mUiContext,
+                        mLoadActionExecutor,
+                        mLoadActionListener,
+                        mTilesTheme,
+                        mPlatformDataProviders);
+            }
+        }
+    }
 }
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java
index 40a43ac..156405f 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/EventBuilders.java
@@ -27,8 +27,6 @@
 
     /**
      * Event fired when a tile has been added to the carousel.
-     *
-     * @since 1.0
      */
     public static final class TileAddEvent {
         private final EventProto.TileAddEvent mImpl;
@@ -41,8 +39,6 @@
          * Gets the instance ID of the tile, allocated when the tile instance is added to the
          * carousel. This ID will remain the same for this tile instance as long it is not removed
          * from the carousel.
-         *
-         * @since 1.0
          */
         public int getTileId() {
             return mImpl.getTileId();
@@ -77,8 +73,6 @@
 
             /**
              * Sets the ID of the tile added to the carousel.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileId(int tileId) {
@@ -96,8 +90,6 @@
 
     /**
      * Event fired when a tile has been removed from the carousel.
-     *
-     * @since 1.0
      */
     public static final class TileRemoveEvent {
         private final EventProto.TileRemoveEvent mImpl;
@@ -110,8 +102,6 @@
          * Gets the instance ID of the tile, allocated when the tile instance is added to the
          * carousel. This ID will remain the same for this tile instance as long it is not removed
          * from the carousel.
-         *
-         * @since 1.0
          */
         public int getTileId() {
             return mImpl.getTileId();
@@ -146,8 +136,6 @@
 
             /**
              * Sets the ID of the tile removed from the carousel.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileId(int tileId) {
@@ -165,8 +153,6 @@
 
     /**
      * Event fired when a tile is swiped to by the user (i.e. it's visible on screen).
-     *
-     * @since 1.0
      */
     public static final class TileEnterEvent {
         private final EventProto.TileEnterEvent mImpl;
@@ -179,8 +165,6 @@
          * Gets the instance ID of the tile, allocated when the tile instance is added to the
          * carousel. This ID will remain the same for this tile instance as long it is not removed
          * from the carousel.
-         *
-         * @since 1.0
          */
         public int getTileId() {
             return mImpl.getTileId();
@@ -215,8 +199,6 @@
 
             /**
              * Sets the ID of the entered tile.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileId(int tileId) {
@@ -235,8 +217,6 @@
     /**
      * Event fired when a tile is swiped away from by the user (i.e. it's no longer visible on
      * screen).
-     *
-     * @since 1.0
      */
     public static final class TileLeaveEvent {
         private final EventProto.TileLeaveEvent mImpl;
@@ -249,8 +229,6 @@
          * Gets the instance ID of the tile, allocated when the tile instance is added to the
          * carousel. This ID will remain the same for this tile instance as long it is not removed
          * from the carousel.
-         *
-         * @since 1.0
          */
         public int getTileId() {
             return mImpl.getTileId();
@@ -285,8 +263,6 @@
 
             /**
              * Sets the ID of the tile.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileId(int tileId) {
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java
index a6da0e8..a0b6980 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/RequestBuilders.java
@@ -35,8 +35,6 @@
     /**
      * Parameters passed to a {@link androidx.wear.tiles.TileBuilders.Tile} Service when the
      * renderer is requesting a new version of the tile.
-     *
-     * @since 1.0
      */
     public static final class TileRequest {
         private final RequestProto.TileRequest mImpl;
@@ -49,8 +47,6 @@
          * Gets the {@link androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters}
          * object describing the device requesting the tile update. If not set, a default empty
          * instance is used.
-         *
-         * @since 1.0
          */
         @NonNull
         public DeviceParameters getDeviceConfiguration() {
@@ -65,8 +61,6 @@
         /**
          * Gets the {@link androidx.wear.protolayout.StateBuilders.State} that should be used when
          * building the tile.
-         *
-         * @since 1.0
          */
         @NonNull
         public State getCurrentState() {
@@ -81,8 +75,6 @@
          * Gets the instance ID of the tile being requested, allocated when the tile instance is
          * added to the carousel. This ID will remain the same for this tile instance as long it is
          * not removed from the carousel.
-         *
-         * @since 1.0
          */
         public int getTileId() {
             return mImpl.getTileId();
@@ -92,7 +84,6 @@
          * Gets the {@link androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters} object
          * describing the device requesting the tile update.
          *
-         * @since 1.0
          * @deprecated Use {@link #getDeviceConfiguration()} instead.
          */
         @Deprecated
@@ -111,7 +102,6 @@
          * Gets the {@link androidx.wear.tiles.StateBuilders.State} that should be used when
          * building the tile.
          *
-         * @since 1.0
          * @deprecated Use {@link #getCurrentState()} instead.
          */
         @Deprecated
@@ -163,8 +153,6 @@
              * Sets the {@link androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters}
              * object describing the device requesting the tile update. If not set, a default empty
              * instance is used.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setDeviceConfiguration(@NonNull DeviceParameters deviceConfiguration) {
@@ -175,8 +163,6 @@
             /**
              * Sets the {@link androidx.wear.protolayout.StateBuilders.State} that should be used
              * when building the tile.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setCurrentState(@NonNull State currentState) {
@@ -186,8 +172,6 @@
 
             /**
              * Sets the ID of the tile being requested.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileId(int tileId) {
@@ -199,7 +183,6 @@
              * Sets the {@link androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters}
              * describing the device requesting the tile update.
              *
-             * @since 1.0
              * @deprecated Use {@link setDeviceConfiguration(DeviceParameters)} instead.
              */
             @Deprecated
@@ -216,7 +199,6 @@
              * Sets the {@link androidx.wear.tiles.StateBuilders.State} that should be used when
              * building the tile.
              *
-             * @since 1.0
              * @deprecated Use {@link setCurrentState(State)} instead.
              */
             @Deprecated
@@ -237,8 +219,6 @@
     /**
      * Parameters passed to a {@link androidx.wear.tiles.TileBuilders.Tile} Service when the
      * renderer is requesting a specific resource version.
-     *
-     * @since 1.0
      */
     public static final class ResourcesRequest {
         private final RequestProto.ResourcesRequest mImpl;
@@ -250,8 +230,6 @@
         /**
          * Gets the version of the resources being fetched. This is the same as the requested
          * resource version, passed in {@link androidx.wear.tiles.TileBuilders.Tile}.
-         *
-         * @since 1.0
          */
         @NonNull
         public String getVersion() {
@@ -266,8 +244,6 @@
          * <p>Note that resource IDs here correspond to tile resources (i.e. keys that would be used
          * in {@link androidx.wear.protolayout.ResourceBuilders.Resources}.idToImage), not Android
          * resource names or similar.
-         *
-         * @since 1.0
          */
         @NonNull
         public List<String> getResourceIds() {
@@ -277,8 +253,6 @@
         /**
          * Gets the {@link androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters}
          * object describing the device requesting the resources.
-         *
-         * @since 1.0
          */
         @NonNull
         public DeviceParameters getDeviceConfiguration() {
@@ -294,8 +268,6 @@
          * Gets the instance ID of the tile for which resources are being requested, allocated when
          * the tile instance is added to the carousel. This ID will remain the same for this tile
          * instance as long it is not removed from the carousel.
-         *
-         * @since 1.0
          */
         public int getTileId() {
             return mImpl.getTileId();
@@ -305,7 +277,6 @@
          * Gets the {@link androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters} object
          * describing the device requesting the resources.
          *
-         * @since 1.0
          * @deprecated Use {@link #getDeviceConfiguration()} instead.
          */
         @Deprecated
@@ -360,7 +331,6 @@
              * Sets the version of the resources being fetched. This is the same as the requested
              * resource version, passed in {@link androidx.wear.tiles.TileBuilders.Tile}.
              *
-             * @since 1.0
              */
             @NonNull
             public Builder setVersion(@NonNull String version) {
@@ -377,8 +347,6 @@
              * <p>Note that resource IDs here correspond to tile resources (i.e. keys that would be
              * used in {@link androidx.wear.protolayout.ResourceBuilders.Resources}.idToImage), not
              * Android resource names or similar.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder addResourceId(@NonNull String resourceId) {
@@ -389,8 +357,6 @@
             /**
              * Sets the {@link androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters}
              * object describing the device requesting the resources.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setDeviceConfiguration(@NonNull DeviceParameters deviceConfiguration) {
@@ -400,8 +366,6 @@
 
             /**
              * Sets the ID of the tile for which resources are being requested.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileId(int tileId) {
@@ -413,7 +377,6 @@
              * Sets the {@link androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters}
              * describing the device requesting the resources.
              *
-             * @since 1.0
              * @deprecated Use {@link setDeviceConfiguration(DeviceParameters)} instead.
              */
             @Deprecated
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java
index 1d1c35c..f81f833 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileBuilders.java
@@ -32,8 +32,6 @@
     /**
      * A holder for a tile. This specifies the resources to use for this delivery of the tile, and
      * the timeline for the tile.
-     *
-     * @since 1.0
      */
     public static final class Tile {
         private final TileProto.Tile mImpl;
@@ -47,8 +45,6 @@
          * string; it is only used to cache resources, and is passed in {@link
          * androidx.wear.tiles.RequestBuilders.ResourcesRequest} if the system does not have a copy
          * of the specified resource version.
-         *
-         * @since 1.0
          */
         @NonNull
         public String getResourcesVersion() {
@@ -58,8 +54,6 @@
         /**
          * Gets the {@link androidx.wear.protolayout.TimelineBuilders.Timeline} containing the
          * layouts for the tiles to show in the carousel, along with their validity periods.
-         *
-         * @since 1.0
          */
         @Nullable
         public Timeline getTileTimeline() {
@@ -82,8 +76,6 @@
          * interval. This interval is also inexact; the system will generally update your tile if it
          * is on-screen, or about to be on-screen, although this is not guaranteed due to
          * system-level optimizations.
-         *
-         * @since 1.0
          */
         public long getFreshnessIntervalMillis() {
             return mImpl.getFreshnessIntervalMillis();
@@ -91,8 +83,6 @@
 
         /**
          * Gets {@link androidx.wear.protolayout.StateBuilders.State} for this tile.
-         *
-         * @since 1.2
          */
         @Nullable
         public State getState() {
@@ -107,7 +97,6 @@
          * Gets the {@link androidx.wear.tiles.TimelineBuilders.Timeline} containing the layouts for
          * the tiles to show in the carousel, along with their validity periods.
          *
-         * @since 1.0
          * @deprecated Use {@link #getTileTimeline()} instead.
          */
         @Deprecated
@@ -162,8 +151,6 @@
              * string; it is only used to cache resources, and is passed in {@link
              * androidx.wear.tiles.RequestBuilders.ResourcesRequest} if the system does not have a
              * copy of the specified resource version.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setResourcesVersion(@NonNull String resourcesVersion) {
@@ -174,8 +161,6 @@
             /**
              * Sets the {@link androidx.wear.protolayout.TimelineBuilders.Timeline} containing the
              * layouts for the tiles to show in the carousel, along with their validity periods.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setTileTimeline(@NonNull Timeline tileTimeline) {
@@ -195,8 +180,6 @@
              * this interval. This interval is also inexact; the system will generally update your
              * tile if it is on-screen, or about to be on-screen, although this is not guaranteed
              * due to system-level optimizations.
-             *
-             * @since 1.0
              */
             @NonNull
             public Builder setFreshnessIntervalMillis(long freshnessIntervalMillis) {
@@ -206,8 +189,6 @@
 
             /**
              * Sets {@link androidx.wear.protolayout.StateBuilders.State} for this tile.
-             *
-             * @since 1.2
              */
             @NonNull
             public Builder setState(@NonNull State state) {
@@ -219,7 +200,6 @@
              * Sets the {@link androidx.wear.tiles.TimelineBuilders.Timeline} containing the layouts
              * for the tiles to show in the carousel, along with their validity periods.
              *
-             * @since 1.0
              * @deprecated Use {@link #setTileTimeline(Timeline)} instead.
              */
             @Deprecated
diff --git a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
index b3a8cc15..e45172f 100644
--- a/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
+++ b/webkit/integration-tests/instrumentation/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -221,8 +221,8 @@
         WebSettingsCompat.setWebViewMediaIntegrityApiStatus(settings, config);
         Assert.assertEquals(
                 WEBVIEW_MEDIA_INTEGRITY_API_DISABLED,
-                        WebSettingsCompat.getWebViewMediaIntegrityApiStatus(settings)
-                                .getDefaultStatus());
+                WebSettingsCompat.getWebViewMediaIntegrityApiStatus(settings)
+                        .getDefaultStatus());
         Assert.assertTrue(
                 WebSettingsCompat.getWebViewMediaIntegrityApiStatus(settings)
                         .getOverrideRules().isEmpty());
@@ -288,4 +288,22 @@
                 WebSettingsCompat.getWebViewMediaIntegrityApiStatus(settings)
                         .getOverrideRules().isEmpty());
     }
+
+    @Test
+    public void testWebauthnSupport() throws Throwable {
+        WebkitUtils.checkFeature(WebViewFeature.WEB_AUTHENTICATION);
+        WebSettings settings = mWebViewOnUiThread.getSettings();
+        mWebViewOnUiThread.setCleanupTask(
+                () -> WebSettingsCompat.setWebAuthenticationSupport(settings,
+                        WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_NONE));
+
+        Assert.assertEquals("NONE is the expected default",
+                WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_NONE,
+                WebSettingsCompat.getWebAuthenticationSupport(settings));
+
+        WebSettingsCompat.setWebAuthenticationSupport(settings,
+                WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_APP);
+        Assert.assertEquals(WebSettingsCompat.WEB_AUTHENTICATION_SUPPORT_APP,
+                WebSettingsCompat.getWebAuthenticationSupport(settings));
+    }
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 71e2734..413c0fc 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -907,6 +907,86 @@
         }
     }
 
+    @IntDef({WEB_AUTHENTICATION_SUPPORT_NONE,
+            WEB_AUTHENTICATION_SUPPORT_APP})
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @Retention(RetentionPolicy.SOURCE)
+    @interface WebAuthenticationSupport {
+    }
+
+    /**
+     * The support level that disables WebAuthn requests from WebView.
+     * <p>
+     * This is the default behavior.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int WEB_AUTHENTICATION_SUPPORT_NONE =
+            WebSettingsBoundaryInterface.WebauthnSupport.NONE;
+    /**
+     * The support level that allows WebAuthn requests for the app in which the WebView is embedded.
+     * See
+     * <a href="https://developers.google.com/digital-asset-links">Digital Asset Links</a>
+     * to learn how to associate an app with a website.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final int WEB_AUTHENTICATION_SUPPORT_APP =
+            WebSettingsBoundaryInterface.WebauthnSupport.APP;
+
+    /**
+     * Sets the support level for the given {@link WebSettings}.
+     *
+     * <p>
+     * This method should only be called if
+     * {@link WebViewFeature#isFeatureSupported(String)} returns true for
+     * {@link WebViewFeature#WEB_AUTHENTICATION}.
+     *
+     * @param settings Settings retrieved from {@link WebView#getSettings()}.
+     * @param support  The new support level which this WebView will use.
+     * @see #WEB_AUTHENTICATION_SUPPORT_NONE
+     * @see #WEB_AUTHENTICATION_SUPPORT_APP
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @RequiresFeature(name = WebViewFeature.WEB_AUTHENTICATION,
+            enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+    public static void setWebAuthenticationSupport(@NonNull WebSettings settings,
+            @WebAuthenticationSupport int support) {
+        final ApiFeature.NoFramework feature =
+                WebViewFeatureInternal.WEB_AUTHENTICATION;
+        if (feature.isSupportedByWebView()) {
+            getAdapter(settings).setWebAuthenticationSupport(support);
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Returns the support level for the given {@link WebSettings}
+     *
+     * <p>
+     * This method should only be called if
+     * {@link WebViewFeature#isFeatureSupported(String)} returns true for
+     * {@link WebViewFeature#WEB_AUTHENTICATION}.
+     *
+     * @param settings Settings retrieved from {@link WebView#getSettings()}.
+     * @return the current support level.
+     * @see #setWebAuthenticationSupport(WebSettings, int)
+     * @see #WEB_AUTHENTICATION_SUPPORT_NONE
+     * @see #WEB_AUTHENTICATION_SUPPORT_APP
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @RequiresFeature(name = WebViewFeature.WEB_AUTHENTICATION,
+            enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+    @WebAuthenticationSupport
+    public static int getWebAuthenticationSupport(@NonNull WebSettings settings) {
+        final ApiFeature.NoFramework feature =
+                WebViewFeatureInternal.WEB_AUTHENTICATION;
+        if (feature.isSupportedByWebView()) {
+            return getAdapter(settings).getWebAuthenticationSupport();
+        } else {
+            throw WebViewFeatureInternal.getUnsupportedOperationException();
+        }
+    }
+
     private static WebSettingsAdapter getAdapter(WebSettings settings) {
         return WebViewGlueCommunicator.getCompatConverter().convertSettings(settings);
     }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 1907f41..8f9fd8c 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -589,6 +589,15 @@
     public static final String MUTE_AUDIO = "MUTE_AUDIO";
 
     /**
+     * Feature for {@link #isFeatureSupported(String)}
+     * This feature covers
+     * {@link androidx.webkit.WebSettingsCompat#setWebAuthenticationSupport(WebSettings, int)}
+     * {@link androidx.webkit.WebSettingsCompat#getWebAuthenticationSupport(WebSettings)}
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String WEB_AUTHENTICATION = "WEB_AUTHENTICATION";
+
+    /**
      * Return whether a feature is supported at run-time. On devices running Android version {@link
      * android.os.Build.VERSION_CODES#LOLLIPOP} and higher, this will check whether a feature is
      * supported, depending on the combination of the desired feature, the Android version of
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
index 64e29ec..542be67 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
@@ -215,4 +215,20 @@
                 .build();
     }
 
+    /**
+     * Adapter method for
+     * {@link androidx.webkit.WebSettingsCompat#setWebAuthenticationSupport(WebSettings, int)}
+     */
+    public void setWebAuthenticationSupport(int support) {
+        mBoundaryInterface.setWebauthnSupport(support);
+    }
+
+    /**
+     * Adapter method for
+     * {@link androidx.webkit.WebSettingsCompat#getWebAuthenticationSupport(WebSettings)}
+     */
+    public int getWebAuthenticationSupport() {
+        return mBoundaryInterface.getWebauthnSupport();
+    }
+
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index 6670041..ea46c38 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -604,6 +604,16 @@
             new ApiFeature.NoFramework(WebViewFeature.MUTE_AUDIO,
                     Features.MUTE_AUDIO);
 
+    /**
+     * Feature for {@link WebViewFeature#isFeatureSupported(String)}.
+     * This feature covers
+     * {@link androidx.webkit.WebSettingsCompat#setWebAuthenticationSupport(WebSettings, int)}
+     * {@link androidx.webkit.WebSettingsCompat#getWebAuthenticationSupport(WebSettings)}
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final ApiFeature.NoFramework WEB_AUTHENTICATION = new ApiFeature.NoFramework(
+            WebViewFeature.WEB_AUTHENTICATION, Features.WEB_AUTHENTICATION);
+
     // --- Add new feature constants above this line ---
 
     private WebViewFeatureInternal() {
diff --git a/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt b/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
index 4487440..6402ad1 100644
--- a/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
+++ b/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
@@ -39,6 +39,7 @@
 import androidx.work.inspection.WorkManagerInspectorProtocol.Response
 import androidx.work.inspection.WorkManagerInspectorProtocol.TrackWorkManagerResponse
 import androidx.work.inspection.WorkManagerInspectorProtocol.WorkAddedEvent
+import androidx.work.inspection.WorkManagerInspectorProtocol.WorkInfo
 import androidx.work.inspection.WorkManagerInspectorProtocol.WorkRemovedEvent
 import androidx.work.inspection.WorkManagerInspectorProtocol.WorkUpdatedEvent
 import java.util.UUID
@@ -89,10 +90,13 @@
                 val response = Response.newBuilder()
                     .setTrackWorkManager(TrackWorkManagerResponse.getDefaultInstance())
                     .build()
-                workManager
+                val allWorkSpecIdsLiveData = workManager
                     .workDatabase
                     .workSpecDao()
                     .getAllWorkSpecIdsLiveData()
+                // just a little hack to continue using `safeObserveWhileNotNull`
+                @Suppress("UNCHECKED_CAST")
+                (allWorkSpecIdsLiveData as LiveData<List<String>?>)
                     .safeObserveWhileNotNull(this, executor) { oldList, newList ->
                         updateWorkIdList(oldList ?: listOf(), newList)
                     }
@@ -123,7 +127,7 @@
      * Observation will last until "null" value is dispatched, then
      * observer will be automatically removed.
      */
-    private fun <T> LiveData<T>.safeObserveWhileNotNull(
+    private fun <T : Any> LiveData<T?>.safeObserveWhileNotNull(
         owner: LifecycleOwner,
         executor: Executor,
         listener: (oldValue: T?, newValue: T) -> Unit
@@ -131,9 +135,9 @@
         mainHandler.post {
             observe(
                 owner,
-                object : Observer<T> {
+                object : Observer<T?> {
                     private var lastValue: T? = null
-                    override fun onChanged(value: T) {
+                    override fun onChanged(value: T?) {
                         if (value == null) {
                             removeObserver(this)
                         } else {
@@ -178,7 +182,7 @@
         workInfoBuilder.isPeriodic = workSpec.isPeriodic
         workInfoBuilder.constraints = workSpec.constraints.toProto()
         workManager.getWorkInfoById(UUID.fromString(id)).let {
-            workInfoBuilder.addAllTags(it.get().tags)
+            workInfoBuilder.addAllTags(it.get()?.tags ?: emptyList())
         }
 
         val workStackBuilder = WorkManagerInspectorProtocol.CallStack.newBuilder()
diff --git a/work/work-multiprocess/build.gradle b/work/work-multiprocess/build.gradle
index ceb3cb9..2171106 100644
--- a/work/work-multiprocess/build.gradle
+++ b/work/work-multiprocess/build.gradle
@@ -45,7 +45,7 @@
     api(libs.kotlinCoroutinesAndroid)
     api(libs.guavaListenableFuture)
     implementation("androidx.core:core:1.12.0")
-    implementation(project(":concurrent:concurrent-futures-ktx"))
+    implementation("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha03")
     implementation("androidx.room:room-runtime:2.6.1")
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index b681410..5af96fc 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -285,6 +285,7 @@
 
   public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
     ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass);
+    ctor public OneTimeWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass);
     method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger> inputMerger);
   }
 
@@ -342,6 +343,10 @@
     ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker?> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
     ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker?> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
     ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker?> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
+    ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval);
+    ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
+    ctor public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+    ctor public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
     method public androidx.work.PeriodicWorkRequest.Builder clearNextScheduleTimeOverride();
     method public androidx.work.PeriodicWorkRequest.Builder setNextScheduleTimeOverride(long nextScheduleTimeOverrideMillis);
   }
@@ -453,41 +458,50 @@
   }
 
   public abstract class WorkManager {
-    method public final androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
-    method public abstract androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest!>);
-    method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest);
-    method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest!>);
+    method public final androidx.work.WorkContinuation beginUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, androidx.work.OneTimeWorkRequest request);
+    method public abstract androidx.work.WorkContinuation beginUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest> requests);
+    method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest request);
+    method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest> requests);
     method public abstract androidx.work.Operation cancelAllWork();
-    method public abstract androidx.work.Operation cancelAllWorkByTag(String);
-    method public abstract androidx.work.Operation cancelUniqueWork(String);
-    method public abstract androidx.work.Operation cancelWorkById(java.util.UUID);
-    method public abstract android.app.PendingIntent createCancelPendingIntent(java.util.UUID);
-    method public final androidx.work.Operation enqueue(androidx.work.WorkRequest);
-    method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest!>);
-    method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
-    method public androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
-    method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest!>);
+    method public abstract androidx.work.Operation cancelAllWorkByTag(String tag);
+    method public abstract androidx.work.Operation cancelUniqueWork(String uniqueWorkName);
+    method public abstract androidx.work.Operation cancelWorkById(java.util.UUID id);
+    method public abstract android.app.PendingIntent createCancelPendingIntent(java.util.UUID id);
+    method public final androidx.work.Operation enqueue(androidx.work.WorkRequest request);
+    method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest> requests);
+    method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String uniqueWorkName, androidx.work.ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest request);
+    method public androidx.work.Operation enqueueUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, androidx.work.OneTimeWorkRequest request);
+    method public abstract androidx.work.Operation enqueueUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest> requests);
     method public abstract androidx.work.Configuration getConfiguration();
     method @Deprecated public static androidx.work.WorkManager getInstance();
-    method public static androidx.work.WorkManager getInstance(android.content.Context);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long!> getLastCancelAllTimeMillis();
-    method public abstract androidx.lifecycle.LiveData<java.lang.Long!> getLastCancelAllTimeMillisLiveData();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo!> getWorkInfoById(java.util.UUID);
-    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo!> getWorkInfoByIdFlow(java.util.UUID);
-    method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo!> getWorkInfoByIdLiveData(java.util.UUID);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfos(androidx.work.WorkQuery);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTag(String);
-    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagFlow(String);
-    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagLiveData(String);
-    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosFlow(androidx.work.WorkQuery);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWork(String);
-    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkFlow(String);
-    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkLiveData(String);
-    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosLiveData(androidx.work.WorkQuery);
-    method public static void initialize(android.content.Context, androidx.work.Configuration);
+    method public static androidx.work.WorkManager getInstance(android.content.Context context);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
+    method public abstract androidx.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo?> getWorkInfoById(java.util.UUID id);
+    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo?> getWorkInfoByIdFlow(java.util.UUID id);
+    method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo?> getWorkInfoByIdLiveData(java.util.UUID id);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos(androidx.work.WorkQuery workQuery);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String tag);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagFlow(String tag);
+    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String tag);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo>> getWorkInfosFlow(androidx.work.WorkQuery workQuery);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String uniqueWorkName);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkFlow(String uniqueWorkName);
+    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String uniqueWorkName);
+    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData(androidx.work.WorkQuery workQuery);
+    method public static void initialize(android.content.Context context, androidx.work.Configuration configuration);
     method public static boolean isInitialized();
     method public abstract androidx.work.Operation pruneWork();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkManager.UpdateResult!> updateWork(androidx.work.WorkRequest);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkManager.UpdateResult> updateWork(androidx.work.WorkRequest request);
+    property public abstract androidx.work.Configuration configuration;
+    field public static final androidx.work.WorkManager.Companion Companion;
+  }
+
+  public static final class WorkManager.Companion {
+    method @Deprecated public androidx.work.WorkManager getInstance();
+    method public androidx.work.WorkManager getInstance(android.content.Context context);
+    method public void initialize(android.content.Context context, androidx.work.Configuration configuration);
+    method public boolean isInitialized();
   }
 
   public enum WorkManager.UpdateResult {
@@ -496,7 +510,7 @@
     enum_constant public static final androidx.work.WorkManager.UpdateResult NOT_APPLIED;
   }
 
-  public final class WorkManagerInitializer implements androidx.startup.Initializer<androidx.work.WorkManager> {
+  public final class WorkManagerInitializer implements androidx.startup.Initializer<androidx.work.WorkManager!> {
     ctor public WorkManagerInitializer();
     method public androidx.work.WorkManager create(android.content.Context);
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>!>!> dependencies();
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index b681410..5af96fc 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -285,6 +285,7 @@
 
   public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
     ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker> workerClass);
+    ctor public OneTimeWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass);
     method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger> inputMerger);
   }
 
@@ -342,6 +343,10 @@
     ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker?> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
     ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker?> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
     ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker?> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
+    ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval);
+    ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, java.time.Duration repeatInterval, java.time.Duration flexInterval);
+    ctor public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit);
+    ctor public PeriodicWorkRequest.Builder(kotlin.reflect.KClass<? extends androidx.work.ListenableWorker> workerClass, long repeatInterval, java.util.concurrent.TimeUnit repeatIntervalTimeUnit, long flexInterval, java.util.concurrent.TimeUnit flexIntervalTimeUnit);
     method public androidx.work.PeriodicWorkRequest.Builder clearNextScheduleTimeOverride();
     method public androidx.work.PeriodicWorkRequest.Builder setNextScheduleTimeOverride(long nextScheduleTimeOverrideMillis);
   }
@@ -453,41 +458,50 @@
   }
 
   public abstract class WorkManager {
-    method public final androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
-    method public abstract androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest!>);
-    method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest);
-    method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest!>);
+    method public final androidx.work.WorkContinuation beginUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, androidx.work.OneTimeWorkRequest request);
+    method public abstract androidx.work.WorkContinuation beginUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest> requests);
+    method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest request);
+    method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest> requests);
     method public abstract androidx.work.Operation cancelAllWork();
-    method public abstract androidx.work.Operation cancelAllWorkByTag(String);
-    method public abstract androidx.work.Operation cancelUniqueWork(String);
-    method public abstract androidx.work.Operation cancelWorkById(java.util.UUID);
-    method public abstract android.app.PendingIntent createCancelPendingIntent(java.util.UUID);
-    method public final androidx.work.Operation enqueue(androidx.work.WorkRequest);
-    method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest!>);
-    method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
-    method public androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
-    method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest!>);
+    method public abstract androidx.work.Operation cancelAllWorkByTag(String tag);
+    method public abstract androidx.work.Operation cancelUniqueWork(String uniqueWorkName);
+    method public abstract androidx.work.Operation cancelWorkById(java.util.UUID id);
+    method public abstract android.app.PendingIntent createCancelPendingIntent(java.util.UUID id);
+    method public final androidx.work.Operation enqueue(androidx.work.WorkRequest request);
+    method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest> requests);
+    method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String uniqueWorkName, androidx.work.ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest request);
+    method public androidx.work.Operation enqueueUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, androidx.work.OneTimeWorkRequest request);
+    method public abstract androidx.work.Operation enqueueUniqueWork(String uniqueWorkName, androidx.work.ExistingWorkPolicy existingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest> requests);
     method public abstract androidx.work.Configuration getConfiguration();
     method @Deprecated public static androidx.work.WorkManager getInstance();
-    method public static androidx.work.WorkManager getInstance(android.content.Context);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long!> getLastCancelAllTimeMillis();
-    method public abstract androidx.lifecycle.LiveData<java.lang.Long!> getLastCancelAllTimeMillisLiveData();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo!> getWorkInfoById(java.util.UUID);
-    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo!> getWorkInfoByIdFlow(java.util.UUID);
-    method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo!> getWorkInfoByIdLiveData(java.util.UUID);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfos(androidx.work.WorkQuery);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTag(String);
-    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagFlow(String);
-    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagLiveData(String);
-    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosFlow(androidx.work.WorkQuery);
-    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWork(String);
-    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkFlow(String);
-    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkLiveData(String);
-    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosLiveData(androidx.work.WorkQuery);
-    method public static void initialize(android.content.Context, androidx.work.Configuration);
+    method public static androidx.work.WorkManager getInstance(android.content.Context context);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
+    method public abstract androidx.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo?> getWorkInfoById(java.util.UUID id);
+    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo?> getWorkInfoByIdFlow(java.util.UUID id);
+    method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo?> getWorkInfoByIdLiveData(java.util.UUID id);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos(androidx.work.WorkQuery workQuery);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String tag);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagFlow(String tag);
+    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String tag);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo>> getWorkInfosFlow(androidx.work.WorkQuery workQuery);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String uniqueWorkName);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkFlow(String uniqueWorkName);
+    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String uniqueWorkName);
+    method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData(androidx.work.WorkQuery workQuery);
+    method public static void initialize(android.content.Context context, androidx.work.Configuration configuration);
     method public static boolean isInitialized();
     method public abstract androidx.work.Operation pruneWork();
-    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkManager.UpdateResult!> updateWork(androidx.work.WorkRequest);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkManager.UpdateResult> updateWork(androidx.work.WorkRequest request);
+    property public abstract androidx.work.Configuration configuration;
+    field public static final androidx.work.WorkManager.Companion Companion;
+  }
+
+  public static final class WorkManager.Companion {
+    method @Deprecated public androidx.work.WorkManager getInstance();
+    method public androidx.work.WorkManager getInstance(android.content.Context context);
+    method public void initialize(android.content.Context context, androidx.work.Configuration configuration);
+    method public boolean isInitialized();
   }
 
   public enum WorkManager.UpdateResult {
@@ -496,7 +510,7 @@
     enum_constant public static final androidx.work.WorkManager.UpdateResult NOT_APPLIED;
   }
 
-  public final class WorkManagerInitializer implements androidx.startup.Initializer<androidx.work.WorkManager> {
+  public final class WorkManagerInitializer implements androidx.startup.Initializer<androidx.work.WorkManager!> {
     ctor public WorkManagerInitializer();
     method public androidx.work.WorkManager create(android.content.Context);
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>!>!> dependencies();
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
index 3e3f75c..dfb0087 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -32,6 +32,7 @@
 import java.util.concurrent.Executors
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
@@ -96,12 +97,12 @@
         workManager.enqueue(request)
         val worker = workerFactory.await(request.id) as ProgressUpdatingWorker
 
-        val progress1 = workManager.getWorkInfoByIdFlow(request.id)
+        val progress1 = workManager.getWorkInfoByIdFlow(request.id).filterNotNull()
             .first { it.progress.getInt("progress", 0) != 0 }.progress
         assertThat(progress1.getInt("progress", 0)).isEqualTo(1)
         worker.firstCheckPoint.complete(Unit)
 
-        val progress2 = workManager.getWorkInfoByIdFlow(request.id)
+        val progress2 = workManager.getWorkInfoByIdFlow(request.id).filterNotNull()
             .first { it.progress.getInt("progress", 0) != 1 }.progress
         assertThat(progress2.getInt("progress", 0)).isEqualTo(100)
         worker.secondCheckPoint.complete(Unit)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/GreedySchedulerTimeoutTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/GreedySchedulerTimeoutTest.kt
index 1f2c690..3b37ebb 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/GreedySchedulerTimeoutTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/GreedySchedulerTimeoutTest.kt
@@ -64,10 +64,10 @@
         val worker = workerFactory.await(request.id)
         val tester = launchTester(workManager.getWorkInfoByIdFlow(request.id))
         val runningWorkInfo = tester.awaitNext()
-        assertThat(runningWorkInfo.state).isEqualTo(WorkInfo.State.RUNNING)
+        assertThat(runningWorkInfo!!.state).isEqualTo(WorkInfo.State.RUNNING)
         runnableScheduler.executedFutureRunnables(TimeUnit.HOURS.toMillis(2))
         val stopInfo = tester.awaitNext()
-        assertThat(stopInfo.state).isEqualTo(WorkInfo.State.ENQUEUED)
+        assertThat(stopInfo!!.state).isEqualTo(WorkInfo.State.ENQUEUED)
         assertThat(worker.stopReason).isEqualTo(STOP_REASON_TIMEOUT)
     }
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
index 0bf15bf..be10c1d 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
@@ -84,7 +84,7 @@
         val finishedLatch = CountDownLatch(1)
         env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(dependency.id).observeForever {
-                if (it.state == WorkInfo.State.SUCCEEDED) finishedLatch.countDown()
+                if (it?.state == WorkInfo.State.SUCCEEDED) finishedLatch.countDown()
             }
         }
         assertThat(finishedLatch.await(5, TimeUnit.SECONDS)).isTrue()
@@ -112,7 +112,7 @@
         val finishedLatch = CountDownLatch(1)
         env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(workRequest.id).observeForever {
-                if (it.state == WorkInfo.State.FAILED) finishedLatch.countDown()
+                if (it?.state == WorkInfo.State.FAILED) finishedLatch.countDown()
             }
         }
         assertThat(finishedLatch.await(5, TimeUnit.SECONDS)).isTrue()
@@ -151,7 +151,7 @@
         var running = false
         env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(request.id).observeForever {
-                when (it.state) {
+                when (it?.state) {
                     WorkInfo.State.RUNNING -> {
                         launcher.stopWork(scheduler.tokens.remove(request.stringId).first())
                         running = true
@@ -206,7 +206,7 @@
         val worker = factory.awaitWorker(request.id) as LatchWorker
         env.taskExecutor.mainThreadExecutor.execute {
             wm.getWorkInfoByIdLiveData(request.id).observeForever {
-                when (it.state) {
+                when (it?.state) {
                     WorkInfo.State.RUNNING -> {
                         running = true
                         worker.mLatch.countDown()
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt
index 8bdf6a9..fe10651 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/StopReasonTest.kt
@@ -34,6 +34,7 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
@@ -68,12 +69,12 @@
         workManager.enqueue(request).await()
         val worker = workerFactory.await(request.id)
         val tester = launchTester(workManager.getWorkInfoByIdFlow(request.id))
-        val runningWorkInfo = tester.awaitNext()
+        val runningWorkInfo = tester.awaitNext()!!
         assertThat(runningWorkInfo.state).isEqualTo(WorkInfo.State.RUNNING)
         assertThat(runningWorkInfo.stopReason).isEqualTo(WorkInfo.STOP_REASON_NOT_STOPPED)
 
         fakeChargingTracker.constraintState = false
-        val workInfo = tester.awaitNext()
+        val workInfo = tester.awaitNext()!!
         assertThat(worker.isStopped).isTrue()
         assertThat(worker.stopReason).isEqualTo(STOP_REASON_CONSTRAINT_CHARGING)
         assertThat(workInfo.stopReason).isEqualTo(STOP_REASON_CONSTRAINT_CHARGING)
@@ -84,7 +85,8 @@
         val request = OneTimeWorkRequest.Builder(CompletableWorker::class.java).build()
         workManager.enqueue(request)
         val worker = workerFactory.await(request.id)
-        workManager.getWorkInfoByIdFlow(request.id).first { it.state == WorkInfo.State.RUNNING }
+        workManager.getWorkInfoByIdFlow(request.id).filterNotNull()
+            .first { it.state == WorkInfo.State.RUNNING }
         assertThat(worker.stopReason).isEqualTo(WorkInfo.STOP_REASON_NOT_STOPPED)
     }
 
@@ -93,9 +95,10 @@
         val request = OneTimeWorkRequest.Builder(CompletableWorker::class.java).build()
         workManager.enqueue(request)
         val worker = workerFactory.await(request.id)
-        workManager.getWorkInfoByIdFlow(request.id).first { it.state == WorkInfo.State.RUNNING }
+        workManager.getWorkInfoByIdFlow(request.id).filterNotNull()
+            .first { it.state == WorkInfo.State.RUNNING }
         workManager.cancelWorkById(request.id)
-        val workInfo = workManager.getWorkInfoByIdFlow(request.id)
+        val workInfo = workManager.getWorkInfoByIdFlow(request.id).filterNotNull()
             .first { it.state == WorkInfo.State.CANCELLED }
         assertThat(worker.isStopped).isTrue()
         assertThat(worker.stopReason).isEqualTo(STOP_REASON_CANCELLED_BY_APP)
@@ -108,7 +111,7 @@
             .setInitialDelay(10, TimeUnit.DAYS).build()
         workManager.enqueue(request).await()
         workManager.cancelWorkById(request.id).await()
-        val workInfo = workManager.getWorkInfoById(request.id).await()
+        val workInfo = workManager.getWorkInfoById(request.id).await()!!
         assertThat(workInfo.state).isEqualTo(WorkInfo.State.CANCELLED)
         assertThat(workInfo.stopReason).isEqualTo(WorkInfo.STOP_REASON_NOT_STOPPED)
     }
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
index d274179..365ffe4 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
@@ -70,12 +70,12 @@
         assertThat(tester.awaitNext()).isNull()
         workManager.enqueue(unrelatedRequest)
         workManager.enqueue(request)
-        assertThat(tester.awaitNext().state).isEqualTo(WorkInfo.State.ENQUEUED)
+        assertThat(tester.awaitNext()!!.state).isEqualTo(WorkInfo.State.ENQUEUED)
         fakeChargingTracker.constraintState = true
-        assertThat(tester.awaitNext().state).isEqualTo(WorkInfo.State.RUNNING)
+        assertThat(tester.awaitNext()!!.state).isEqualTo(WorkInfo.State.RUNNING)
         val worker = workerFactory.awaitWorker(request.id) as LatchWorker
         worker.mLatch.countDown()
-        assertThat(tester.awaitNext().state).isEqualTo(WorkInfo.State.SUCCEEDED)
+        assertThat(tester.awaitNext()!!.state).isEqualTo(WorkInfo.State.SUCCEEDED)
     }
 
     @Test
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
index cfc03fa..158a9b2 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
@@ -51,6 +51,7 @@
 import java.util.concurrent.TimeUnit.DAYS
 import java.util.concurrent.TimeUnit.HOURS
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.test.runTest
 import org.junit.After
@@ -83,13 +84,13 @@
     @MediumTest
     fun constraintsUpdate() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true))
             .build()
         workManager.enqueue(oneTimeWorkRequest).result.await()
         val requestId = oneTimeWorkRequest.id
 
-        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(requestId)
             .build()
 
@@ -101,11 +102,11 @@
     @Test
     @MediumTest
     fun updateRunningOneTimeWork() = runTest {
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(CompletableWorker::class.java).build()
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(CompletableWorker::class).build()
         workManager.enqueue(oneTimeWorkRequest).result.await()
         val worker = workerFactory.await(oneTimeWorkRequest.id) as CompletableWorker
         // requiresCharging constraint is faked, so it will never be satisfied
-        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(oneTimeWorkRequest.id)
             .setConstraints(Constraints(requiresCharging = true))
             .build()
@@ -117,10 +118,10 @@
     @Test
     @MediumTest
     fun failFinished() = runTest {
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class).build()
         workManager.enqueue(oneTimeWorkRequest)
         workManager.awaitSuccess(oneTimeWorkRequest.id)
-        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(oneTimeWorkRequest.id)
             .setConstraints(Constraints(requiresCharging = true))
             .build()
@@ -130,10 +131,10 @@
     @Test
     @MediumTest
     fun failWorkDoesntExit() = runTest {
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class).build()
         workManager.enqueue(oneTimeWorkRequest)
         workManager.awaitSuccess(oneTimeWorkRequest.id)
-        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(UUID.randomUUID()).build()
         try {
             workManager.updateWork(updatedRequest).await()
@@ -146,13 +147,13 @@
     @Test
     @MediumTest
     fun updateTags() = runTest {
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setInitialDelay(10, DAYS)
             .addTag("previous")
             .build()
         workManager.enqueue(oneTimeWorkRequest).result.await()
 
-        val updatedWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setInitialDelay(10, DAYS)
             .setId(oneTimeWorkRequest.id)
             .addTag("test")
@@ -160,7 +161,7 @@
         assertThat(workManager.updateWork(updatedWorkRequest).await())
             .isEqualTo(APPLIED_IMMEDIATELY)
 
-        val info = workManager.getWorkInfoByIdFlow(oneTimeWorkRequest.id).first()
+        val info = workManager.getWorkInfoByIdFlow(oneTimeWorkRequest.id).first()!!
         assertThat(info.tags).contains("test")
         assertThat(info.tags).doesNotContain("previous")
     }
@@ -174,7 +175,7 @@
     @Test
     @MediumTest
     fun updateTagsWhileRunning() = runTest {
-        val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val request = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true))
             .addTag("original").build()
         workManager.enqueue(request).result.await()
@@ -185,7 +186,7 @@
         }
         // will add startWork task to the serialTaskExecutor queue
         greedyScheduler.onConstraintsStateChanged(request.workSpec, ConstraintsMet)
-        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true))
             .setId(request.id)
             .addTag("updated")
@@ -203,13 +204,13 @@
     @MediumTest
     fun updateWorkerClass() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true))
             .build()
         workManager.enqueue(oneTimeWorkRequest).result.await()
         val requestId = oneTimeWorkRequest.id
 
-        val updatedRequest = OneTimeWorkRequest.Builder(CompletableWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(CompletableWorker::class)
             .setId(requestId)
             .build()
 
@@ -224,12 +225,12 @@
     @MediumTest
     fun progressReset() = runTest {
         // requiresCharging constraint is faked, so it will be controlled in the test
-        val request = OneTimeWorkRequest.Builder(ProgressWorker::class.java)
+        val request = OneTimeWorkRequest.Builder(ProgressWorker::class)
             .setConstraints(Constraints(requiresCharging = true))
             .build()
         workManager.enqueue(request).result.await()
         fakeChargingTracker.constraintState = true
-        workManager.getWorkInfoByIdFlow(request.id).first {
+        workManager.getWorkInfoByIdFlow(request.id).filterNotNull().first {
             it.state == State.RUNNING && it.progress.size() != 0
         }
         // will trigger worker to be stopped
@@ -238,12 +239,12 @@
 
         assertThat(info.progress).isEqualTo(TEST_DATA)
 
-        val updatedRequest = OneTimeWorkRequest.Builder(ProgressWorker::class.java)
+        val updatedRequest = OneTimeWorkRequest.Builder(ProgressWorker::class)
             .setId(request.id)
             .addTag("bla")
             .build()
         assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_IMMEDIATELY)
-        val updatedInfo = workManager.getWorkInfoByIdFlow(request.id).first()
+        val updatedInfo = workManager.getWorkInfoByIdFlow(request.id).first()!!
         assertThat(updatedInfo.tags).contains("bla")
         assertThat(updatedInfo.progress).isEqualTo(Data.EMPTY)
     }
@@ -252,14 +253,14 @@
     @MediumTest
     fun continuationLeafUpdate() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val step1 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true)).build()
-        val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val step2 = OneTimeWorkRequest.Builder(TestWorker::class).build()
         workManager.beginWith(step1).then(step2).enqueue().result.await()
-        val updatedStep2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedStep2 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(step2.id).addTag("updated").build()
         assertThat(workManager.updateWork(updatedStep2).await()).isEqualTo(APPLIED_IMMEDIATELY)
-        val workInfo = workManager.getWorkInfoById(step2.id).await()
+        val workInfo = workManager.getWorkInfoById(step2.id).await()!!
         assertThat(workInfo.state).isEqualTo(State.BLOCKED)
         assertThat(workInfo.tags).contains("updated")
     }
@@ -268,13 +269,13 @@
     @MediumTest
     fun continuationLeafRoot() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val step1 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true)).build()
-        val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val step2 = OneTimeWorkRequest.Builder(TestWorker::class).build()
         workManager.beginWith(step1).then(step2).enqueue().result.await()
-        val workInfo = workManager.getWorkInfoById(step2.id).await()
+        val workInfo = workManager.getWorkInfoById(step2.id).await()!!
         assertThat(workInfo.state).isEqualTo(State.BLOCKED)
-        val updatedStep1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedStep1 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(step1.id).build()
         assertThat(workManager.updateWork(updatedStep1).await()).isEqualTo(APPLIED_IMMEDIATELY)
         workManager.awaitSuccess(step2.id)
@@ -284,15 +285,15 @@
     @MediumTest
     fun chainsViaExistingPolicyLeafUpdate() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val step1 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true)).build()
-        val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val step2 = OneTimeWorkRequest.Builder(TestWorker::class).build()
         workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step1)
         workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step2)
-        val updatedStep2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedStep2 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(step2.id).addTag("updated").build()
         assertThat(workManager.updateWork(updatedStep2).await()).isEqualTo(APPLIED_IMMEDIATELY)
-        val workInfo = workManager.getWorkInfoById(step2.id).await()
+        val workInfo = workManager.getWorkInfoById(step2.id).await()!!
         assertThat(workInfo.state).isEqualTo(State.BLOCKED)
         assertThat(workInfo.tags).contains("updated")
     }
@@ -301,14 +302,14 @@
     @MediumTest
     fun chainsViaExistingPolicyRootUpdate() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val step1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val step1 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true)).build()
-        val step2 = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val step2 = OneTimeWorkRequest.Builder(TestWorker::class).build()
         workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step1)
         workManager.enqueueUniqueWork("name", ExistingWorkPolicy.APPEND, step2)
-        val workInfo = workManager.getWorkInfoById(step2.id).await()
+        val workInfo = workManager.getWorkInfoById(step2.id).await()!!
         assertThat(workInfo.state).isEqualTo(State.BLOCKED)
-        val updatedStep1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updatedStep1 = OneTimeWorkRequest.Builder(TestWorker::class)
             .setId(step1.id).build()
         assertThat(workManager.updateWork(updatedStep1).await()).isEqualTo(APPLIED_IMMEDIATELY)
         workManager.awaitSuccess(step2.id)
@@ -318,12 +319,11 @@
     @MediumTest
     fun oneTimeWorkToPeriodic() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val request = OneTimeWorkRequest.Builder(TestWorker::class)
             .setConstraints(Constraints(requiresCharging = true)).build()
         workManager.enqueue(request).result.await()
         val updatedRequest =
-            PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
-                .build()
+            PeriodicWorkRequest.Builder(TestWorker::class, 1, DAYS).build()
         try {
             workManager.updateWork(updatedRequest).await()
             throw AssertionError()
@@ -336,11 +336,11 @@
     @MediumTest
     fun periodicWorkToOneTime() = runTest {
         // requiresCharging constraint is faked, so it will never be satisfied
-        val request = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
+        val request = PeriodicWorkRequest.Builder(TestWorker::class, 1, DAYS)
             .setConstraints(Constraints(requiresCharging = true))
             .build()
         workManager.enqueue(request).result.await()
-        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
+        val updatedRequest = OneTimeWorkRequest.Builder(TestWorker::class).build()
         try {
             workManager.updateWork(updatedRequest).await()
             throw AssertionError()
@@ -352,11 +352,11 @@
     @Test
     @MediumTest
     fun updateRunningPeriodicWorkRequest() = runTest {
-        val request = PeriodicWorkRequest.Builder(CompletableWorker::class.java, 1, DAYS)
+        val request = PeriodicWorkRequest.Builder(CompletableWorker::class, 1, DAYS)
             .addTag("original").build()
         workManager.enqueue(request).result.await()
         val updatedRequest =
-            PeriodicWorkRequest.Builder(CompletableWorker::class.java, 1, DAYS)
+            PeriodicWorkRequest.Builder(CompletableWorker::class, 1, DAYS)
                 .setId(request.id).addTag("updated").build()
         val worker = workerFactory.await(request.id) as CompletableWorker
         assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_FOR_NEXT_RUN)
@@ -365,7 +365,7 @@
         assertThat(worker.tags).doesNotContain("updated")
         worker.result.complete(Result.success())
         workManager.awaitWorkerEnqueued(request.id)
-        val newTags = workManager.getWorkInfoById(request.id).await().tags
+        val newTags = workManager.getWorkInfoById(request.id).await()!!.tags
         assertThat(newTags).contains("updated")
         assertThat(newTags).doesNotContain("original")
     }
@@ -373,21 +373,21 @@
     @MediumTest
     @Test
     fun updatePeriodicWorkAfterFirstPeriod() = runTest {
-        val request = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
+        val request = PeriodicWorkRequest.Builder(TestWorker::class, 1, DAYS)
             .addTag("original").build()
         workManager.enqueue(request).result.await()
         workerFactory.await(request.id)
         workManager.awaitWorkerEnqueued(request.id)
 
         val updatedRequest =
-            PeriodicWorkRequest.Builder(TestWorker::class.java, 1, DAYS)
+            PeriodicWorkRequest.Builder(TestWorker::class, 1, DAYS)
                 // requiresCharging constraint is faked, so it will never be satisfied
                 .setConstraints(Constraints(requiresCharging = true))
                 .setId(request.id).addTag("updated").build()
 
         assertThat(workManager.updateWork(updatedRequest).await()).isEqualTo(APPLIED_IMMEDIATELY)
 
-        val newTags = workManager.getWorkInfoById(request.id).await().tags
+        val newTags = workManager.getWorkInfoById(request.id).await()!!.tags
         assertThat(newTags).contains("updated")
         assertThat(newTags).doesNotContain("original")
         val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
@@ -397,7 +397,7 @@
     @MediumTest
     @Test
     fun updateRetryingOneTimeWork() = runTest {
-        val request = OneTimeWorkRequest.Builder(RetryWorker::class.java)
+        val request = OneTimeWorkRequest.Builder(RetryWorker::class)
             .setBackoffCriteria(BackoffPolicy.LINEAR, 10, DAYS)
             .build()
         workManager.enqueue(request)
@@ -413,7 +413,7 @@
             request.stringId,
             spec.lastEnqueueTime - delta
         )
-        val updated = OneTimeWorkRequest.Builder(TestWorker::class.java).setId(request.id)
+        val updated = OneTimeWorkRequest.Builder(TestWorker::class).setId(request.id)
             .setBackoffCriteria(BackoffPolicy.LINEAR, 10, DAYS)
             .build()
         workManager.updateWork(updated).await()
@@ -425,7 +425,7 @@
     @MediumTest
     @Test
     fun updateCorrectNextRunTime() = runTest {
-        val request = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val request = OneTimeWorkRequest.Builder(TestWorker::class)
             .setInitialDelay(10, TimeUnit.MINUTES).build()
         val enqueueTime = System.currentTimeMillis()
         workManager.enqueue(request).result.await()
@@ -433,7 +433,7 @@
             request.stringId,
             enqueueTime - TimeUnit.MINUTES.toMillis(5)
         )
-        val updated = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val updated = OneTimeWorkRequest.Builder(TestWorker::class)
             .setInitialDelay(20, TimeUnit.MINUTES)
             .setId(request.id)
             .build()
@@ -450,10 +450,10 @@
     @MediumTest
     @SdkSuppress(minSdkVersion = 23, maxSdkVersion = 25)
     fun testUpdatePeriodicWorker_preservesConstraintTrackingWorker() = runTest {
-        val originRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        val originRequest = OneTimeWorkRequest.Builder(TestWorker::class)
             .setInitialDelay(10, HOURS).build()
         workManager.enqueue(originRequest).result.await()
-        val updateRequest = OneTimeWorkRequest.Builder(RetryWorker::class.java)
+        val updateRequest = OneTimeWorkRequest.Builder(RetryWorker::class)
             .setId(originRequest.id).setInitialDelay(10, HOURS)
             .setConstraints(Constraints(requiresBatteryNotLow = true))
             .build()
@@ -467,12 +467,12 @@
     @Test
     @MediumTest
     fun updateWorkerGeneration() = runTest {
-        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(WorkerWithParam::class.java)
+        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(WorkerWithParam::class)
             .setInitialDelay(10, DAYS)
             .build()
         workManager.enqueue(oneTimeWorkRequest).result.await()
 
-        val updatedWorkRequest = OneTimeWorkRequest.Builder(WorkerWithParam::class.java)
+        val updatedWorkRequest = OneTimeWorkRequest.Builder(WorkerWithParam::class)
             .setId(oneTimeWorkRequest.id)
             .build()
 
@@ -480,7 +480,7 @@
             .isEqualTo(APPLIED_IMMEDIATELY)
         val worker = workerFactory.await(oneTimeWorkRequest.id) as WorkerWithParam
         assertThat(worker.generation).isEqualTo(1)
-        val workInfo = workManager.getWorkInfoById(oneTimeWorkRequest.id).await()
+        val workInfo = workManager.getWorkInfoById(oneTimeWorkRequest.id).await()!!
         assertThat(workInfo.generation).isEqualTo(1)
     }
 
@@ -491,19 +491,19 @@
         val nextRunTimeMillis = HOURS.toMillis(10)
 
         val request = PeriodicWorkRequest.Builder(
-            TestWorker::class.java, 1, DAYS
+            TestWorker::class, 1, DAYS
         ).setInitialDelay(2, DAYS).build()
         workManager.enqueue(request).result.await()
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .setNextScheduleTimeOverride(nextRunTimeMillis)
                 .build()
         ).await()
 
-        val workInfo = workManager.getWorkInfoById(request.id).await()
+        val workInfo = workManager.getWorkInfoById(request.id).await()!!
         assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(nextRunTimeMillis)
     }
 
@@ -515,29 +515,29 @@
         val overrideScheduleTimeMillis2 = HOURS.toMillis(12)
 
         val request = PeriodicWorkRequest.Builder(
-            TestWorker::class.java, 1, DAYS
+            TestWorker::class, 1, DAYS
         ).setInitialDelay(2, DAYS).build()
         workManager.enqueue(request).result.await()
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .setNextScheduleTimeOverride(overrideScheduleTimeMillis)
                 .build()
         ).await()
-        val workInfo = workManager.getWorkInfoById(request.id).await()
+        val workInfo = workManager.getWorkInfoById(request.id).await()!!
         assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis)
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .setNextScheduleTimeOverride(overrideScheduleTimeMillis2)
                 .build()
         ).await()
 
-        val workInfo2 = workManager.getWorkInfoById(request.id).await()
+        val workInfo2 = workManager.getWorkInfoById(request.id).await()!!
         assertThat(workInfo2.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis2)
     }
 
@@ -548,7 +548,7 @@
         val overrideScheduleTimeMillis = HOURS.toMillis(10)
 
         val request = PeriodicWorkRequest.Builder(
-            TestWorker::class.java, 1, DAYS
+            TestWorker::class, 1, DAYS
         ).setBackoffCriteria(BackoffPolicy.LINEAR, HOURS.toMillis(1), HOURS)
             .setInitialDelay(2, DAYS)
             .build()
@@ -557,13 +557,13 @@
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .setNextScheduleTimeOverride(overrideScheduleTimeMillis)
                 .build()
         ).await()
 
-        val workInfo = workManager.getWorkInfoById(request.id).await()
+        val workInfo = workManager.getWorkInfoById(request.id).await()!!
         assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(overrideScheduleTimeMillis)
         val workSpec = env.db.workSpecDao().getWorkSpec(request.stringId)!!
         // attemptCount is still kept, just not used in the schedule time calculation.
@@ -577,7 +577,7 @@
         val overrideScheduleTimeMillis = HOURS.toMillis(10)
 
         val request = PeriodicWorkRequest.Builder(
-            TestWorker::class.java, 1, DAYS
+            TestWorker::class, 1, DAYS
         ).setInitialDelay(2, DAYS)
             .setNextScheduleTimeOverride(overrideScheduleTimeMillis)
             .build()
@@ -585,14 +585,14 @@
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .clearNextScheduleTimeOverride()
                 .setInitialDelay(2, DAYS)
                 .build()
         ).await()
 
-        val workInfo = workManager.getWorkInfoById(request.id).await()
+        val workInfo = workManager.getWorkInfoById(request.id).await()!!
         assertThat(workInfo.nextScheduleTimeMillis).isEqualTo(
             testClock.currentTimeMillis + DAYS.toMillis(2)
         )
@@ -609,13 +609,13 @@
         testClock.currentTimeMillis = HOURS.toMillis(5)
 
         val request = PeriodicWorkRequest.Builder(
-            TestWorker::class.java, 1, DAYS
+            TestWorker::class, 1, DAYS
         ).setInitialDelay(2, DAYS).build()
         workManager.enqueue(request).result.await()
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .clearNextScheduleTimeOverride()
                 .build()
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt
index b5da863..0dec209 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTestKt.kt
@@ -34,6 +34,7 @@
 import androidx.work.worker.CompletableWorker
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executors
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
@@ -71,7 +72,7 @@
             .complete(Success(workDataOf(intTag to 1, stringTag to "hello")))
         (workerFactory.await(secondWork.id) as CompletableWorker).result
             .complete(Success(workDataOf(intTag to 3)))
-        val info = workManager.getWorkInfoByIdFlow(thirdId)
+        val info = workManager.getWorkInfoByIdFlow(thirdId).filterNotNull()
             .first { it.state == WorkInfo.State.SUCCEEDED }
         assertThat(info.outputData.size()).isEqualTo(2)
         assertThat(info.outputData.getStringArray(stringTag)).isEqualTo(arrayOf("hello"))
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt
index 702d26d..b7ea9b0 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/WorkManagerImplTestKt.kt
@@ -51,7 +51,7 @@
         preferenceUtils.lastCancelAllTimeMillis = 0L
 
         val testLifecycleOwner = TestLifecycleOwner()
-        val cancelAllTimeLiveData = workManager.lastCancelAllTimeMillisLiveData
+        val cancelAllTimeLiveData = workManager.getLastCancelAllTimeMillisLiveData()
         val firstValueLatch = CountDownLatch(1)
         val secondValueLatch = CountDownLatch(1)
         var firstCancelAll = -1L
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt
index 7fb6887..ebc7a95 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.kt
@@ -50,6 +50,7 @@
 import java.util.UUID
 import java.util.concurrent.Executors
 import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
@@ -117,7 +118,7 @@
         workerFactory.await(workerWrapper.workSpecId)
         future.await()
         assertThat(workManager.awaitNotRunning(workerWrapper)).isEqualTo(State.SUCCEEDED)
-        val outputData = workManager.getWorkInfoById(workerWrapper.workSpecId).await().outputData
+        val outputData = workManager.getWorkInfoById(workerWrapper.workSpecId).await()!!.outputData
         assertThat(outputData.getBoolean(TEST_ARGUMENT_NAME, false)).isTrue()
     }
 
@@ -204,7 +205,8 @@
 }
 
 private suspend fun WorkManager.awaitNotRunning(workerWrapper: WorkerWrapper) =
-    getWorkInfoByIdFlow(workerWrapper.workSpecId).first { it.state != State.RUNNING }.state
+    getWorkInfoByIdFlow(workerWrapper.workSpecId).filterNotNull()
+        .first { it.state != State.RUNNING }.state
 
 private val WorkerWrapper.workSpecId
     get() = UUID.fromString(workGenerationalId.workSpecId)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt b/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt
index 3558523..ac464c4 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/testutils/WorkManagerExt.kt
@@ -19,10 +19,11 @@
 import androidx.work.WorkInfo
 import androidx.work.WorkManager
 import java.util.UUID
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 
 suspend fun WorkManager.awaitWorkerFinished(id: UUID): WorkInfo =
-    getWorkInfoByIdFlow(id).first { it.state.isFinished }
+    getWorkInfoByIdFlow(id).filterNotNull().first { it.state.isFinished }
 
 suspend fun WorkManager.awaitWorkerEnqueued(id: UUID): WorkInfo =
-    getWorkInfoByIdFlow(id).first { it.state == WorkInfo.State.ENQUEUED }
+    getWorkInfoByIdFlow(id).filterNotNull().first { it.state == WorkInfo.State.ENQUEUED }
diff --git a/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt
index 37c4559..0f41311 100644
--- a/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt
+++ b/work/work-runtime/src/main/java/androidx/work/OneTimeWorkRequest.kt
@@ -36,6 +36,13 @@
         WorkRequest.Builder<Builder, OneTimeWorkRequest>(workerClass) {
 
         /**
+         * Creates a builder for [OneTimeWorkRequest]s.
+         *
+         * @param workerClass The [ListenableWorker] class to run for this work
+         */
+        constructor(workerClass: KClass<out ListenableWorker>) : this(workerClass.java)
+
+        /**
          * Specifies the [InputMerger] class name for this [OneTimeWorkRequest].
          *
          * Before workers run, they receive input [Data] from their parent workers, as well as
diff --git a/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt
index 6fa29f7..200dd41 100644
--- a/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt
+++ b/work/work-runtime/src/main/java/androidx/work/PeriodicWorkRequest.kt
@@ -21,6 +21,7 @@
 import androidx.work.impl.utils.toMillisCompat
 import java.time.Duration
 import java.util.concurrent.TimeUnit
+import kotlin.reflect.KClass
 
 /**
  * A [WorkRequest] for repeating work.  This work executes multiple times until it is
@@ -88,6 +89,28 @@
          * may run immediately, at the end of the period, or any time in between so long as the
          * other conditions are satisfied at the time. The run time of the
          * [PeriodicWorkRequest] can be restricted to a flex period within an interval (see
+         * `#Builder(Class, long, TimeUnit, long, TimeUnit)`).
+         *
+         * @param workerClass The [ListenableWorker] class to run for this work
+         * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
+         * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
+         */
+        constructor(
+            workerClass: KClass<out ListenableWorker>,
+            repeatInterval: Long,
+            repeatIntervalTimeUnit: TimeUnit
+        ) : super(workerClass.java) {
+            workSpec.setPeriodic(repeatIntervalTimeUnit.toMillis(repeatInterval))
+        }
+
+        /**
+         * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
+         * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval
+         * (subject to OS battery optimizations, such as doze mode). The repeat interval must
+         * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It
+         * may run immediately, at the end of the period, or any time in between so long as the
+         * other conditions are satisfied at the time. The run time of the
+         * [PeriodicWorkRequest] can be restricted to a flex period within an interval (see
          * `#Builder(Class, Duration, Duration)`).
          *
          * @param workerClass The [ListenableWorker] class to run for this work
@@ -102,6 +125,27 @@
         }
 
         /**
+         * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
+         * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval
+         * (subject to OS battery optimizations, such as doze mode). The repeat interval must
+         * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It
+         * may run immediately, at the end of the period, or any time in between so long as the
+         * other conditions are satisfied at the time. The run time of the
+         * [PeriodicWorkRequest] can be restricted to a flex period within an interval (see
+         * `#Builder(Class, Duration, Duration)`).
+         *
+         * @param workerClass The [ListenableWorker] class to run for this work
+         * @param repeatInterval The repeat interval
+         */
+        @RequiresApi(26)
+        constructor(
+            workerClass: KClass<out ListenableWorker>,
+            repeatInterval: Duration
+        ) : super(workerClass.java) {
+            workSpec.setPeriodic(repeatInterval.toMillisCompat())
+        }
+
+        /**
          * Creates a [PeriodicWorkRequest] to run periodically once within the
          * **flex period** of every interval period. See diagram below.  The flex
          * period begins at `repeatInterval - flexInterval` to the end of the interval.
@@ -142,6 +186,40 @@
          * The repeat interval must be greater than or equal to
          * [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must
          * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
+         *  ```
+         * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
+         * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
+         * \____________________________________/\____________________________________/...
+         * interval 1                            interval 2             ...(repeat)
+         * ```
+         *
+         * @param workerClass The [ListenableWorker] class to run for this work
+         * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
+         * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
+         * @param flexInterval The duration in `flexIntervalTimeUnit` units for which this
+         * work repeats from the end of the `repeatInterval`
+         * @param flexIntervalTimeUnit The [TimeUnit] for `flexInterval`
+         */
+        constructor(
+            workerClass: KClass<out ListenableWorker>,
+            repeatInterval: Long,
+            repeatIntervalTimeUnit: TimeUnit,
+            flexInterval: Long,
+            flexIntervalTimeUnit: TimeUnit
+        ) : super(workerClass.java) {
+            workSpec.setPeriodic(
+                repeatIntervalTimeUnit.toMillis(repeatInterval),
+                flexIntervalTimeUnit.toMillis(flexInterval)
+            )
+        }
+
+        /**
+         * Creates a [PeriodicWorkRequest] to run periodically once within the
+         * **flex period** of every interval period. See diagram below.  The flex
+         * period begins at `repeatInterval - flexInterval` to the end of the interval.
+         * The repeat interval must be greater than or equal to
+         * [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must
+         * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
          *
          *  ```
          * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
@@ -165,6 +243,35 @@
         }
 
         /**
+         * Creates a [PeriodicWorkRequest] to run periodically once within the
+         * **flex period** of every interval period. See diagram below.  The flex
+         * period begins at `repeatInterval - flexInterval` to the end of the interval.
+         * The repeat interval must be greater than or equal to
+         * [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must
+         * be greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
+         *
+         *  ```
+         * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
+         * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
+         * \____________________________________/\____________________________________/...
+         * interval 1                            interval 2             ...(repeat)
+         * ```
+         *
+         * @param workerClass The [ListenableWorker] class to run for this work
+         * @param repeatInterval The repeat interval
+         * @param flexInterval The duration in for which this work repeats from the end of the
+         * `repeatInterval`
+         */
+        @RequiresApi(26)
+        constructor(
+            workerClass: KClass<out ListenableWorker>,
+            repeatInterval: Duration,
+            flexInterval: Duration
+        ) : super(workerClass.java) {
+            workSpec.setPeriodic(repeatInterval.toMillisCompat(), flexInterval.toMillisCompat())
+        }
+
+        /**
          * Overrides the next time this work is scheduled to run.
          *
          * Calling this method sets a specific time at which the work will be scheduled to run next,
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkManager.java b/work/work-runtime/src/main/java/androidx/work/WorkManager.java
deleted file mode 100644
index bd3213b..0000000
--- a/work/work-runtime/src/main/java/androidx/work/WorkManager.java
+++ /dev/null
@@ -1,706 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.work;
-
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.lifecycle.LiveData;
-import androidx.work.impl.WorkManagerImpl;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-import kotlinx.coroutines.flow.Flow;
-
-/**
- * WorkManager is the recommended library for persistent work.
- * Scheduled work is guaranteed to execute sometime after its {@link Constraints} are met.
- * WorkManager allows observation of work status and the ability to create complex chains of work.
- * <p>
- * WorkManager uses an underlying job dispatching service when available based on the following
- * criteria:
- * <p><ul>
- * <li>Uses JobScheduler for API 23+
- * <li>Uses a custom AlarmManager + BroadcastReceiver implementation for API 14-22</ul>
- * <p>
- * All work must be done in a {@link ListenableWorker} class.  A simple implementation,
- * {@link Worker}, is recommended as the starting point for most developers.  With the optional
- * dependencies, you can also use {@code CoroutineWorker} or {@code RxWorker}.  All background work
- * is given a maximum of ten minutes to finish its execution.  After this time has expired, the
- * worker will be signalled to stop.
- * <p>
- * There are two types of work supported by WorkManager: {@link OneTimeWorkRequest} and
- * {@link PeriodicWorkRequest}.  You can enqueue requests using WorkManager as follows:
- *
- * <pre class="prettyprint">
- * WorkManager workManager = WorkManager.getInstance(Context);
- * workManager.enqueue(new OneTimeWorkRequest.Builder(FooWorker.class).build());
- * </pre>
- *
- * A {@link WorkRequest} has an associated id that can be used for lookups and observation as
- * follows:
- *
- * <pre class="prettyprint">
- * WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
- * workManager.enqueue(request);
- * LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(request.getId());
- * status.observe(...);
- * </pre>
- *
- * You can also use the id for cancellation:
- *
- * <pre class="prettyprint">
- * WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
- * workManager.enqueue(request);
- * workManager.cancelWorkById(request.getId());
- * </pre>
- *
- * You can chain work as follows:
- *
- * <pre class="prettyprint">
- * WorkRequest request1 = new OneTimeWorkRequest.Builder(FooWorker.class).build();
- * WorkRequest request2 = new OneTimeWorkRequest.Builder(BarWorker.class).build();
- * WorkRequest request3 = new OneTimeWorkRequest.Builder(BazWorker.class).build();
- * workManager.beginWith(request1, request2).then(request3).enqueue();
- * </pre>
- *
- * Each call to {@link #beginWith(OneTimeWorkRequest)} or {@link #beginWith(List)} returns a
- * {@link WorkContinuation} upon which you can call
- * {@link WorkContinuation#then(OneTimeWorkRequest)} or {@link WorkContinuation#then(List)} to
- * chain further work.  This allows for creation of complex chains of work.  For example, to create
- * a chain like this:
- *
- * <pre>
- *            A
- *            |
- *      +----------+
- *      |          |
- *      B          C
- *      |
- *   +----+
- *   |    |
- *   D    E             </pre>
- *
- * you would enqueue them as follows:
- *
- * <pre class="prettyprint">
- * WorkContinuation continuation = workManager.beginWith(A);
- * continuation.then(B).then(D, E).enqueue();  // A is implicitly enqueued here
- * continuation.then(C).enqueue();
- * </pre>
- *
- * Work is eligible for execution when all of its prerequisites are complete.  If any of its
- * prerequisites fail or are cancelled, the work will never run.
- * <p>
- * WorkRequests can accept {@link Constraints}, inputs (see {@link Data}), and backoff criteria.
- * WorkRequests can be tagged with human-readable Strings
- * (see {@link WorkRequest.Builder#addTag(String)}), and chains of work can be given a
- * uniquely-identifiable name (see
- * {@link #beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)}).
- * <p>
- * <h3 id="initializing">Initializing WorkManager</h3>
- * <p>
- * By default, WorkManager auto-initializes itself using a built-in {@code ContentProvider}.
- * ContentProviders are created and run before the {@code Application} object, so this allows the
- * WorkManager singleton to be setup before your code can run in most cases.  This is suitable for
- * most developers.  However, you can provide a custom {@link Configuration} by using
- * {@link Configuration.Provider} or
- * {@link WorkManager#initialize(android.content.Context, androidx.work.Configuration)}.
- * <p>
- * <h3 id="worker_class_names">Renaming and Removing ListenableWorker Classes</h3>
- * <p>
- * Exercise caution in renaming classes derived from {@link ListenableWorker}s.  WorkManager stores
- * the class name in its internal database when the {@link WorkRequest} is enqueued so it can later
- * create an instance of that worker when constraints are met.  Unless otherwise specified in the
- * WorkManager {@link Configuration}, this is done in the default {@link WorkerFactory} which tries
- * to reflectively create the ListenableWorker object.  Therefore, renaming or removing these
- * classes is dangerous - if there is pending work with the given class, it will fail permanently
- * if the class cannot be found.  If you are using a custom WorkerFactory, make sure you properly
- * handle cases where the class is not found so that your code does not crash.
- * <p>
- * In case it is desirable to rename a class, implement a custom WorkerFactory that instantiates the
- * right ListenableWorker for the old class name.
- * */
-// Suppressing Metalava checks for added abstract methods in WorkManager.
-// WorkManager cannot be extended, because the constructor is marked @Restricted
-@SuppressLint("AddedAbstractMethod")
-public abstract class WorkManager {
-
-    /**
-     * Retrieves the {@code default} singleton instance of {@link WorkManager}.
-     *
-     * @return The singleton instance of {@link WorkManager}; this may be {@code null} in unusual
-     *         circumstances where you have disabled automatic initialization and have failed to
-     *         manually call {@link #initialize(Context, Configuration)}.
-     * @throws IllegalStateException If WorkManager is not initialized properly as per the exception
-     *                               message.
-     * @deprecated Call {@link WorkManager#getInstance(Context)} instead.
-     */
-    @Deprecated
-    public static @NonNull WorkManager getInstance() {
-        WorkManager workManager = WorkManagerImpl.getInstance();
-        if (workManager == null) {
-            throw new IllegalStateException("WorkManager is not initialized properly.  The most "
-                    + "likely cause is that you disabled WorkManagerInitializer in your manifest "
-                    + "but forgot to call WorkManager#initialize in your Application#onCreate or a "
-                    + "ContentProvider.");
-        } else {
-            return workManager;
-        }
-    }
-
-    /**
-     * Retrieves the {@code default} singleton instance of {@link WorkManager}.
-     *
-     * @param context A {@link Context} for on-demand initialization.
-     * @return The singleton instance of {@link WorkManager}; this may be {@code null} in unusual
-     *         circumstances where you have disabled automatic initialization and have failed to
-     *         manually call {@link #initialize(Context, Configuration)}.
-     * @throws IllegalStateException If WorkManager is not initialized properly
-     */
-    public static @NonNull WorkManager getInstance(@NonNull Context context) {
-        return WorkManagerImpl.getInstance(context);
-    }
-
-    /**
-     * Used to do a one-time initialization of the {@link WorkManager} singleton with a custom
-     * {@link Configuration}.  By default, this method should not be called because WorkManager is
-     * automatically initialized.  To initialize WorkManager yourself, please follow these steps:
-     * <p><ul>
-     * <li>Disable {@code androidx.work.WorkManagerInitializer} in your manifest.
-     * <li>Invoke this method in {@code Application#onCreate} or a {@code ContentProvider}. Note
-     * that this method <b>must</b> be invoked in one of these two places or you risk getting a
-     * {@code NullPointerException} in {@link #getInstance(Context)}.
-     * </ul></p>
-     * <p>
-     * This method throws an {@link IllegalStateException} when attempting to initialize in
-     * direct boot mode.
-     * <p>
-     * This method throws an exception if it is called multiple times.
-     *
-     * @param context A {@link Context} object for configuration purposes. Internally, this class
-     *                will call {@link Context#getApplicationContext()}, so you may safely pass in
-     *                any Context without risking a memory leak.
-     * @param configuration The {@link Configuration} for used to set up WorkManager.
-     * @see Configuration.Provider for on-demand initialization.
-     */
-    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
-        WorkManagerImpl.initialize(context, configuration);
-    }
-
-    /**
-     * Provides a way to check if {@link WorkManager} is initialized in this process.
-     *
-     * @return {@code true} if {@link WorkManager} has been initialized in this process.
-     */
-    public static boolean isInitialized() {
-        return WorkManagerImpl.isInitialized();
-    }
-
-    /**
-     * Provides the {@link Configuration} instance that {@link WorkManager} was initialized with.
-     *
-     * @return The {@link Configuration} instance that {@link WorkManager} was initialized with.
-     */
-    @NonNull
-    public abstract Configuration getConfiguration();
-
-    /**
-     * Enqueues one item for background processing.
-     *
-     * @param workRequest The {@link WorkRequest} to enqueue
-     * @return An {@link Operation} that can be used to determine when the enqueue has completed
-     */
-    @NonNull
-    public final Operation enqueue(@NonNull WorkRequest workRequest) {
-        return enqueue(Collections.singletonList(workRequest));
-    }
-
-    /**
-     * Enqueues one or more items for background processing.
-     *
-     * @param requests One or more {@link WorkRequest} to enqueue
-     * @return An {@link Operation} that can be used to determine when the enqueue has completed
-     */
-    @NonNull
-    public abstract Operation enqueue(@NonNull List<? extends WorkRequest> requests);
-
-    /**
-     * Begins a chain with one or more {@link OneTimeWorkRequest}s, which can be enqueued together
-     * in the future using {@link WorkContinuation#enqueue()}.
-     * <p>
-     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
-     * and will never run.
-     *
-     * @param work One or more {@link OneTimeWorkRequest} to start a chain of work
-     * @return A {@link WorkContinuation} that allows for further chaining of dependent
-     *         {@link OneTimeWorkRequest}
-     */
-    public final @NonNull WorkContinuation beginWith(@NonNull OneTimeWorkRequest work) {
-        return beginWith(Collections.singletonList(work));
-    }
-
-    /**
-     * Begins a chain with one or more {@link OneTimeWorkRequest}s, which can be enqueued together
-     * in the future using {@link WorkContinuation#enqueue()}.
-     * <p>
-     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
-     * and will never run.
-     *
-     * @param work One or more {@link OneTimeWorkRequest} to start a chain of work
-     * @return A {@link WorkContinuation} that allows for further chaining of dependent
-     *         {@link OneTimeWorkRequest}
-     */
-    public abstract @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work);
-
-    /**
-     * This method allows you to begin unique chains of work for situations where you only want one
-     * chain with a given name to be active at a time.  For example, you may only want one sync
-     * operation to be active.  If there is one pending, you can choose to let it run or replace it
-     * with your new work.
-     * <p>
-     * The {@code uniqueWorkName} uniquely identifies this set of work.
-     * <p>
-     * If this method determines that new work should be enqueued and run, all records of previous
-     * work with {@code uniqueWorkName} will be pruned.  If this method determines that new work
-     * should NOT be run, then the entire chain will be considered a no-op.
-     * <p>
-     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
-     * and will never run.  This is particularly important if you are using {@code APPEND} as your
-     * {@link ExistingWorkPolicy}.
-     *
-     * @param uniqueWorkName A unique name which for this chain of work
-     * @param existingWorkPolicy An {@link ExistingWorkPolicy}
-     * @param work The {@link OneTimeWorkRequest} to enqueue. {@code REPLACE} ensures that if there
-     *             is pending work labelled with {@code uniqueWorkName}, it will be cancelled and
-     *             the new work will run. {@code KEEP} will run the new sequence of work only if
-     *             there is no pending work labelled with {@code uniqueWorkName}.  {@code APPEND}
-     *             will create a new sequence of work if there is no existing work with
-     *             {@code uniqueWorkName}; otherwise, {@code work} will be added as a child of all
-     *             leaf nodes labelled with {@code uniqueWorkName}.
-     * @return A {@link WorkContinuation} that allows further chaining
-     */
-    public final @NonNull WorkContinuation beginUniqueWork(
-            @NonNull String uniqueWorkName,
-            @NonNull ExistingWorkPolicy existingWorkPolicy,
-            @NonNull OneTimeWorkRequest work) {
-        return beginUniqueWork(uniqueWorkName, existingWorkPolicy, Collections.singletonList(work));
-    }
-
-    /**
-     * This method allows you to begin unique chains of work for situations where you only want one
-     * chain with a given name to be active at a time.  For example, you may only want one sync
-     * operation to be active.  If there is one pending, you can choose to let it run or replace it
-     * with your new work.
-     * <p>
-     * The {@code uniqueWorkName} uniquely identifies this set of work.
-     * <p>
-     * If this method determines that new work should be enqueued and run, all records of previous
-     * work with {@code uniqueWorkName} will be pruned.  If this method determines that new work
-     * should NOT be run, then the entire chain will be considered a no-op.
-     * <p>
-     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
-     * and will never run.  This is particularly important if you are using {@code APPEND} as your
-     * {@link ExistingWorkPolicy}.
-     *
-     * @param uniqueWorkName A unique name which for this chain of work
-     * @param existingWorkPolicy An {@link ExistingWorkPolicy}; see below for more information
-     * @param work One or more {@link OneTimeWorkRequest} to enqueue. {@code REPLACE} ensures that
-     *             if there is pending work labelled with {@code uniqueWorkName}, it will be
-     *             cancelled and the new work will run. {@code KEEP} will run the new sequence of
-     *             work only if there is no pending work labelled with {@code uniqueWorkName}.
-     *             {@code APPEND} will create a new sequence of work if there is no
-     *             existing work with {@code uniqueWorkName}; otherwise, {@code work} will be added
-     *             as a child of all leaf nodes labelled with {@code uniqueWorkName}.
-     * @return A {@link WorkContinuation} that allows further chaining
-     */
-    public abstract @NonNull WorkContinuation beginUniqueWork(
-            @NonNull String uniqueWorkName,
-            @NonNull ExistingWorkPolicy existingWorkPolicy,
-            @NonNull List<OneTimeWorkRequest> work);
-
-
-    /**
-     * This method allows you to enqueue {@code work} requests to a uniquely-named
-     * {@link WorkContinuation}, where only one continuation of a particular name can be active at
-     * a time. For example, you may only want one sync operation to be active. If there is one
-     * pending, you can choose to let it run or replace it with your new work.
-     * <p>
-     * The {@code uniqueWorkName} uniquely identifies this {@link WorkContinuation}.
-     *
-     * @param uniqueWorkName A unique name which for this operation
-     * @param existingWorkPolicy An {@link ExistingWorkPolicy}; see below for more information
-     * @param work The {@link OneTimeWorkRequest}s to enqueue. {@code REPLACE} ensures that if there
-     *             is pending work labelled with {@code uniqueWorkName}, it will be cancelled and
-     *             the new work will run. {@code KEEP} will run the new OneTimeWorkRequests only if
-     *             there is no pending work labelled with {@code uniqueWorkName}.  {@code APPEND}
-     *             will append the OneTimeWorkRequests as leaf nodes labelled with
-     *             {@code uniqueWorkName}.
-     * @return An {@link Operation} that can be used to determine when the enqueue has completed
-     */
-    @NonNull
-    public Operation enqueueUniqueWork(
-            @NonNull String uniqueWorkName,
-            @NonNull ExistingWorkPolicy existingWorkPolicy,
-            @NonNull OneTimeWorkRequest work) {
-        return enqueueUniqueWork(
-                uniqueWorkName,
-                existingWorkPolicy,
-                Collections.singletonList(work));
-    }
-
-    /**
-     * This method allows you to enqueue {@code work} requests to a uniquely-named
-     * {@link WorkContinuation}, where only one continuation of a particular name can be active at
-     * a time. For example, you may only want one sync operation to be active. If there is one
-     * pending, you can choose to let it run or replace it with your new work.
-     * <p>
-     * The {@code uniqueWorkName} uniquely identifies this {@link WorkContinuation}.
-     *
-     * @param uniqueWorkName A unique name which for this operation
-     * @param existingWorkPolicy An {@link ExistingWorkPolicy}
-     * @param work {@link OneTimeWorkRequest}s to enqueue. {@code REPLACE} ensures
-     *                     that if there is pending work labelled with {@code uniqueWorkName}, it
-     *                     will be cancelled and the new work will run. {@code KEEP} will run the
-     *                     new OneTimeWorkRequests only if there is no pending work labelled with
-     *                     {@code uniqueWorkName}. {@code APPEND} will append the
-     *                     OneTimeWorkRequests as leaf nodes labelled with {@code uniqueWorkName}.
-     * @return An {@link Operation} that can be used to determine when the enqueue has completed
-     */
-    @NonNull
-    public abstract Operation enqueueUniqueWork(
-            @NonNull String uniqueWorkName,
-            @NonNull ExistingWorkPolicy existingWorkPolicy,
-            @NonNull List<OneTimeWorkRequest> work);
-
-    /**
-     * This method allows you to enqueue a uniquely-named {@link PeriodicWorkRequest}, where only
-     * one PeriodicWorkRequest of a particular name can be active at a time.  For example, you may
-     * only want one sync operation to be active.  If there is one pending, you can choose to let it
-     * run or replace it with your new work.
-     * <p>
-     * The {@code uniqueWorkName} uniquely identifies this PeriodicWorkRequest.
-     *
-     * @param uniqueWorkName A unique name which for this operation
-     * @param existingPeriodicWorkPolicy An {@link ExistingPeriodicWorkPolicy}
-     * @param periodicWork A {@link PeriodicWorkRequest} to enqueue. {@code REPLACE} ensures that if
-     *                     there is pending work labelled with {@code uniqueWorkName}, it will be
-     *                     cancelled and the new work will run. {@code KEEP} will run the new
-     *                     PeriodicWorkRequest only if there is no pending work labelled with
-     *                     {@code uniqueWorkName}.
-     * @return An {@link Operation} that can be used to determine when the enqueue has completed
-     */
-    @NonNull
-    public abstract Operation enqueueUniquePeriodicWork(
-            @NonNull String uniqueWorkName,
-            @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
-            @NonNull PeriodicWorkRequest periodicWork);
-
-    /**
-     * Cancels work with the given id if it isn't finished.  Note that cancellation is a best-effort
-     * policy and work that is already executing may continue to run.  Upon cancellation,
-     * {@link ListenableWorker#onStopped()} will be invoked for any affected workers.
-     *
-     * @param id The id of the work
-     * @return An {@link Operation} that can be used to determine when the cancelWorkById has
-     * completed
-     */
-    public abstract @NonNull Operation cancelWorkById(@NonNull UUID id);
-
-    /**
-     * Cancels all unfinished work with the given tag.  Note that cancellation is a best-effort
-     * policy and work that is already executing may continue to run.  Upon cancellation,
-     * {@link ListenableWorker#onStopped()} will be invoked for any affected workers.
-     *
-     * @param tag The tag used to identify the work
-     * @return An {@link Operation} that can be used to determine when the cancelAllWorkByTag has
-     * completed
-     */
-    public abstract @NonNull Operation cancelAllWorkByTag(@NonNull String tag);
-
-    /**
-     * Cancels all unfinished work in the work chain with the given name.  Note that cancellation is
-     * a best-effort policy and work that is already executing may continue to run.  Upon
-     * cancellation, {@link ListenableWorker#onStopped()} will be invoked for any affected workers.
-     *
-     * @param uniqueWorkName The unique name used to identify the chain of work
-     * @return An {@link Operation} that can be used to determine when the cancelUniqueWork has
-     * completed
-     */
-    public abstract @NonNull Operation cancelUniqueWork(@NonNull String uniqueWorkName);
-
-    /**
-     * Cancels all unfinished work.  <b>Use this method with extreme caution!</b>  By invoking it,
-     * you will potentially affect other modules or libraries in your codebase.  It is strongly
-     * recommended that you use one of the other cancellation methods at your disposal.
-     * <p>
-     * Upon cancellation, {@link ListenableWorker#onStopped()} will be invoked for any affected
-     * workers.
-     *
-     * @return An {@link Operation} that can be used to determine when the cancelAllWork has
-     * completed
-     */
-    public abstract @NonNull Operation cancelAllWork();
-
-    /**
-     * Creates a {@link PendingIntent} which can be used to cancel a {@link WorkRequest} with the
-     * given {@code id}.
-     *
-     * @param id      The {@link WorkRequest} id.
-     * @return The {@link PendingIntent} that can be used to cancel the {@link WorkRequest}.
-     */
-    public abstract @NonNull PendingIntent createCancelPendingIntent(@NonNull UUID id);
-
-    /**
-     * Prunes all eligible finished work from the internal database.  Eligible work must be finished
-     * ({@link WorkInfo.State#SUCCEEDED}, {@link WorkInfo.State#FAILED}, or
-     * {@link WorkInfo.State#CANCELLED}), with zero unfinished dependents.
-     * <p>
-     * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in
-     * your codebase) will no longer be able to observe the {@link WorkInfo} of the pruned work.
-     * You do not normally need to call this method - WorkManager takes care to auto-prune its work
-     * after a sane period of time.  This method also ignores the
-     * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy.
-     *
-     * @return An {@link Operation} that can be used to determine when the pruneWork has
-     * completed
-     */
-    public abstract @NonNull Operation pruneWork();
-
-    /**
-     * Gets a {@link LiveData} of the last time all work was cancelled.  This method is intended for
-     * use by library and module developers who have dependent data in their own repository that
-     * must be updated or deleted in case someone cancels their work without their prior knowledge.
-     *
-     * @return A {@link LiveData} of the timestamp ({@code System#getCurrentTimeMillis()}) when
-     *         {@link #cancelAllWork()} was last invoked; this timestamp may be {@code 0L} if this
-     *         never occurred
-     */
-    public abstract @NonNull LiveData<Long> getLastCancelAllTimeMillisLiveData();
-
-    /**
-     * Gets a {@link ListenableFuture} of the last time all work was cancelled.  This method is
-     * intended for use by library and module developers who have dependent data in their own
-     * repository that must be updated or deleted in case someone cancels their work without
-     * their prior knowledge.
-     *
-     * @return A {@link ListenableFuture} of the timestamp ({@code System#getCurrentTimeMillis()})
-     *         when {@link #cancelAllWork()} was last invoked; this timestamp may be {@code 0L} if
-     *         this never occurred
-     */
-    public abstract @NonNull ListenableFuture<Long> getLastCancelAllTimeMillis();
-
-    /**
-     * Gets a {@link LiveData} of the {@link WorkInfo} for a given work id.
-     *
-     * @param id The id of the work
-     * @return A {@link LiveData} of the {@link WorkInfo} associated with {@code id}; note that
-     *         this {@link WorkInfo} may be {@code null} if {@code id} is not known to
-     *         WorkManager.
-     */
-    public abstract @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id);
-
-    /**
-     * Gets a {@link Flow} of the {@link WorkInfo} for a given work id.
-     *
-     * @param id The id of the work
-     * @return A {@link Flow} of the {@link WorkInfo} associated with {@code id}; note that
-     *         this {@link WorkInfo} may be {@code null} if {@code id} is not known to
-     *         WorkManager.
-     */
-    public abstract @NonNull Flow<WorkInfo> getWorkInfoByIdFlow(@NonNull UUID id);
-
-    /**
-     * Gets a {@link ListenableFuture} of the {@link WorkInfo} for a given work id.
-     *
-     * @param id The id of the work
-     * @return A {@link ListenableFuture} of the {@link WorkInfo} associated with {@code id};
-     * note that this {@link WorkInfo} may be {@code null} if {@code id} is not known to
-     * WorkManager
-     */
-    public abstract @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id);
-
-    /**
-     * Gets a {@link LiveData} of the {@link WorkInfo} for all work for a given tag.
-     *
-     * @param tag The tag of the work
-     * @return A {@link LiveData} list of {@link WorkInfo} for work tagged with {@code tag}
-     */
-    public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(
-            @NonNull String tag);
-
-    /**
-     * Gets a {@link Flow} of the {@link WorkInfo} for all work for a given tag.
-     *
-     * @param tag The tag of the work
-     * @return A {@link Flow} list of {@link WorkInfo} for work tagged with {@code tag}
-     */
-    public abstract @NonNull Flow<List<WorkInfo>> getWorkInfosByTagFlow(@NonNull String tag);
-
-    /**
-     * Gets a {@link ListenableFuture} of the {@link WorkInfo} for all work for a given tag.
-     *
-     * @param tag The tag of the work
-     * @return A {@link ListenableFuture} list of {@link WorkInfo} for work tagged with
-     * {@code tag}
-     */
-    public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosByTag(
-            @NonNull String tag);
-
-    /**
-     * Gets a {@link LiveData} of the {@link WorkInfo} for all work in a work chain with a given
-     * unique name.
-     *
-     * @param uniqueWorkName The unique name used to identify the chain of work
-     * @return A {@link LiveData} of the {@link WorkInfo} for work in the chain named
-     *         {@code uniqueWorkName}
-     */
-    public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosForUniqueWorkLiveData(
-            @NonNull String uniqueWorkName);
-
-    /**
-     * Gets a {@link Flow} of the {@link WorkInfo} for all work in a work chain with a given
-     * unique name.
-     *
-     * @param uniqueWorkName The unique name used to identify the chain of work
-     * @return A {@link Flow} of the {@link WorkInfo} for work in the chain named
-     *         {@code uniqueWorkName}
-     */
-    public abstract @NonNull Flow<List<WorkInfo>> getWorkInfosForUniqueWorkFlow(
-            @NonNull String uniqueWorkName);
-
-    /**
-     * Gets a {@link ListenableFuture} of the {@link WorkInfo} for all work in a work chain
-     * with a given unique name.
-     *
-     * @param uniqueWorkName The unique name used to identify the chain of work
-     * @return A {@link ListenableFuture} of the {@link WorkInfo} for work in the chain named
-     *         {@code uniqueWorkName}
-     */
-    public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(
-            @NonNull String uniqueWorkName);
-
-    /**
-     * Gets the {@link LiveData} of the {@link List} of {@link WorkInfo} for all work
-     * referenced by the {@link WorkQuery} specification.
-     *
-     * @param workQuery The work query specification
-     * @return A {@link LiveData} of the {@link List} of {@link WorkInfo} for work
-     * referenced by this {@link WorkQuery}.
-     */
-    public abstract @NonNull LiveData<List<WorkInfo>> getWorkInfosLiveData(
-            @NonNull WorkQuery workQuery);
-
-    /**
-     * Gets the {@link Flow} of the {@link List} of {@link WorkInfo} for all work
-     * referenced by the {@link WorkQuery} specification.
-     *
-     * @param workQuery The work query specification
-     * @return A {@link Flow} of the {@link List} of {@link WorkInfo} for work
-     * referenced by this {@link WorkQuery}.
-     */
-    public abstract @NonNull Flow<List<WorkInfo>> getWorkInfosFlow(
-            @NonNull WorkQuery workQuery);
-
-    /**
-     * Gets the {@link ListenableFuture} of the {@link List} of {@link WorkInfo} for all work
-     * referenced by the {@link WorkQuery} specification.
-     *
-     * @param workQuery The work query specification
-     * @return A {@link ListenableFuture} of the {@link List} of {@link WorkInfo} for work
-     * referenced by this {@link WorkQuery}.
-     */
-    public abstract @NonNull ListenableFuture<List<WorkInfo>> getWorkInfos(
-            @NonNull WorkQuery workQuery);
-
-    /**
-     * Updates the work with the new specification. A {@link WorkRequest} passed as parameter
-     * must have an id set with {@link WorkRequest.Builder#setId(UUID)} that matches an id of the
-     * previously enqueued work.
-     * <p>
-     * It preserves enqueue time, e.g. if a work was enqueued 3 hours ago and had 6 hours long
-     * initial delay, after the update it would be still eligible for run in 3 hours, assuming
-     * that initial delay wasn't updated.
-     * <p>
-     * If the work being updated is currently running the returned ListenableFuture will be
-     * completed with {@link UpdateResult#APPLIED_FOR_NEXT_RUN}. In this case the current run won't
-     * be interrupted and will continue to rely on previous state of the request, e.g. using
-     * old constraints, tags etc. However, on the next run, e.g. retry of one-time Worker or
-     * another iteration of periodic worker, the new worker specification will be used.
-     * <p>
-     * If the one time work that is updated is already finished the returned ListenableFuture
-     * will be completed with {@link UpdateResult#NOT_APPLIED}.
-     * <p>
-     * If update can be applied immediately, e.g. the updated work isn't currently running,
-     * the returned ListenableFuture will be completed with
-     * {@link UpdateResult#APPLIED_IMMEDIATELY}.
-     * <p>
-     * If the work with the given id ({@code request.getId()}) doesn't exist the returned
-     * ListenableFuture will be completed exceptionally with {@link IllegalArgumentException}.
-     * <p>
-     * Worker type can't be changed, {@link OneTimeWorkRequest} can't be updated to
-     * {@link PeriodicWorkRequest} and otherwise, the returned ListenableFuture will be
-     * completed with {@link IllegalArgumentException}.
-     *
-     * @param request the new specification for the work.
-     * @return a {@link ListenableFuture} that will be successfully completed if the update was
-     * successful. The future will be completed with an exception if the work is already running
-     * or finished.
-     */
-    // consistent with already existent method like getWorkInfos() in WorkManager
-    @SuppressWarnings("AsyncSuffixFuture")
-    @NonNull
-    public abstract ListenableFuture<UpdateResult> updateWork(@NonNull WorkRequest request);
-
-    /**
-     * An enumeration of results for {@link WorkManager#updateWork(WorkRequest)} method.
-     */
-    public enum UpdateResult {
-        /**
-         * An update wasn't applied, because {@code Worker} has already finished.
-         */
-        NOT_APPLIED,
-        /**
-         * An update was successfully applied immediately, meaning
-         * the updated work wasn't currently running in the moment of the request.
-         * See {@link UpdateResult#APPLIED_FOR_NEXT_RUN} for the case of running worker.
-         */
-        APPLIED_IMMEDIATELY,
-        /**
-         * An update was successfully applied, but the worker being updated was running.
-         * This run isn't interrupted and will continue to rely on previous state of the
-         * request, e.g. using old constraints, tags etc. However, on the next run, e.g. retry of
-         * one-time Worker or another iteration of periodic worker, the new worker specification.
-         * will be used.
-         */
-        APPLIED_FOR_NEXT_RUN,
-    }
-
-    /**
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    protected WorkManager() {
-    }
-}
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkManager.kt b/work/work-runtime/src/main/java/androidx/work/WorkManager.kt
new file mode 100644
index 0000000..e9be91f
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/WorkManager.kt
@@ -0,0 +1,686 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.content.Context
+import androidx.lifecycle.LiveData
+import androidx.work.impl.WorkManagerImpl
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.UUID
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * WorkManager is the recommended library for persistent work.
+ * Scheduled work is guaranteed to execute sometime after its [Constraints] are met.
+ * WorkManager allows observation of work status and the ability to create complex chains of work.
+ *
+ * WorkManager uses an underlying job dispatching service when available based on the following
+ * criteria:
+ *
+ *  * Uses JobScheduler for API 23+
+ *  * Uses a custom AlarmManager + BroadcastReceiver implementation for API 14-22
+ *
+ * All work must be done in a [ListenableWorker] class.  A simple implementation,
+ * [Worker], is recommended as the starting point for most developers.  With the optional
+ * dependencies, you can also use `CoroutineWorker` or `RxWorker`.  All background work
+ * is given a maximum of ten minutes to finish its execution.  After this time has expired, the
+ * worker will be signalled to stop.
+ *
+ * There are two types of work supported by WorkManager: [OneTimeWorkRequest] and
+ * [PeriodicWorkRequest].  You can enqueue requests using WorkManager as follows:
+ *
+ * ```
+ * WorkManager workManager = WorkManager.getInstance(Context);
+ * workManager.enqueue(new OneTimeWorkRequest.Builder(FooWorker.class).build());
+ * ```
+ *
+ * A [WorkRequest] has an associated id that can be used for lookups and observation as
+ * follows:
+ *
+ * ```
+ * WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
+ * workManager.enqueue(request);
+ * LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(request.getId());
+ * status.observe(...);
+ * ```
+ *
+ * You can also use the id for cancellation:
+ *
+ * ```
+ * WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
+ * workManager.enqueue(request);
+ * workManager.cancelWorkById(request.getId());
+ * ```
+ *
+ * You can chain work as follows:
+ *
+ * ```
+ * WorkRequest request1 = new OneTimeWorkRequest.Builder(FooWorker.class).build();
+ * WorkRequest request2 = new OneTimeWorkRequest.Builder(BarWorker.class).build();
+ * WorkRequest request3 = new OneTimeWorkRequest.Builder(BazWorker.class).build();
+ * workManager.beginWith(request1, request2).then(request3).enqueue();
+ * ```
+ *
+ * Each call to [beginWith] returns a [WorkContinuation] upon which you can call
+ * [WorkContinuation.then] with a single [OneTimeWorkRequest] or a list of [OneTimeWorkRequest] to
+ * chain further work.  This allows for creation of complex chains of work.  For example, to create
+ * a chain like this:
+ *
+ * ```
+ *            A
+ *            |
+ *      +----------+
+ *      |          |
+ *      B          C
+ *      |
+ *   +----+
+ *   |    |
+ *   D    E
+ * ```
+ *
+ * you would enqueue them as follows:
+ *
+ * ```
+ * WorkContinuation continuation = workManager.beginWith(A);
+ * continuation.then(B).then(D, E).enqueue();  // A is implicitly enqueued here
+ * continuation.then(C).enqueue();
+ * ```
+ *
+ * Work is eligible for execution when all of its prerequisites are complete.  If any of its
+ * prerequisites fail or are cancelled, the work will never run.
+ *
+ * WorkRequests can accept [Constraints], inputs (see [Data]), and backoff criteria.
+ * WorkRequests can be tagged with human-readable Strings
+ * (see [WorkRequest.Builder.addTag]), and chains of work can be given a
+ * uniquely-identifiable name (see [beginUniqueWork]).
+ *
+ * ### Initializing WorkManager
+ *
+ * By default, WorkManager auto-initializes itself using a built-in `ContentProvider`.
+ * ContentProviders are created and run before the `Application` object, so this allows the
+ * WorkManager singleton to be setup before your code can run in most cases.  This is suitable for
+ * most developers.  However, you can provide a custom [Configuration] by using
+ * [Configuration.Provider] or [WorkManager.initialize].
+ *
+ * ### Renaming and Removing ListenableWorker Classes
+ *
+ * Exercise caution in renaming classes derived from [ListenableWorker]s. WorkManager stores
+ * the class name in its internal database when the [WorkRequest] is enqueued so it can later
+ * create an instance of that worker when constraints are met. Unless otherwise specified in the
+ * WorkManager [Configuration], this is done in the default [WorkerFactory] which tries
+ * to reflectively create the ListenableWorker object. Therefore, renaming or removing these
+ * classes is dangerous - if there is pending work with the given class, it will fail permanently
+ * if the class cannot be found.  If you are using a custom WorkerFactory, make sure you properly
+ * handle cases where the class is not found so that your code does not crash.
+ *
+ * In case it is desirable to rename a class, implement a custom WorkerFactory that instantiates the
+ * right ListenableWorker for the old class name.
+ */
+// Suppressing Metalava checks for added abstract methods in WorkManager.
+// WorkManager cannot be extended, because the constructor is marked @Restricted
+@SuppressLint("AddedAbstractMethod")
+abstract class WorkManager internal constructor() {
+
+    companion object {
+        /**
+         * Retrieves the `default` singleton instance of [WorkManager].
+         *
+         * @return The singleton instance of [WorkManager]; this may be `null` in unusual
+         * circumstances where you have disabled automatic initialization and have failed to
+         * manually call [initialize].
+         * @throws IllegalStateException If WorkManager is not initialized properly as per
+         * the exception message.
+         */
+        // `open` modifier was added to avoid errors in WorkManagerImpl:
+        // "WorkManagerImpl cannot override <X> in WorkManager", even though methods are static
+        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
+        @Deprecated(
+            message = "Use the overload receiving Context",
+            replaceWith = ReplaceWith("WorkManager.getContext(context)"),
+        )
+        @JvmStatic
+        open fun getInstance(): WorkManager {
+            @Suppress("DEPRECATION")
+            val workManager: WorkManager? = WorkManagerImpl.getInstance()
+            checkNotNull(workManager) {
+                "WorkManager is not initialized properly.  The most " +
+                    "likely cause is that you disabled WorkManagerInitializer in your manifest " +
+                    "but forgot to call WorkManager#initialize in your Application#onCreate or a " +
+                    "ContentProvider."
+            }
+            return workManager
+        }
+
+        /**
+         * Retrieves the `default` singleton instance of [WorkManager].
+         *
+         * @param context A [Context] for on-demand initialization.
+         * @return The singleton instance of [WorkManager]; this may be `null` in unusual
+         * circumstances where you have disabled automatic initialization and have failed to
+         * manually call [initialize].
+         * @throws IllegalStateException If WorkManager is not initialized properly
+         */
+        // `open` modifier was added to avoid errors in WorkManagerImpl:
+        // "WorkManagerImpl cannot override <X> in WorkManager", even though methods are static
+        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
+        @JvmStatic
+        open fun getInstance(context: Context): WorkManager {
+            return WorkManagerImpl.getInstance(context)
+        }
+
+        /**
+         * Used to do a one-time initialization of the [WorkManager] singleton with a custom
+         * [Configuration]. By default, this method should not be called because WorkManager is
+         * automatically initialized. To initialize WorkManager yourself, please follow these steps:
+         *
+         *  * Disable `androidx.work.WorkManagerInitializer` in your manifest.
+         *  * Invoke this method in `Application#onCreate` or a `ContentProvider`. Note
+         * that this method **must** be invoked in one of these two places or you risk getting a
+         * `NullPointerException` in [getInstance].
+         *
+         * This method throws an [IllegalStateException] when attempting to initialize in
+         * direct boot mode.
+         *
+         * This method throws an exception if it is called multiple times.
+         *
+         * @param context A [Context] object for configuration purposes. Internally, this class
+         * will call [Context.getApplicationContext], so you may safely pass in
+         * any Context without risking a memory leak.
+         * @param configuration The [Configuration] for used to set up WorkManager.
+         * @see Configuration.Provider for on-demand initialization.
+         */
+        // `open` modifier was added to avoid errors in WorkManagerImpl:
+        // "WorkManagerImpl cannot override <X> in WorkManager", even though methods are static
+        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
+        @JvmStatic
+        open fun initialize(context: Context, configuration: Configuration) {
+            WorkManagerImpl.initialize(context, configuration)
+        }
+
+        /**
+         * Provides a way to check if [WorkManager] is initialized in this process.
+         *
+         * @return `true` if [WorkManager] has been initialized in this process.
+         */
+        @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
+        @JvmStatic
+        open fun isInitialized(): Boolean = WorkManagerImpl.isInitialized()
+    }
+
+    /**
+     * The [Configuration] instance that [WorkManager] was initialized with.
+     */
+    abstract val configuration: Configuration
+
+    /**
+     * Enqueues one item for background processing.
+     *
+     * @param request The [WorkRequest] to enqueue
+     * @return An [Operation] that can be used to determine when the enqueue has completed
+     */
+    fun enqueue(request: WorkRequest): Operation {
+        return enqueue(listOf(request))
+    }
+
+    /**
+     * Enqueues one or more items for background processing.
+     *
+     * @param requests One or more [WorkRequest] to enqueue
+     * @return An [Operation] that can be used to determine when the enqueue has completed
+     */
+    abstract fun enqueue(requests: List<WorkRequest>): Operation
+
+    /**
+     * Begins a chain with one or more [OneTimeWorkRequest]s, which can be enqueued together
+     * in the future using [WorkContinuation.enqueue].
+     *
+     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
+     * and will never run.
+     *
+     * @param request One or more [OneTimeWorkRequest] to start a chain of work
+     * @return A [WorkContinuation] that allows for further chaining of dependent
+     * [OneTimeWorkRequest]
+     */
+    fun beginWith(request: OneTimeWorkRequest): WorkContinuation {
+        return beginWith(listOf(request))
+    }
+
+    /**
+     * Begins a chain with one or more [OneTimeWorkRequest]s, which can be enqueued together
+     * in the future using [WorkContinuation.enqueue].
+     *
+     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
+     * and will never run.
+     *
+     * @param requests One or more [OneTimeWorkRequest] to start a chain of work
+     * @return A [WorkContinuation] that allows for further chaining of dependent
+     * [OneTimeWorkRequest]
+     */
+    abstract fun beginWith(requests: List<OneTimeWorkRequest>): WorkContinuation
+
+    /**
+     * This method allows you to begin unique chains of work for situations where you only want one
+     * chain with a given name to be active at a time.  For example, you may only want one sync
+     * operation to be active.  If there is one pending, you can choose to let it run or replace it
+     * with your new work.
+     *
+     * The `uniqueWorkName` uniquely identifies this set of work.
+     *
+     * If this method determines that new work should be enqueued and run, all records of previous
+     * work with `uniqueWorkName` will be pruned.  If this method determines that new work
+     * should NOT be run, then the entire chain will be considered a no-op.
+     *
+     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
+     * and will never run.  This is particularly important if you are using `APPEND` as your
+     * [ExistingWorkPolicy].
+     *
+     * @param uniqueWorkName A unique name which for this chain of work
+     * @param existingWorkPolicy An [ExistingWorkPolicy]
+     * @param request The [OneTimeWorkRequest] to enqueue. `REPLACE` ensures that if there
+     * is pending work labelled with `uniqueWorkName`, it will be cancelled and
+     * the new work will run. `KEEP` will run the new sequence of work only if
+     * there is no pending work labelled with `uniqueWorkName`.  `APPEND`
+     * will create a new sequence of work if there is no existing work with
+     * `uniqueWorkName`; otherwise, `work` will be added as a child of all
+     * leaf nodes labelled with `uniqueWorkName`.
+     * @return A [WorkContinuation] that allows further chaining
+     */
+    fun beginUniqueWork(
+        uniqueWorkName: String,
+        existingWorkPolicy: ExistingWorkPolicy,
+        request: OneTimeWorkRequest
+    ): WorkContinuation {
+        return beginUniqueWork(uniqueWorkName, existingWorkPolicy, listOf(request))
+    }
+
+    /**
+     * This method allows you to begin unique chains of work for situations where you only want one
+     * chain with a given name to be active at a time.  For example, you may only want one sync
+     * operation to be active.  If there is one pending, you can choose to let it run or replace it
+     * with your new work.
+     *
+     * The `uniqueWorkName` uniquely identifies this set of work.
+     *
+     * If this method determines that new work should be enqueued and run, all records of previous
+     * work with `uniqueWorkName` will be pruned.  If this method determines that new work
+     * should NOT be run, then the entire chain will be considered a no-op.
+     *
+     * If any work in the chain fails or is cancelled, all of its dependent work inherits that state
+     * and will never run.  This is particularly important if you are using `APPEND` as your
+     * [ExistingWorkPolicy].
+     *
+     * @param uniqueWorkName A unique name which for this chain of work
+     * @param existingWorkPolicy An [ExistingWorkPolicy]; see below for more information
+     * @param requests One or more [OneTimeWorkRequest] to enqueue. `REPLACE` ensures that
+     * if there is pending work labelled with `uniqueWorkName`, it will be
+     * cancelled and the new work will run. `KEEP` will run the new sequence of
+     * work only if there is no pending work labelled with `uniqueWorkName`.
+     * `APPEND` will create a new sequence of work if there is no
+     * existing work with `uniqueWorkName`; otherwise, `work` will be added
+     * as a child of all leaf nodes labelled with `uniqueWorkName`.
+     * @return A [WorkContinuation] that allows further chaining
+     */
+    abstract fun beginUniqueWork(
+        uniqueWorkName: String,
+        existingWorkPolicy: ExistingWorkPolicy,
+        requests: List<OneTimeWorkRequest>
+    ): WorkContinuation
+
+    /**
+     * This method allows you to enqueue `work` requests to a uniquely-named
+     * [WorkContinuation], where only one continuation of a particular name can be active at
+     * a time. For example, you may only want one sync operation to be active. If there is one
+     * pending, you can choose to let it run or replace it with your new work.
+     *
+     * The `uniqueWorkName` uniquely identifies this [WorkContinuation].
+     *
+     * @param uniqueWorkName A unique name which for this operation
+     * @param existingWorkPolicy An [ExistingWorkPolicy]; see below for more information
+     * @param request The [OneTimeWorkRequest]s to enqueue. `REPLACE` ensures that if there
+     * is pending work labelled with `uniqueWorkName`, it will be cancelled and
+     * the new work will run. `KEEP` will run the new OneTimeWorkRequests only if
+     * there is no pending work labelled with `uniqueWorkName`.  `APPEND`
+     * will append the OneTimeWorkRequests as leaf nodes labelled with
+     * `uniqueWorkName`.
+     * @return An [Operation] that can be used to determine when the enqueue has completed
+     */
+    open fun enqueueUniqueWork(
+        uniqueWorkName: String,
+        existingWorkPolicy: ExistingWorkPolicy,
+        request: OneTimeWorkRequest
+    ): Operation {
+        return enqueueUniqueWork(uniqueWorkName, existingWorkPolicy, listOf(request))
+    }
+
+    /**
+     * This method allows you to enqueue `work` requests to a uniquely-named
+     * [WorkContinuation], where only one continuation of a particular name can be active at
+     * a time. For example, you may only want one sync operation to be active. If there is one
+     * pending, you can choose to let it run or replace it with your new work.
+     *
+     * The `uniqueWorkName` uniquely identifies this [WorkContinuation].
+     *
+     * @param uniqueWorkName A unique name which for this operation
+     * @param existingWorkPolicy An [ExistingWorkPolicy]
+     * @param requests [OneTimeWorkRequest]s to enqueue. `REPLACE` ensures
+     * that if there is pending work labelled with `uniqueWorkName`, it
+     * will be cancelled and the new work will run. `KEEP` will run the
+     * new OneTimeWorkRequests only if there is no pending work labelled with
+     * `uniqueWorkName`. `APPEND` will append the
+     * OneTimeWorkRequests as leaf nodes labelled with `uniqueWorkName`.
+     * @return An [Operation] that can be used to determine when the enqueue has completed
+     */
+    abstract fun enqueueUniqueWork(
+        uniqueWorkName: String,
+        existingWorkPolicy: ExistingWorkPolicy,
+        requests: List<OneTimeWorkRequest>
+    ): Operation
+
+    /**
+     * This method allows you to enqueue a uniquely-named [PeriodicWorkRequest], where only
+     * one PeriodicWorkRequest of a particular name can be active at a time.  For example, you may
+     * only want one sync operation to be active.  If there is one pending, you can choose to let it
+     * run or replace it with your new work.
+     *
+     * The `uniqueWorkName` uniquely identifies this PeriodicWorkRequest.
+     *
+     * @param uniqueWorkName A unique name which for this operation
+     * @param existingPeriodicWorkPolicy An [ExistingPeriodicWorkPolicy]
+     * @param request A [PeriodicWorkRequest] to enqueue. `REPLACE` ensures that if
+     * there is pending work labelled with `uniqueWorkName`, it will be
+     * cancelled and the new work will run. `KEEP` will run the new
+     * PeriodicWorkRequest only if there is no pending work labelled with
+     * `uniqueWorkName`.
+     * @return An [Operation] that can be used to determine when the enqueue has completed
+     */
+    abstract fun enqueueUniquePeriodicWork(
+        uniqueWorkName: String,
+        existingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy,
+        request: PeriodicWorkRequest
+    ): Operation
+
+    /**
+     * Cancels work with the given id if it isn't finished.  Note that cancellation is a best-effort
+     * policy and work that is already executing may continue to run. Upon cancellation,
+     * [ListenableFuture] returned by [ListenableWorker.startWork] will be cancelled. Also
+     * [ListenableWorker.onStopped] will be invoked for any affected workers.
+     *
+     * @param id The id of the work
+     * @return An [Operation] that can be used to determine when the cancelWorkById has
+     * completed
+     */
+    abstract fun cancelWorkById(id: UUID): Operation
+
+    /**
+     * Cancels all unfinished work with the given tag.  Note that cancellation is a best-effort
+     * policy and work that is already executing may continue to run. Upon cancellation,
+     * [ListenableFuture] returned by [ListenableWorker.startWork] will be cancelled. Also
+     * [ListenableWorker.onStopped] will be invoked for any affected workers.
+     *
+     * @param tag The tag used to identify the work
+     * @return An [Operation] that can be used to determine when the cancelAllWorkByTag has
+     * completed
+     */
+    abstract fun cancelAllWorkByTag(tag: String): Operation
+
+    /**
+     * Cancels all unfinished work in the work chain with the given name.  Note that cancellation is
+     * a best-effort policy and work that is already executing may continue to run. Upon
+     * cancellation, [ListenableFuture] returned by [ListenableWorker.startWork] will be cancelled.
+     * Also [ListenableWorker.onStopped] will be invoked for any affected workers.
+     *
+     * @param uniqueWorkName The unique name used to identify the chain of work
+     * @return An [Operation] that can be used to determine when the cancelUniqueWork has
+     * completed
+     */
+    abstract fun cancelUniqueWork(uniqueWorkName: String): Operation
+
+    /**
+     * Cancels all unfinished work.  **Use this method with extreme caution!**  By invoking it,
+     * you will potentially affect other modules or libraries in your codebase.  It is strongly
+     * recommended that you use one of the other cancellation methods at your disposal.
+     *
+     * Upon cancellation, [ListenableFuture] returned by [ListenableWorker.startWork] will be
+     * cancelled. Also [ListenableWorker.onStopped] will be invoked for any affected workers.
+     *
+     * @return An [Operation] that can be used to determine when the cancelAllWork has
+     * completed
+     */
+    abstract fun cancelAllWork(): Operation
+
+    /**
+     * Creates a [PendingIntent] which can be used to cancel a [WorkRequest] with the
+     * given `id`.
+     *
+     * @param id      The [WorkRequest] id.
+     * @return The [PendingIntent] that can be used to cancel the [WorkRequest].
+     */
+    abstract fun createCancelPendingIntent(id: UUID): PendingIntent
+
+    /**
+     * Prunes all eligible finished work from the internal database.  Eligible work must be finished
+     * ([WorkInfo.State.SUCCEEDED], [WorkInfo.State.FAILED], or
+     * [WorkInfo.State.CANCELLED]), with zero unfinished dependents.
+     *
+     * **Use this method with caution**; by invoking it, you (and any modules and libraries in
+     * your codebase) will no longer be able to observe the [WorkInfo] of the pruned work.
+     * You do not normally need to call this method - WorkManager takes care to auto-prune its work
+     * after a sane period of time.  This method also ignores the
+     * [OneTimeWorkRequest.Builder.keepResultsForAtLeast] policy.
+     *
+     * @return An [Operation] that can be used to determine when the pruneWork has
+     * completed
+     */
+    abstract fun pruneWork(): Operation
+
+    /**
+     * Gets a [LiveData] of the last time all work was cancelled.  This method is intended for
+     * use by library and module developers who have dependent data in their own repository that
+     * must be updated or deleted in case someone cancels their work without their prior knowledge.
+     *
+     * @return A [LiveData] of the timestamp (`System#getCurrentTimeMillis()`) when
+     * [cancelAllWork] was last invoked; this timestamp may be `0L` if this
+     * never occurred
+     */
+    abstract fun getLastCancelAllTimeMillisLiveData(): LiveData<Long>
+
+    /**
+     * Gets a [ListenableFuture] of the last time all work was cancelled.  This method is
+     * intended for use by library and module developers who have dependent data in their own
+     * repository that must be updated or deleted in case someone cancels their work without
+     * their prior knowledge.
+     *
+     * @return A [ListenableFuture] of the timestamp (`System#getCurrentTimeMillis()`)
+     * when [cancelAllWork] was last invoked; this timestamp may be `0L` if
+     * this never occurred
+     */
+    abstract fun getLastCancelAllTimeMillis(): ListenableFuture<Long>
+
+    /**
+     * Gets a [LiveData] of the [WorkInfo] for a given work id.
+     *
+     * @param id The id of the work
+     * @return A [LiveData] of the [WorkInfo] associated with `id`; note that
+     * this [WorkInfo] may be `null` if `id` is not known to WorkManager.
+     */
+    abstract fun getWorkInfoByIdLiveData(id: UUID): LiveData<WorkInfo?>
+
+    /**
+     * Gets a [Flow] of the [WorkInfo] for a given work id.
+     *
+     * @param id The id of the work
+     * @return A [Flow] of the [WorkInfo] associated with `id`; note that
+     * this [WorkInfo] may be `null` if `id` is not known to WorkManager.
+     */
+    abstract fun getWorkInfoByIdFlow(id: UUID): Flow<WorkInfo?>
+
+    /**
+     * Gets a [ListenableFuture] of the [WorkInfo] for a given work id.
+     *
+     * @param id The id of the work
+     * @return A [ListenableFuture] of the [WorkInfo] associated with `id`;
+     * note that this [WorkInfo] may be `null` if `id` is not known to WorkManager
+     */
+    abstract fun getWorkInfoById(id: UUID): ListenableFuture<WorkInfo?>
+
+    /**
+     * Gets a [LiveData] of the [WorkInfo] for all work for a given tag.
+     *
+     * @param tag The tag of the work
+     * @return A [LiveData] list of [WorkInfo] for work tagged with `tag`
+     */
+    abstract fun getWorkInfosByTagLiveData(tag: String): LiveData<List<WorkInfo>>
+
+    /**
+     * Gets a [Flow] of the [WorkInfo] for all work for a given tag.
+     *
+     * @param tag The tag of the work
+     * @return A [Flow] list of [WorkInfo] for work tagged with `tag`
+     */
+    abstract fun getWorkInfosByTagFlow(tag: String): Flow<List<WorkInfo>>
+
+    /**
+     * Gets a [ListenableFuture] of the [WorkInfo] for all work for a given tag.
+     *
+     * @param tag The tag of the work
+     * @return A [ListenableFuture] list of [WorkInfo] for work tagged with `tag`
+     */
+    abstract fun getWorkInfosByTag(tag: String): ListenableFuture<List<WorkInfo>>
+
+    /**
+     * Gets a [LiveData] of the [WorkInfo] for all work in a work chain with a given
+     * unique name.
+     *
+     * @param uniqueWorkName The unique name used to identify the chain of work
+     * @return A [LiveData] of the [WorkInfo] for work in the chain named `uniqueWorkName`
+     */
+    abstract fun getWorkInfosForUniqueWorkLiveData(uniqueWorkName: String): LiveData<List<WorkInfo>>
+
+    /**
+     * Gets a [Flow] of the [WorkInfo] for all work in a work chain with a given
+     * unique name.
+     *
+     * @param uniqueWorkName The unique name used to identify the chain of work
+     * @return A [Flow] of the [WorkInfo] for work in the chain named `uniqueWorkName`
+     */
+    abstract fun getWorkInfosForUniqueWorkFlow(uniqueWorkName: String): Flow<List<WorkInfo>>
+
+    /**
+     * Gets a [ListenableFuture] of the [WorkInfo] for all work in a work chain
+     * with a given unique name.
+     *
+     * @param uniqueWorkName The unique name used to identify the chain of work
+     * @return A [ListenableFuture] of the [WorkInfo] for work in the chain named `uniqueWorkName`
+     */
+    abstract fun getWorkInfosForUniqueWork(uniqueWorkName: String): ListenableFuture<List<WorkInfo>>
+
+    /**
+     * Gets the [LiveData] of the [List] of [WorkInfo] for all work
+     * referenced by the [WorkQuery] specification.
+     *
+     * @param workQuery The work query specification
+     * @return A [LiveData] of the [List] of [WorkInfo] for work
+     * referenced by this [WorkQuery].
+     */
+    abstract fun getWorkInfosLiveData(workQuery: WorkQuery): LiveData<List<WorkInfo>>
+
+    /**
+     * Gets the [Flow] of the [List] of [WorkInfo] for all work
+     * referenced by the [WorkQuery] specification.
+     *
+     * @param workQuery The work query specification
+     * @return A [Flow] of the [List] of [WorkInfo] for work
+     * referenced by this [WorkQuery].
+     */
+    abstract fun getWorkInfosFlow(workQuery: WorkQuery): Flow<List<WorkInfo>>
+
+    /**
+     * Gets the [ListenableFuture] of the [List] of [WorkInfo] for all work
+     * referenced by the [WorkQuery] specification.
+     *
+     * @param workQuery The work query specification
+     * @return A [ListenableFuture] of the [List] of [WorkInfo] for work
+     * referenced by this [WorkQuery].
+     */
+    abstract fun getWorkInfos(workQuery: WorkQuery): ListenableFuture<List<WorkInfo>>
+
+    /**
+     * Updates the work with the new specification. A [WorkRequest] passed as parameter
+     * must have an id set with [WorkRequest.Builder.setId] that matches an id of the
+     * previously enqueued work.
+     *
+     * It preserves enqueue time, e.g. if a work was enqueued 3 hours ago and had 6 hours long
+     * initial delay, after the update it would be still eligible for run in 3 hours, assuming
+     * that initial delay wasn't updated.
+     *
+     * If the work being updated is currently running the returned ListenableFuture will be
+     * completed with [UpdateResult.APPLIED_FOR_NEXT_RUN]. In this case the current run won't
+     * be interrupted and will continue to rely on previous state of the request, e.g. using
+     * old constraints, tags etc. However, on the next run, e.g. retry of one-time Worker or
+     * another iteration of periodic worker, the new worker specification will be used.
+     *
+     * If the one time work that is updated is already finished the returned ListenableFuture
+     * will be completed with [UpdateResult.NOT_APPLIED].
+     *
+     * If update can be applied immediately, e.g. the updated work isn't currently running,
+     * the returned ListenableFuture will be completed with
+     * [UpdateResult.APPLIED_IMMEDIATELY].
+     *
+     * If the work with the given id (`request.getId()`) doesn't exist the returned
+     * ListenableFuture will be completed exceptionally with [IllegalArgumentException].
+     *
+     * Worker type can't be changed, [OneTimeWorkRequest] can't be updated to
+     * [PeriodicWorkRequest] and otherwise, the returned ListenableFuture will be
+     * completed with [IllegalArgumentException].
+     *
+     * @param request the new specification for the work.
+     * @return a [ListenableFuture] that will be successfully completed if the update was
+     * successful. The future will be completed with an exception if the work is already running
+     * or finished.
+     */
+    // consistent with already existent method like getWorkInfos() in WorkManager
+    @Suppress("AsyncSuffixFuture")
+    abstract fun updateWork(request: WorkRequest): ListenableFuture<UpdateResult>
+
+    /**
+     * An enumeration of results for [WorkManager.updateWork] method.
+     */
+    enum class UpdateResult {
+        /**
+         * An update wasn't applied, because `Worker` has already finished.
+         */
+        NOT_APPLIED,
+
+        /**
+         * An update was successfully applied immediately, meaning
+         * the updated work wasn't currently running in the moment of the request.
+         * See [UpdateResult.APPLIED_FOR_NEXT_RUN] for the case of running worker.
+         */
+        APPLIED_IMMEDIATELY,
+
+        /**
+         * An update was successfully applied, but the worker being updated was running.
+         * This run isn't interrupted and will continue to rely on previous state of the
+         * request, e.g. using old constraints, tags etc. However, on the next run, e.g. retry of
+         * one-time Worker or another iteration of periodic worker, the new worker specification.
+         * will be used.
+         */
+        APPLIED_FOR_NEXT_RUN,
+    }
+}
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 1ff6900..b2d4e6c 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -351,32 +351,33 @@
     }
 
     @Override
-    public @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work) {
-        if (work.isEmpty()) {
+    public @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> requests) {
+        if (requests.isEmpty()) {
             throw new IllegalArgumentException(
                     "beginWith needs at least one OneTimeWorkRequest.");
         }
-        return new WorkContinuationImpl(this, work);
+        return new WorkContinuationImpl(this, requests);
     }
 
     @Override
     public @NonNull WorkContinuation beginUniqueWork(
             @NonNull String uniqueWorkName,
             @NonNull ExistingWorkPolicy existingWorkPolicy,
-            @NonNull List<OneTimeWorkRequest> work) {
-        if (work.isEmpty()) {
+            @NonNull List<OneTimeWorkRequest> requests) {
+        if (requests.isEmpty()) {
             throw new IllegalArgumentException(
                     "beginUniqueWork needs at least one OneTimeWorkRequest.");
         }
-        return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, work);
+        return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, requests);
     }
 
     @NonNull
     @Override
     public Operation enqueueUniqueWork(@NonNull String uniqueWorkName,
             @NonNull ExistingWorkPolicy existingWorkPolicy,
-            @NonNull List<OneTimeWorkRequest> work) {
-        return new WorkContinuationImpl(this, uniqueWorkName, existingWorkPolicy, work).enqueue();
+            @NonNull List<OneTimeWorkRequest> requests) {
+        return new WorkContinuationImpl(this, uniqueWorkName,
+                existingWorkPolicy, requests).enqueue();
     }
 
     @Override
@@ -384,14 +385,14 @@
     public Operation enqueueUniquePeriodicWork(
             @NonNull String uniqueWorkName,
             @NonNull ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy,
-            @NonNull PeriodicWorkRequest periodicWork) {
+            @NonNull PeriodicWorkRequest request) {
         if (existingPeriodicWorkPolicy == ExistingPeriodicWorkPolicy.UPDATE) {
-            return enqueueUniquelyNamedPeriodic(this, uniqueWorkName, periodicWork);
+            return enqueueUniquelyNamedPeriodic(this, uniqueWorkName, request);
         }
         return createWorkContinuationForUniquePeriodicWork(
                 uniqueWorkName,
                 existingPeriodicWorkPolicy,
-                periodicWork)
+                request)
                 .enqueue();
     }
 
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
index 1c15845..9a91fe4 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
@@ -364,7 +364,7 @@
      * @return The time at which the [WorkSpec] was scheduled.
      */
     @Query("SELECT schedule_requested_at FROM workspec WHERE id=:id")
-    fun getScheduleRequestedAtLiveData(id: String): LiveData<Long>
+    fun getScheduleRequestedAtLiveData(id: String): LiveData<Long?>
 
     /**
      * Resets the scheduled state on the [WorkSpec]s that are not in a a completed state.
diff --git a/work/work-testing/api/current.txt b/work/work-testing/api/current.txt
index 9361e19..6af87ec 100644
--- a/work/work-testing/api/current.txt
+++ b/work/work-testing/api/current.txt
@@ -32,7 +32,7 @@
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.testing.TestListenableWorkerBuilder<W> TestListenableWorkerBuilder(android.content.Context context, optional androidx.work.Data inputData, optional java.util.List<java.lang.String> tags, optional int runAttemptCount, optional java.util.List<? extends android.net.Uri> triggeredContentUris, optional java.util.List<java.lang.String> triggeredContentAuthorities);
   }
 
-  public class TestWorkerBuilder<W extends androidx.work.Worker> extends androidx.work.testing.TestListenableWorkerBuilder<W> {
+  public class TestWorkerBuilder<W extends androidx.work.Worker> extends androidx.work.testing.TestListenableWorkerBuilder<W!> {
     method public static androidx.work.testing.TestWorkerBuilder<? extends androidx.work.Worker!> from(android.content.Context, androidx.work.WorkRequest, java.util.concurrent.Executor);
     method public static <W extends androidx.work.Worker> androidx.work.testing.TestWorkerBuilder<W!> from(android.content.Context, Class<W!>, java.util.concurrent.Executor);
   }
diff --git a/work/work-testing/api/restricted_current.txt b/work/work-testing/api/restricted_current.txt
index 9361e19..6af87ec 100644
--- a/work/work-testing/api/restricted_current.txt
+++ b/work/work-testing/api/restricted_current.txt
@@ -32,7 +32,7 @@
     method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.testing.TestListenableWorkerBuilder<W> TestListenableWorkerBuilder(android.content.Context context, optional androidx.work.Data inputData, optional java.util.List<java.lang.String> tags, optional int runAttemptCount, optional java.util.List<? extends android.net.Uri> triggeredContentUris, optional java.util.List<java.lang.String> triggeredContentAuthorities);
   }
 
-  public class TestWorkerBuilder<W extends androidx.work.Worker> extends androidx.work.testing.TestListenableWorkerBuilder<W> {
+  public class TestWorkerBuilder<W extends androidx.work.Worker> extends androidx.work.testing.TestListenableWorkerBuilder<W!> {
     method public static androidx.work.testing.TestWorkerBuilder<? extends androidx.work.Worker!> from(android.content.Context, androidx.work.WorkRequest, java.util.concurrent.Executor);
     method public static <W extends androidx.work.Worker> androidx.work.testing.TestWorkerBuilder<W!> from(android.content.Context, Class<W!>, java.util.concurrent.Executor);
   }
diff --git a/work/work-testing/src/androidTest/java/androidx/work/testing/CustomClockTest.kt b/work/work-testing/src/androidTest/java/androidx/work/testing/CustomClockTest.kt
index 002e439..f594704 100644
--- a/work/work-testing/src/androidTest/java/androidx/work/testing/CustomClockTest.kt
+++ b/work/work-testing/src/androidTest/java/androidx/work/testing/CustomClockTest.kt
@@ -72,7 +72,7 @@
         workManagerImpl.enqueue(listOf(request)).result.get()
 
         val status = workManagerImpl.getWorkInfoById(request.id).get()
-        assertThat(status.nextScheduleTimeMillis)
+        assertThat(status!!.nextScheduleTimeMillis)
             .isEqualTo(testClock.timeMillis + initialDelay.toMillis())
     }
 }
diff --git a/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerRealExecutorTest.kt b/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerRealExecutorTest.kt
index 2089353..8a10c71 100644
--- a/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerRealExecutorTest.kt
+++ b/work/work-testing/src/androidTest/java/androidx/work/testing/TestSchedulerRealExecutorTest.kt
@@ -187,7 +187,7 @@
         }
 
         drainSerialExecutor()
-        assertThat(wm.getWorkInfoById(request.id).get().state).isEqualTo(ENQUEUED)
+        assertThat(wm.getWorkInfoById(request.id).get()!!.state).isEqualTo(ENQUEUED)
         assertThat(CountingTestWorker.COUNT.get()).isEqualTo(1)
     }
 
diff --git a/work/work-testing/src/test/java/androidx/work/testing/TestCoroutineSchedulerTest.kt b/work/work-testing/src/test/java/androidx/work/testing/TestCoroutineSchedulerTest.kt
index f58bf28..53b0595 100644
--- a/work/work-testing/src/test/java/androidx/work/testing/TestCoroutineSchedulerTest.kt
+++ b/work/work-testing/src/test/java/androidx/work/testing/TestCoroutineSchedulerTest.kt
@@ -112,7 +112,7 @@
         val workInfoEarly = workManager.getWorkInfoById(request.id)
         synchronizeThreads()
 
-        assertThat(Futures.getDone(workInfoEarly).state).isEqualTo(ENQUEUED)
+        assertThat(Futures.getDone(workInfoEarly)!!.state).isEqualTo(ENQUEUED)
 
         // Work should run and finish now.
         testCoroutineScheduler.advanceTimeBy(initialDelayMillis)
@@ -121,7 +121,7 @@
         // Verify result
         val workInfoOnTime = workManager.getWorkInfoById(request.id)
         synchronizeThreads()
-        assertThat(Futures.getDone(workInfoOnTime).state).isEqualTo(SUCCEEDED)
+        assertThat(Futures.getDone(workInfoOnTime)!!.state).isEqualTo(SUCCEEDED)
     }
 
     @Test
@@ -138,7 +138,7 @@
         val workInfoEarly = workManager.getWorkInfoById(request.id)
         synchronizeThreads()
 
-        assertThat(Futures.getDone(workInfoEarly).state).isEqualTo(ENQUEUED)
+        assertThat(Futures.getDone(workInfoEarly)!!.state).isEqualTo(ENQUEUED)
 
         // Can't use setXDelayMet with clock-based scheduling
         assertThrows(IllegalStateException::class.java) {