Merge "Revert "passing common-source-path to metalava"" into androidx-main
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/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/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/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 775d422..949b04b 100755
--- a/busytown/androidx_incremental.sh
+++ b/busytown/androidx_incremental.sh
@@ -64,7 +64,6 @@
 else
     # Run Gradle
     if impl/build.sh $DIAGNOSE_ARG buildOnServer checkExternalLicenses listTaskOutputs exportSboms \
-        --profile \
         "$@"; then
     echo build succeeded
     EXIT_VALUE=0
@@ -73,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/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/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/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/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-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/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/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/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/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/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index aa18150..360497e 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
@@ -884,7 +884,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/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/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/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/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/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/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/ContextualFlowLayout.kt
index 1c194ca..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
@@ -455,7 +455,7 @@
         }
         overflow.itemCount = itemCount
         overflow.setOverflowMeasurables(
-            isHorizontal,
+            this@FlowMeasureLazyPolicy,
             constraints
         ) { canExpand, noOfItemsShown ->
             val composableIndex = if (canExpand) 0 else 1
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 c595316..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
@@ -660,9 +660,9 @@
         val collapseMeasurable = measurables.getOrNull(2)?.firstOrNull()
         overflow.itemCount = list.size
         overflow.setOverflowMeasurables(
+            this@FlowMeasurePolicy,
             seeMoreMeasurable,
             collapseMeasurable,
-            isHorizontal,
             constraints,
         )
         return breakDownItems(
@@ -1182,7 +1182,7 @@
     val spacing = ceil(mainAxisSpacingDp.toPx()).toInt()
     val crossAxisSpacing = ceil(crossAxisSpacingDp.toPx()).toInt()
     val subsetConstraints = OrientationIndependentConstraints(
-        mainAxisMin,
+        0,
         mainAxisMax,
         0,
         crossAxisMax
@@ -1347,6 +1347,7 @@
 
     ellipsisWrapInfo?.let {
         measurables.add(it.ellipsis)
+        placeables[measurables.size - 1] = it.placeable
         lineIndex = endBreakLineList.lastIndex
         if (it.placeEllipsisOnLastContentLine) {
             val lastIndex = endBreakLineList.size - 1
@@ -1450,7 +1451,7 @@
 // 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(
+internal fun Measurable.measureAndCache(
     measurePolicy: FlowLineMeasurePolicy,
     constraints: Constraints,
     storePlaceable: (Placeable?) -> Unit
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 ed110b2..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!!
+                )
             }
         }
     }
@@ -810,6 +817,7 @@
             )
             this.seeMoreSize = IntIntPair(mainAxisSize, crossAxisSize)
             this.seeMoreMeasurable = item as? Measurable
+            this.seeMorePlaceable = null
         }
         collapseMeasurable?.let { item ->
             val mainAxisSize = item.mainAxisMin(
@@ -819,11 +827,63 @@
             val crossAxisSize = item.crossAxisMin(isHorizontal, mainAxisSize)
             this.collapseSize = IntIntPair(mainAxisSize, crossAxisSize)
             this.collapseMeasurable = item as? Measurable
+            this.collapsePlaceable = null
         }
     }
 
     internal fun setOverflowMeasurables(
-        isHorizontal: Boolean,
+        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?)
     ) {
@@ -836,9 +896,9 @@
             /* isExpandable */ false, /* numberOfItemsShown */ 0
         )
         setOverflowMeasurables(
+            measurePolicy,
             seeMoreMeasurable,
             collapseMeasurable,
-            isHorizontal,
             constraints
         )
     }
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 032cf78..5d21cbf 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -391,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>);
@@ -1748,9 +1749,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 +1767,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 @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 652aacc..c291ab6 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -393,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>);
@@ -1752,10 +1753,14 @@
     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 @kotlin.PublishedApi internal void finishEditing();
-    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();
-    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;
   }
 
@@ -1767,11 +1772,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 @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..ab1b2e3 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
@@ -91,6 +91,7 @@
         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),
     )
 )
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/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/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..2604fea 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.
@@ -426,41 +425,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 +436,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
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..356546a 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
@@ -46,7 +45,6 @@
     )
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun StringTextField(
     value: String,
@@ -198,20 +196,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/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/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/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..6e216ca 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
@@ -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/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..bc7f1d1 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 90f6621..ff0558e 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
@@ -78,9 +78,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)
+            assertThat(state.selection).isEqualTo(startSelection)
         }
     }
 
@@ -93,7 +93,7 @@
         ) {
             val accepted = drag(Offset(fontSize.toPx() * 2, 10f), defaultUri)
             assertThat(accepted).isTrue()
-            assertThat(state.text.selection).isEqualTo(TextRange(2))
+            assertThat(state.selection).isEqualTo(TextRange(2))
         }
     }
 
@@ -101,7 +101,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,11 +109,11 @@
     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))
         }
     }
 
@@ -125,11 +125,11 @@
             }
         ) {
             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 +140,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))
         }
     }
 
@@ -354,7 +354,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!")
         }
     }
@@ -378,7 +378,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)
@@ -402,7 +402,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)
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/TextFieldScrollTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/TextFieldScrollTest.kt
index 8781298..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)
         }
     }
 
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..f944f79 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
@@ -125,8 +125,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 +186,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 +208,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 +225,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 +247,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 0106d69..2577a43 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
@@ -193,7 +193,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 +206,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 +226,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 +243,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 +262,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 +494,7 @@
 
         selectAllOption?.invoke()
 
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(state.selection).isEqualTo(TextRange(0, 5))
         rule.runOnIdle {
             assertThat(selectAllOption).isNull()
         }
@@ -615,7 +615,7 @@
 
         rule.runOnIdle {
             assertThat(state.text.toString()).isEqualTo("Heworldllo")
-            assertThat(state.text.selection).isEqualTo(TextRange(7))
+            assertThat(state.selection).isEqualTo(TextRange(7))
         }
     }
 
@@ -688,7 +688,7 @@
 
         rule.runOnIdle {
             assertThat(clipboardManager.getText()?.toString()).isEqualTo("Hello")
-            assertThat(state.text.selection).isEqualTo(TextRange(5))
+            assertThat(state.selection).isEqualTo(TextRange(5))
         }
     }
 
@@ -715,7 +715,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 +747,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))
         }
     }
 
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/text/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/AndroidTextInputSession.android.kt
index d90df9e..861830f 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
                     )
                 }
 
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 5aab181..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
@@ -27,6 +28,7 @@
 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
@@ -47,21 +49,21 @@
     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
@@ -290,7 +292,7 @@
             replace(0, 0, "hello")
             placeCursorAtEnd()
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(5))
+        assertThat(state.selection).isEqualTo(TextRange(5))
     }
 
     @Test
@@ -299,7 +301,7 @@
             replace(0, 0, "hello")
             placeCursorBeforeCharAt(2)
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(2))
+        assertThat(state.selection).isEqualTo(TextRange(2))
     }
 
     @Test
@@ -321,7 +323,7 @@
             replace(0, 0, "hello")
             selectAll()
         }
-        assertThat(state.text.selection).isEqualTo(TextRange(0, 5))
+        assertThat(state.selection).isEqualTo(TextRange(0, 5))
     }
 
     @Test
@@ -330,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
@@ -398,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
@@ -466,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))
     }
@@ -479,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 {
@@ -491,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
@@ -502,7 +497,7 @@
         val texts = mutableListOf<TextFieldCharSequence>()
 
         launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
+            state.valueAsFlow().collectLatest { texts += it }
         }
 
         state.edit {
@@ -510,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))
     }
@@ -521,7 +516,7 @@
         val texts = mutableListOf<TextFieldCharSequence>()
 
         launch(Dispatchers.Unconfined) {
-            state.forEachTextValue { texts += it }
+            state.valueAsFlow().collectLatest { texts += it }
         }
 
         state.edit {
@@ -536,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")
     }
 
@@ -547,7 +542,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
+                state.valueAsFlow().collectLatest { texts += it }
             }
 
             state.edit {
@@ -556,7 +551,7 @@
                 placeCursorAtEnd()
             }
 
-            assertThat(texts.last()).isSameInstanceAs(state.text)
+            assertThat(texts.last()).isSameInstanceAs(state.value)
             assertThat(texts.last().toString()).isEqualTo("hello world")
         }
 
@@ -567,7 +562,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
+                state.valueAsFlow().collectLatest { texts += it }
             }
 
             val snapshot = Snapshot.takeMutableSnapshot()
@@ -583,7 +578,7 @@
             snapshot.apply()
             snapshot.dispose()
 
-            assertThat(texts.last()).isSameInstanceAs(state.text)
+            assertThat(texts.last()).isSameInstanceAs(state.value)
         }
 
     @Test
@@ -593,7 +588,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue { texts += it }
+                state.valueAsFlow().collectLatest { texts += it }
             }
 
             val snapshot = Snapshot.takeMutableSnapshot()
@@ -617,7 +612,7 @@
             val texts = mutableListOf<TextFieldCharSequence>()
 
             launch(Dispatchers.Unconfined) {
-                state.forEachTextValue {
+                state.valueAsFlow().collectLatest {
                     texts += it
                     awaitCancellation()
                 }
@@ -631,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/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/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/lazy/LazyLayoutSemanticState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyLayoutSemanticState.kt
index 169b1c5..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
@@ -40,9 +40,7 @@
             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/grid/LazySemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazySemantics.kt
index 208f87c4..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
@@ -46,9 +46,8 @@
                     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/LazyLayoutSemantics.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutSemantics.kt
index ce101ad..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
@@ -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()
@@ -242,7 +263,7 @@
     val maxScrollOffset: Float
 
     fun collectionInfo(): CollectionInfo
-    suspend fun animateScrollBy(delta: Float)
+    suspend fun animateScrollBy(delta: Float): Float
     suspend fun scrollToItem(index: Int)
 }
 
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 1d551a5..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
@@ -46,9 +46,8 @@
                     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/pager/LazyLayoutSemanticState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutSemanticState.kt
index e91f380..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
@@ -30,9 +30,7 @@
     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/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 2a587b9..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
@@ -96,29 +98,67 @@
     private var isEditing: Boolean by mutableStateOf(false)
 
     /**
-     * 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].
+     * 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.
@@ -133,7 +173,7 @@
      * @see setTextAndSelectAll
      */
     inline fun edit(block: TextFieldBuffer.() -> Unit) {
-        val mutableValue = startEdit(text)
+        val mutableValue = startEdit()
         try {
             mutableValue.block()
             commitEdit(mutableValue)
@@ -143,7 +183,7 @@
     }
 
     override fun toString(): String =
-        "TextFieldState(selection=${text.selection}, text=\"$text\")"
+        "TextFieldState(selection=$selection, text=\"$text\")"
 
     /**
      * Undo history controller for this TextFieldState.
@@ -159,7 +199,7 @@
 
     @Suppress("ShowingMemberInHiddenClass")
     @PublishedApi
-    internal fun startEdit(value: TextFieldCharSequence): TextFieldBuffer {
+    internal fun startEdit(): TextFieldBuffer {
         check(!isEditing) {
             "TextFieldState does not support concurrent or nested editing."
         }
@@ -218,7 +258,7 @@
         undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
         block: EditingBuffer.() -> Unit
     ) {
-        val previousValue = text
+        val previousValue = value
 
         mainBuffer.changeTracker.clearChanges()
         mainBuffer.block()
@@ -249,7 +289,7 @@
      * a public API.
      */
     internal inline fun editWithNoSideEffects(block: EditingBuffer.() -> Unit) {
-        val previousValue = text
+        val previousValue = value
 
         mainBuffer.changeTracker.clearChanges()
         mainBuffer.block()
@@ -260,7 +300,7 @@
             composition = mainBuffer.composition
         )
 
-        text = afterEditValue
+        value = afterEditValue
         sendChangesToIme(
             oldValue = previousValue,
             newValue = afterEditValue,
@@ -281,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,
@@ -307,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,
@@ -332,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)
     }
 
     /**
@@ -445,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,
@@ -489,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)
                 }
@@ -514,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
@@ -609,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/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/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 ee16cb3..7cbf897 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -118,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/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/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index c60dfc7..7925121 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -71,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 {
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index c60dfc7..7925121 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -71,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 {
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 c7be96d..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
@@ -376,7 +376,7 @@
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Composable
 private fun SampleThreePaneScaffoldStandardMode() {
-    val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+    val scaffoldDirective = calculatePaneScaffoldDirective(
         currentWindowAdaptiveInfo()
     )
     val scaffoldValue = calculateThreePaneScaffoldValue(
@@ -394,7 +394,7 @@
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 @Composable
 private fun SampleThreePaneScaffoldDenseMode() {
-    val scaffoldDirective = calculateDensePaneScaffoldDirective(
+    val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
         currentWindowAdaptiveInfo()
     )
     val scaffoldValue = calculateThreePaneScaffoldValue(
@@ -415,7 +415,7 @@
     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/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 1d6be9b..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
@@ -33,7 +33,7 @@
 class PaneScaffoldDirectiveTest {
     @Test
     fun test_calculateStandardPaneScaffoldDirective_compactWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(400, 800),
                 Posture()
@@ -48,7 +48,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_mediumWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(750, 900),
                 Posture()
@@ -63,7 +63,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_expandedWidth() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(1200, 800),
                 Posture()
@@ -78,7 +78,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_tabletop() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(isTabletop = true)
@@ -93,7 +93,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_compactWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(400, 800),
                 Posture()
@@ -108,7 +108,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_mediumWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(750, 900),
                 Posture()
@@ -123,7 +123,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_expandedWidth() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(1200, 800),
                 Posture()
@@ -138,7 +138,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_tabletop() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(isTabletop = true)
@@ -153,7 +153,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_alwaysAvoidHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -166,7 +166,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_avoidOccludingHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -179,7 +179,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_avoidSeparatingHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -192,7 +192,7 @@
 
     @Test
     fun test_calculateStandardPaneScaffoldDirective_neverAvoidHinge() {
-        val scaffoldDirective = calculateStandardPaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirective(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -205,7 +205,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_alwaysAvoidHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -218,7 +218,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_avoidOccludingHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -231,7 +231,7 @@
 
     @Test
     fun test_calculateDensePaneScaffoldDirective_avoidSeparatingHinge() {
-        val scaffoldDirective = calculateDensePaneScaffoldDirective(
+        val scaffoldDirective = calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
             WindowAdaptiveInfo(
                 WindowSizeClass(700, 800),
                 Posture(hingeList = hingeList)
@@ -244,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/PaneScaffoldDirective.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt
index c4977e1..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
@@ -30,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.
  *
@@ -43,9 +43,8 @@
  *        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 {
@@ -87,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).
@@ -100,9 +103,8 @@
  *        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 {
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 0540d83..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
@@ -69,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.
  *
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..74742c8a 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,
@@ -186,7 +186,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 +200,7 @@
 @Composable
 fun <T> rememberSupportingPaneScaffoldNavigator(
     scaffoldDirective: PaneScaffoldDirective =
-        calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
+        calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         SupportingPaneScaffoldDefaults.adaptStrategies(),
     isDestinationHistoryAware: Boolean = true,
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/api/current.txt b/compose/material3/material3/api/current.txt
index e58e6b2..db4fc05 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -622,17 +622,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;
   }
@@ -1068,8 +1057,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);
@@ -1213,7 +1204,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();
@@ -1222,6 +1213,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;
@@ -1508,7 +1500,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>);
@@ -1516,6 +1508,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;
@@ -1619,7 +1612,6 @@
   }
 
   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, optional boolean gesturesEnabled, 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);
   }
@@ -1631,7 +1623,6 @@
     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>);
@@ -2105,6 +2096,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;
+  }
+
+  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 e58e6b2..db4fc05 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -622,17 +622,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;
   }
@@ -1068,8 +1057,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);
@@ -1213,7 +1204,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();
@@ -1222,6 +1213,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;
@@ -1508,7 +1500,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>);
@@ -1516,6 +1508,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;
@@ -1619,7 +1612,6 @@
   }
 
   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, optional boolean gesturesEnabled, 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);
   }
@@ -1631,7 +1623,6 @@
     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>);
@@ -2105,6 +2096,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;
+  }
+
+  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/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/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/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/carousel/CarouselTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/carousel/CarouselTest.kt
index 80e6eee..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
@@ -21,6 +21,7 @@
 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
@@ -193,9 +194,12 @@
             add(smallSize)
             add(xSmallSize, isAnchor = true)
         }
-        val strategy = Strategy { _, _ ->
-            keylineList
-        }.apply(availableSpace = 1000f, itemSpacing = 0f)
+        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
@@ -219,7 +223,12 @@
                     add(56f)
                     add(10f, isAnchor = true)
                 }
-            }.apply(availableSpace = 380f, itemSpacing = 8f)
+            }.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
@@ -278,6 +287,7 @@
                 flingBehavior = flingBehavior(state),
                 modifier = modifier.testTag(CarouselTestTag),
                 itemSpacing = 0.dp,
+                contentPadding = PaddingValues(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/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineSnapPositionTest.kt
index 9e32c8e..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
@@ -97,7 +97,12 @@
             add(xSmallSize, isAnchor = true)
         }
 
-        return Strategy { _, _ -> keylineList }.apply(availableSpace = 1000f, itemSpacing = 0f)
+        return Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 1000f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
     }
 
     // Test strategy that is start aligned:
@@ -127,7 +132,12 @@
             add(smallSize)
             add(xSmallSize, isAnchor = true)
         }
-        return Strategy { _, _ -> keylineList }.apply(availableSpace = 1000f, itemSpacing = 0f)
+        return Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 1000f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
     }
 
     // Test strategy that is start aligned:
@@ -150,6 +160,11 @@
             add(smallSize)
             add(xSmallSize, isAnchor = true)
         }
-        return Strategy { _, _ -> keylineList }.apply(1000f, 0f)
+        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/MultiBrowseTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/MultiBrowseTest.kt
index 84738c7..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
@@ -39,7 +39,12 @@
             itemSpacing = 0f,
             itemCount = 10,
         )!!
-        val strategy = Strategy { _, _ -> keylineList }.apply(500f, 0f)
+        val strategy = Strategy { _, _ -> keylineList }.apply(
+            availableSpace = 500f,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy.itemMainAxisSize).isEqualTo(itemSize)
     }
@@ -53,10 +58,12 @@
             preferredItemSize = itemSize,
             itemSpacing = 0f,
             itemCount = 10,
-        )!!
+            )!!
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = 100f,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val minSmallItemSize: Float = with(Density) { CarouselDefaults.MinSmallItemSize.toPx() }
         val keylines = strategy.defaultKeylines
@@ -80,10 +87,12 @@
             preferredItemSize = 200f,
             itemSpacing = 0f,
             itemCount = 10,
-        )!!
+            )!!
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = minSmallItemSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val keylines = strategy.defaultKeylines
 
@@ -118,7 +127,9 @@
             )!!
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = carouselSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val keylines = strategy.defaultKeylines
 
@@ -144,7 +155,12 @@
             itemSpacing = 0f,
             itemCount = 3,
         )!!
-        val strategy = Strategy { _, _ -> keylineList }.apply(carouselSize, 0f)
+        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
@@ -155,6 +171,7 @@
         assertThat(keylines[3].size).isLessThan(keylines[2].size)
     }
 
+    @Test
     fun testMultiBrowse_adjustsForItemSpacing() {
         val keylineList = multiBrowseKeylineList(
             density = Density,
@@ -163,7 +180,12 @@
             itemSpacing = 8f,
             itemCount = 10
         )!!
-        val strategy = Strategy { _, _ -> keylineList }.apply(380f, 8f)
+        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
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 06052ff..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
@@ -36,7 +36,9 @@
 
         val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
             availableSpace = carouselMainAxisSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
 
         assertThat(strategy.getKeylineListForScrollOffset(0f, maxScrollOffset))
@@ -67,7 +69,9 @@
 
         val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
             availableSpace = carouselMainAxisSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val endKeylineList = strategy.getEndKeylines()
 
@@ -89,7 +93,9 @@
 
         val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
             availableSpace = carouselMainAxisSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val startKeylineList = strategy.getStartKeylines()
 
@@ -112,7 +118,9 @@
 
         val strategy = Strategy { _, _ -> defaultKeylines }.apply(
             availableSpace = carouselMainAxisSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
 
         val startSteps = listOf(
@@ -219,7 +227,9 @@
 
         val strategy = Strategy { _, _ -> defaultKeylines }.apply(
             availableSpace = carouselMainAxisSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
 
         val endSteps = listOf(
@@ -354,8 +364,18 @@
         val strategy2 = Strategy { availableSpace, itemSpacingPx ->
             multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        strategy1.apply(availableSpace = 500f, itemSpacing = itemSpacing)
-        strategy2.apply(availableSpace = 500f, itemSpacing = itemSpacing)
+        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())
@@ -372,8 +392,18 @@
         val strategy2 = Strategy { availableSpace, itemSpacingPx ->
             multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        strategy1.apply(availableSpace = 500f, itemSpacing = itemSpacing)
-        strategy2.apply(availableSpace = 500f + 1f, itemSpacing = itemSpacing)
+        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())
@@ -390,7 +420,12 @@
         val strategy2 = Strategy { availableSpace, itemSpacingPx ->
             multiBrowseKeylineList(Density, availableSpace, itemSize, itemSpacingPx, itemCount)
         }
-        strategy1.apply(availableSpace = 500f, itemSpacing = itemSpacing)
+        strategy1.apply(
+            availableSpace = 500f,
+            itemSpacing = itemSpacing,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy1 == strategy2).isFalse()
         assertThat(strategy1.hashCode()).isNotEqualTo(strategy2.hashCode())
@@ -403,7 +438,12 @@
         val maxScrollOffset = (itemCount * large) - carouselMainAxisSize
         val defaultKeylineList = createStartAlignedKeylineList()
 
-        val strategy = Strategy { _, _ -> defaultKeylineList }.apply(carouselMainAxisSize, 0f)
+        val strategy = Strategy { _, _ -> defaultKeylineList }.apply(
+            availableSpace = carouselMainAxisSize,
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy.getKeylineListForScrollOffset(0f, maxScrollOffset))
             .isEqualTo(defaultKeylineList)
@@ -419,7 +459,12 @@
                 add(56f)
                 add(10f, isAnchor = true)
             }
-        }.apply(availableSpace = 380f, itemSpacing = 8f)
+        }.apply(
+            availableSpace = 380f,
+            itemSpacing = 8f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         val middleStep = strategy.endKeylineSteps[1]
         val actualMiddleOffsets = middleStep.map { it.offset }.toFloatArray()
@@ -453,7 +498,12 @@
                 add(56f)
                 add(10f, isAnchor = true)
             }
-        }.apply(availableSpace = 768f, itemSpacing = 8f)
+        }.apply(
+            availableSpace = 768f,
+            itemSpacing = 8f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
+        )
 
         assertThat(strategy.startKeylineSteps).hasSize(3)
         assertThat(strategy.endKeylineSteps).hasSize(3)
@@ -507,6 +557,38 @@
         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,
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 9f7ba239..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
@@ -41,7 +41,9 @@
         )
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = carouselSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val keylines = strategy.defaultKeylines
         val anchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
@@ -67,7 +69,9 @@
         )
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = carouselSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val keylines = strategy.defaultKeylines
         val anchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
@@ -96,7 +100,9 @@
         )
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = carouselSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val keylines = strategy.defaultKeylines
         val rightAnchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
@@ -134,7 +140,9 @@
         )
         val strategy = Strategy { _, _ -> keylineList }.apply(
             availableSpace = carouselSize,
-            itemSpacing = 0f
+            itemSpacing = 0f,
+            beforeContentPadding = 0f,
+            afterContentPadding = 0f
         )
         val keylines = strategy.defaultKeylines
         val rightAnchorSize = with(Density) { CarouselDefaults.AnchorSize.toPx() }
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/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 c55ac5116..05b7a1b 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
@@ -122,25 +122,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
@@ -226,47 +207,6 @@
  * @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.
@@ -332,51 +272,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/carousel/Carousel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
index 1a41903..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
@@ -29,6 +29,9 @@
 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
@@ -65,18 +68,24 @@
  * 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 preferredItemWidth The width the fully visible items would like to be in the main 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
+ * @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 flingBehavior The [TargetedFlingBehavior] to be used for post scroll gestures
@@ -86,13 +95,14 @@
  * @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,
     preferredItemWidth: Dp,
     modifier: Modifier = Modifier,
@@ -101,6 +111,7 @@
         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
@@ -120,6 +131,7 @@
                 )
             }
         },
+        contentPadding = contentPadding,
         modifier = modifier,
         itemSpacing = itemSpacing,
         flingBehavior = flingBehavior,
@@ -140,23 +152,28 @@
  * 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 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,
     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
@@ -173,6 +190,7 @@
                 )
             }
         },
+        contentPadding = contentPadding,
         modifier = modifier,
         itemSpacing = itemSpacing,
         flingBehavior = flingBehavior,
@@ -190,19 +208,22 @@
  * @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 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, itemSpacing: Float) -> KeylineList?,
+    contentPadding: PaddingValues,
     modifier: Modifier = Modifier,
     itemSpacing: Dp = 0.dp,
     flingBehavior: TargetedFlingBehavior =
@@ -210,7 +231,11 @@
     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)
+    }
 
     val outOfBoundsPageCount = remember(pageSize.strategy.itemMainAxisSize) {
         calculateOutOfBounds(pageSize.strategy)
@@ -228,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,
@@ -250,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,
@@ -272,6 +307,28 @@
     }
 }
 
+@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
@@ -297,11 +354,18 @@
  * 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, itemSpacing: Float) -> KeylineList?
+    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(), pageSpacing.toFloat())
+        strategy.apply(
+            availableSpace.toFloat(),
+            pageSpacing.toFloat(),
+            beforeContentPadding,
+            afterContentPadding
+        )
         return if (strategy.isValid()) {
             strategy.itemMainAxisSize.roundToInt()
         } else {
@@ -337,7 +401,7 @@
  * @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(ExperimentalMaterial3Api::class)
 internal fun Modifier.carouselItem(
@@ -503,12 +567,7 @@
  * Contains the default values used by [Carousel].
  */
 @ExperimentalMaterial3Api
-internal object CarouselDefaults {
-    /** The minimum size that a carousel strategy can choose its small items to be. **/
-    val MinSmallItemSize = 40.dp
-
-    /** The maximum size that a carousel strategy can choose its small items to be. **/
-    val MaxSmallItemSize = 56.dp
+object CarouselDefaults {
 
     /**
      * A [TargetedFlingBehavior] that limits a fling to one item at a time. [snapAnimationSpec] can
@@ -602,6 +661,12 @@
         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..1046082 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)
@@ -92,7 +93,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/KeylineSnapPosition.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/KeylineSnapPosition.kt
index c8a7af0..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
@@ -44,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
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 9c654b2..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
@@ -65,10 +65,7 @@
     // 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(
-        minSmallItemSize + itemSpacing,
-        maxSmallItemSize + itemSpacing
-    )
+    val targetSmallSize: Float = (targetLargeSize / 3f).coerceIn(minSmallItemSize, maxSmallItemSize)
     val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f
 
     if (carouselMainAxisSize < minSmallItemSize * 2) {
@@ -108,15 +105,15 @@
         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,
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 5641be1..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,8 +22,11 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.setValue
+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
 
@@ -84,6 +87,8 @@
     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)
 
@@ -101,8 +106,15 @@
      * 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, itemSpacing: 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 && this.itemSpacing == itemSpacing) {
@@ -110,15 +122,16 @@
         }
 
         val keylineList = keylineList.invoke(availableSpace, itemSpacing) ?: return this
-        val startKeylineSteps = getStartKeylineSteps(keylineList, availableSpace, itemSpacing)
+        val startKeylineSteps =
+            getStartKeylineSteps(keylineList, availableSpace, itemSpacing, beforeContentPadding)
         val endKeylineSteps =
-            getEndKeylineSteps(keylineList, availableSpace, itemSpacing)
+            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
@@ -138,6 +151,8 @@
         )
         this.availableSpace = availableSpace
         this.itemSpacing = itemSpacing
+        this.beforeContentPadding = beforeContentPadding
+        this.afterContentPadding = afterContentPadding
         this.itemMainAxisSize = defaultKeylines.firstFocal.size
 
         return this
@@ -229,6 +244,8 @@
         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
@@ -247,6 +264,8 @@
         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()
@@ -277,12 +296,23 @@
         private fun getStartKeylineSteps(
             defaultKeylines: KeylineList,
             carouselMainAxisSize: Float,
-            itemSpacing: 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
             }
 
@@ -329,6 +359,15 @@
                 i++
             }
 
+            if (beforeContentPadding != 0f) {
+                steps[steps.lastIndex] = createShiftedKeylineListForContentPadding(
+                    steps.last(),
+                    carouselMainAxisSize,
+                    itemSpacing,
+                    beforeContentPadding
+                )
+            }
+
             return steps
         }
 
@@ -351,12 +390,21 @@
         private fun getEndKeylineSteps(
             defaultKeylines: KeylineList,
             carouselMainAxisSize: Float,
-            itemSpacing: 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
             }
 
@@ -403,10 +451,54 @@
                 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.
          */
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/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/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/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/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/api/current.txt b/compose/ui/ui/api/current.txt
index 355ba52..d7a30f9 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -3201,6 +3201,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>> {
@@ -3319,6 +3329,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();
@@ -3346,6 +3357,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;
@@ -3571,6 +3583,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);
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index ab80884..8abe50e 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -3261,6 +3261,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>> {
@@ -3379,6 +3389,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();
@@ -3406,6 +3417,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;
@@ -3631,6 +3643,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);
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/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..b20ed84 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/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/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 17c75d2..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
@@ -171,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
@@ -793,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()
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..f7e23b3
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ComposeScrollCaptureCallback.android.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.math.roundToInt
+import kotlin.random.Random
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.launch
+
+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,
+    private val coroutineScope: CoroutineScope,
+) : ScrollCaptureCallback {
+    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 = signal,
+            // Don't animate scrollByOffset calls.
+            context = DisableAnimationMotionDurationScale
+        ) {
+            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,
+    context: CoroutineContext = EmptyCoroutineContext,
+    block: suspend CoroutineScope.() -> Unit
+): Job {
+    val job = launch(context = context, 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..f4ba501
--- /dev/null
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/scrollcapture/ScrollCapture.android.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.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.platform.isVisible
+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.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)
+
+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/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index 7f7220d..f1938fe 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
@@ -302,6 +303,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")
@@ -1244,12 +1250,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 +1268,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/core/core/src/main/java/androidx/core/hardware/fingerprint/FingerprintManagerCompat.java b/core/core/src/main/java/androidx/core/hardware/fingerprint/FingerprintManagerCompat.java
index 244b776..e8e1bbf3 100644
--- a/core/core/src/main/java/androidx/core/hardware/fingerprint/FingerprintManagerCompat.java
+++ b/core/core/src/main/java/androidx/core/hardware/fingerprint/FingerprintManagerCompat.java
@@ -18,11 +18,16 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Handler;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.RequiresPermission;
 import androidx.annotation.RestrictTo;
 
@@ -34,53 +39,62 @@
 /**
  * A class that coordinates access to the fingerprint hardware.
  * <p>
- * This class has been deprecated and should no longer be used. On all platform versions, it behaves
- * as though no fingerprint hardware is available.
+ * On platforms before {@link android.os.Build.VERSION_CODES#M}, this class behaves as there would
+ * be no fingerprint hardware available.
  *
- * @deprecated {@code FingerprintManager} was removed from the platform SDK in Android V, use
- * {@code androidx.biometrics.BiometricPrompt} instead.
+ * @deprecated Use {@code androidx.biometrics.BiometricPrompt} instead.
  */
 @SuppressWarnings({"deprecation", "unused"})
 @Deprecated
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 public class FingerprintManagerCompat {
 
+    private final Context mContext;
+
     /** Get a {@link FingerprintManagerCompat} instance for a provided context. */
     @NonNull
     public static FingerprintManagerCompat from(@NonNull Context context) {
-        return new FingerprintManagerCompat();
+        return new FingerprintManagerCompat(context);
     }
 
-    private FingerprintManagerCompat() {
+    private FingerprintManagerCompat(Context context) {
+        mContext = context;
     }
 
     /**
-     * Prior to deprecation, this method would determine if there is at least one fingerprint
-     * enrolled.
+     * Determine if there is at least one fingerprint enrolled.
      *
-     * @return false
+     * @return true if at least one fingerprint is enrolled, false otherwise
      */
     @RequiresPermission(Manifest.permission.USE_FINGERPRINT)
     public boolean hasEnrolledFingerprints() {
-        return false;
+        if (Build.VERSION.SDK_INT >= 23) {
+            final FingerprintManager fp = getFingerprintManagerOrNull(mContext);
+            return (fp != null) && Api23Impl.hasEnrolledFingerprints(fp);
+        } else {
+            return false;
+        }
     }
 
     /**
-     * Prior to deprecation, this method would determine if fingerprint hardware is present and
-     * functional.
+     * Determine if fingerprint hardware is present and functional.
      *
-     * @return false
+     * @return true if hardware is present and functional, false otherwise.
      */
     @RequiresPermission(Manifest.permission.USE_FINGERPRINT)
     public boolean isHardwareDetected() {
-        return false;
+        if (Build.VERSION.SDK_INT >= 23) {
+            final FingerprintManager fp = getFingerprintManagerOrNull(mContext);
+            return (fp != null) && Api23Impl.isHardwareDetected(fp);
+        } else {
+            return false;
+        }
     }
 
     /**
-     * Prior to deprecation, this method would request authentication of a crypto object.
-     * <p>
-     * This call warms up the fingerprint hardware and starts scanning for a fingerprint. It
-     * terminates when {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
+     * Request authentication of a crypto object. This call warms up the fingerprint hardware
+     * and starts scanning for a fingerprint. It terminates when
+     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
      * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
      * which point the object is no longer valid. The operation can be canceled by using the
      * provided cancel object.
@@ -100,14 +114,15 @@
             @Nullable androidx.core.os.CancellationSignal cancel,
             @NonNull AuthenticationCallback callback,
             @Nullable Handler handler) {
-        // No-op.
+        authenticate(crypto, flags,
+                cancel != null ? (CancellationSignal) cancel.getCancellationSignalObject() : null,
+                callback, handler);
     }
 
     /**
-     * Prior to deprecation, this method would request authentication of a crypto object.
-     * <p>
-     * This call warms up the fingerprint hardware and starts scanning for a fingerprint.
-     * It terminates when {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
+     * Request authentication of a crypto object. This call warms up the fingerprint hardware
+     * and starts scanning for a fingerprint. It terminates when
+     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
      * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
      * which point the object is no longer valid. The operation can be canceled by using the
      * provided cancel object.
@@ -122,7 +137,56 @@
     public void authenticate(@Nullable CryptoObject crypto, int flags,
             @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback,
             @Nullable Handler handler) {
-        // No-op.
+        if (Build.VERSION.SDK_INT >= 23) {
+            final FingerprintManager fp = getFingerprintManagerOrNull(mContext);
+            if (fp != null) {
+                Api23Impl.authenticate(fp, wrapCryptoObject(crypto), cancel, flags,
+                        wrapCallback(callback), handler);
+            }
+        }
+    }
+
+    @Nullable
+    @RequiresApi(23)
+    private static FingerprintManager getFingerprintManagerOrNull(@NonNull Context context) {
+        return Api23Impl.getFingerprintManagerOrNull(context);
+    }
+
+    @RequiresApi(23)
+    private static FingerprintManager.CryptoObject wrapCryptoObject(CryptoObject cryptoObject) {
+        return Api23Impl.wrapCryptoObject(cryptoObject);
+    }
+
+    @RequiresApi(23)
+    static CryptoObject unwrapCryptoObject(FingerprintManager.CryptoObject cryptoObject) {
+        return Api23Impl.unwrapCryptoObject(cryptoObject);
+    }
+
+    @RequiresApi(23)
+    private static FingerprintManager.AuthenticationCallback wrapCallback(
+            final AuthenticationCallback callback) {
+        return new FingerprintManager.AuthenticationCallback() {
+            @Override
+            public void onAuthenticationError(int errMsgId, CharSequence errString) {
+                callback.onAuthenticationError(errMsgId, errString);
+            }
+
+            @Override
+            public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+                callback.onAuthenticationHelp(helpMsgId, helpString);
+            }
+
+            @Override
+            public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+                callback.onAuthenticationSucceeded(new AuthenticationResult(
+                        unwrapCryptoObject(Api23Impl.getCryptoObject(result))));
+            }
+
+            @Override
+            public void onAuthenticationFailed() {
+                callback.onAuthenticationFailed();
+            }
+        };
     }
 
     /**
@@ -232,4 +296,82 @@
          */
         public void onAuthenticationFailed() { }
     }
+
+    @RequiresApi(23)
+    static class Api23Impl {
+        private Api23Impl() {
+            // This class is not instantiable.
+        }
+
+        @RequiresPermission(Manifest.permission.USE_FINGERPRINT)
+        @DoNotInline
+        static boolean hasEnrolledFingerprints(Object fingerprintManager) {
+            return ((FingerprintManager) fingerprintManager).hasEnrolledFingerprints();
+        }
+
+        @RequiresPermission(Manifest.permission.USE_FINGERPRINT)
+        @DoNotInline
+        static boolean isHardwareDetected(Object fingerprintManager) {
+            return ((FingerprintManager) fingerprintManager).isHardwareDetected();
+        }
+
+        @RequiresPermission(Manifest.permission.USE_FINGERPRINT)
+        @DoNotInline
+        static void authenticate(Object fingerprintManager, Object crypto,
+                CancellationSignal cancel, int flags, Object callback, Handler handler) {
+            ((FingerprintManager) fingerprintManager).authenticate(
+                    (FingerprintManager.CryptoObject) crypto, cancel, flags,
+                    (FingerprintManager.AuthenticationCallback) callback, handler);
+        }
+
+        @DoNotInline
+        static FingerprintManager.CryptoObject getCryptoObject(Object authenticationResult) {
+            return ((FingerprintManager.AuthenticationResult) authenticationResult)
+                    .getCryptoObject();
+        }
+
+        @DoNotInline
+        public static FingerprintManager getFingerprintManagerOrNull(Context context) {
+            if (Build.VERSION.SDK_INT == 23) {
+                return context.getSystemService(FingerprintManager.class);
+            } else if (Build.VERSION.SDK_INT > 23 && context.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+                return context.getSystemService(FingerprintManager.class);
+            } else {
+                return null;
+            }
+        }
+
+        @DoNotInline
+        public static FingerprintManager.CryptoObject wrapCryptoObject(CryptoObject cryptoObject) {
+            if (cryptoObject == null) {
+                return null;
+            } else if (cryptoObject.getCipher() != null) {
+                return new FingerprintManager.CryptoObject(cryptoObject.getCipher());
+            } else if (cryptoObject.getSignature() != null) {
+                return new FingerprintManager.CryptoObject(cryptoObject.getSignature());
+            } else if (cryptoObject.getMac() != null) {
+                return new FingerprintManager.CryptoObject(cryptoObject.getMac());
+            } else {
+                return null;
+            }
+        }
+
+        @DoNotInline
+        public static CryptoObject unwrapCryptoObject(Object cryptoObjectObj) {
+            FingerprintManager.CryptoObject cryptoObject =
+                    (FingerprintManager.CryptoObject) cryptoObjectObj;
+            if (cryptoObject == null) {
+                return null;
+            } else if (cryptoObject.getCipher() != null) {
+                return new CryptoObject(cryptoObject.getCipher());
+            } else if (cryptoObject.getSignature() != null) {
+                return new CryptoObject(cryptoObject.getSignature());
+            } else if (cryptoObject.getMac() != null) {
+                return new CryptoObject(cryptoObject.getMac());
+            } else {
+                return null;
+            }
+        }
+    }
 }
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..db1503b 100755
--- a/development/build_log_simplifier/build_log_simplifier.py
+++ b/development/build_log_simplifier/build_log_simplifier.py
@@ -204,6 +204,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 +527,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/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 7e79e78..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"
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index a85faf4..3dc311e 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -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"/>
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/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..40bde13 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,86 @@
             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. Divide vertical oscillation (converted to meters)
+         * by stride length (in meters) in `double` format.
+         */
+        @JvmField
+        val VERTICAL_RATIO: DeltaDataType<Double, SampleDataPoint<Double>> =
+            createSampleDataType("Vertical Ratio")
+
+        /**
+         * Statistics on vertical oscillation / stride length. Divide vertical oscillation
+         * (converted to meters) by stride length (in meters) in `double` format.
+         */
+        @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 +742,8 @@
             FLAT_GROUND_DURATION,
             FLOORS,
             GOLF_SHOT_COUNT,
+            GROUND_CONTACT_BALANCE,
+            GROUND_CONTACT_TIME,
             HEART_RATE_BPM,
             INCLINE_DISTANCE,
             INCLINE_DURATION,
@@ -673,8 +755,11 @@
             SPEED,
             STEPS,
             STEPS_PER_MINUTE,
+            STRIDE_LENGTH,
             SWIMMING_LAP_COUNT,
             SWIMMING_STROKES,
+            VERTICAL_OSCILLATION,
+            VERTICAL_RATIO,
             VO2_MAX,
             WALKING_STEPS,
         )
@@ -692,6 +777,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 +789,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/libraryversions.toml b/libraryversions.toml
index 5cf45d7c..c2d38b8 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,15 +14,15 @@
 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"
@@ -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"
@@ -63,7 +63,7 @@
 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_PREVIEW = "1.0.0-alpha06"
@@ -92,14 +92,14 @@
 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"
+NAVIGATION = "2.8.0-alpha06"
 PAGING = "3.3.0-alpha05"
 PALETTE = "1.1.0-alpha01"
 PDF = "1.0.0-alpha01"
@@ -145,7 +145,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"
@@ -155,7 +155,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"
@@ -166,13 +166,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]
@@ -203,7 +203,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/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index 449e4b9..89417d1 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -252,6 +252,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 @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, optional kotlin.reflect.KClass<?>? route, optional java.util.Map<kotlin.reflect.KType,? extends 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();
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 b33cd9b..7160edf 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/NavGraphTest.kt
@@ -148,7 +148,7 @@
         }
         assertThat(graph.startDestinationId).isEqualTo(15)
 
-        graph.setStartDestination(TestClass::class)
+        graph.setStartDestination<TestClass>()
         assertThat(graph.startDestinationRoute).isEqualTo("route/{arg}")
         assertThat(graph.startDestinationId).isEqualTo(serializer<TestClass>().hashCode())
     }
@@ -162,7 +162,7 @@
 
         // start destination not added via KClass, cannot match
         assertFailsWith<IllegalStateException> {
-            graph.setStartDestination(TestClass::class)
+            graph.setStartDestination<TestClass>()
         }
     }
 
@@ -205,7 +205,7 @@
             addDestination(NavDestinationBuilder(navGraphNavigator, TestClass::class).build())
         }
 
-        val dest = graph.findNode(TestClass::class)
+        val dest = graph.findNode<TestClass>()
         assertThat(dest).isNotNull()
     }
 
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 61da16f..0d2ffa9 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestinationBuilder.kt
@@ -90,7 +90,7 @@
      *
      * @return the newly constructed [NavDestination]
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     @OptIn(InternalSerializationApi::class)
     public constructor(
         navigator: Navigator<out D>,
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 7f6ddf4..d175f19 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraph.kt
@@ -192,8 +192,8 @@
      * @return the node with route - the node must have been created with a route from [KClass]
      */
     @OptIn(InternalSerializationApi::class)
-    internal fun <T : Any> findNode(route: KClass<T>?): NavDestination? {
-        return if (route != null) findNode(route.serializer().hashCode()) else null
+    internal inline fun <reified T> findNode(): NavDestination? {
+        return findNode(serializer<T>().hashCode())
     }
 
     /**
@@ -367,10 +367,9 @@
      * @param startDestRoute The route of the destination as a [KClass] to be shown when navigating
      * to this NavGraph.
      */
-    @OptIn(InternalSerializationApi::class)
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public fun setStartDestination(startDestRoute: KClass<*>) {
-        setStartDestination(startDestRoute.serializer()) { startDestination ->
+    public inline fun <reified T> setStartDestination() {
+        setStartDestination(serializer<T>()) { startDestination ->
             startDestination.route!!
         }
     }
@@ -394,8 +393,10 @@
         }
     }
 
+    // unfortunately needs to be public so reified setStartDestination can access this
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @OptIn(ExperimentalSerializationApi::class)
-    private fun <T> setStartDestination(
+    public fun <T> setStartDestination(
         serializer: KSerializer<T>,
         parseRoute: (NavDestination) -> String,
     ) {
@@ -403,7 +404,7 @@
         val startDest = findNode(id)
         checkNotNull(startDest) {
             "Cannot find startDestination ${serializer.descriptor.serialName} from NavGraph. " +
-                "Ensure the starting NavDestination was added via KClass."
+                "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
@@ -523,8 +524,8 @@
  * @throws IllegalArgumentException if no destination is found with that route.
  */
 @Suppress("NOTHING_TO_INLINE")
-internal inline operator fun <T : Any> NavGraph.get(route: KClass<T>): NavDestination =
-    findNode(route)
+internal inline operator fun <reified T : Any> NavGraph.get(route: KClass<T>): NavDestination =
+    findNode<T>()
         ?: throw IllegalArgumentException("No destination for $route was found in $this")
 
 /**
@@ -544,8 +545,9 @@
 public operator fun NavGraph.contains(route: String): Boolean = findNode(route) != null
 
 /** Returns `true` if a destination with `route` is found in this navigation graph. */
-internal operator fun <T : Any> NavGraph.contains(route: KClass<T>): Boolean =
-    findNode(route) != null
+@Suppress("UNUSED_PARAMETER")
+internal 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. */
 internal operator fun <T> NavGraph.contains(route: T): Boolean = findNode(route) != null
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 6e42aff..e4213e9 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavGraphBuilder.kt
@@ -21,6 +21,7 @@
 import kotlin.reflect.KClass
 import kotlin.reflect.KType
 import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.serializer
 
 /**
  * Construct a new [NavGraph]
@@ -64,8 +65,8 @@
 /**
  * Construct a new [NavGraph]
  *
- * @param startDestination the starting destination's route as a [KClass] for this NavGraph. The
- * respective NavDestination must be added as a [KClass] in order to match.
+ * @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.
@@ -84,8 +85,8 @@
 /**
  * Construct a new [NavGraph]
  *
- * @param startDestination the starting destination's route as an Object for this NavGraph. The
- * respective NavDestination must be added as a [KClass] in order to match.
+ * @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.
@@ -142,9 +143,9 @@
 /**
  * Construct a nested [NavGraph]
  *
- * @param startDestination the starting destination's route as 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 as a [KClass]
+ * @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.
  *
@@ -161,9 +162,9 @@
 /**
  * Construct a nested [NavGraph]
  *
- * @param startDestination the starting destination's route as 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 as a [KClass]
+ * @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.
  *
@@ -240,7 +241,7 @@
      *
      * @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 as a [KClass] in order to match.
+     * 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.
@@ -263,7 +264,7 @@
      *
      * @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 as a [KClass] in order to match.
+     * 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.
@@ -318,7 +319,7 @@
         if (startDestinationRoute != null) {
             navGraph.setStartDestination(startDestinationRoute!!)
         } else if (startDestinationClass != null) {
-            navGraph.setStartDestination(startDestinationClass!!)
+            navGraph.setStartDestination(startDestinationClass!!.serializer()) { it.route!! }
         } else if (startDestinationObject != null) {
             navGraph.setStartDestination(startDestinationObject!!)
         } else {
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..f2405d5 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -53,6 +53,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 +203,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 +252,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
@@ -1224,6 +1360,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
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..bcddb2b 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -42,8 +42,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 +54,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 +527,64 @@
     }
 
     /**
+     * Attempts to pop the controller's back stack back to a specific destination.
+     *
+     * @param route 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 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
+    internal 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
+     */
+    @OptIn(InternalSerializationApi::class)
+    @MainThread
+    @JvmOverloads
+    internal fun <T : Any> popBackStack(
+        route: T,
+        inclusive: Boolean,
+        saveState: Boolean = false
+    ): Boolean {
+        val dest = backQueue.lastOrNull {
+            it.destination.id == route::class.serializer().hashCode()
+        }
+        if (dest == null) return false
+        // route contains arguments so we need to generate and pop with the populated route
+        // rather than popping based on route pattern
+        val finalRoute = route.generateRouteWithArgs(
+            // get argument typeMap
+            dest.destination.arguments.mapValues { it.value.type }
+        )
+        return popBackStack(finalRoute, inclusive, saveState)
+    }
+
+    /**
      * Attempts to pop the controller's back stack back to a specific destination. This does
      * **not** handle calling [dispatchOnDestinationChanged]
      *
@@ -2602,3 +2665,37 @@
     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
+ */
+internal inline fun NavController.createGraph(
+    startDestination: KClass<*>,
+    route: KClass<*>? = null,
+    typeMap: Map<KType, 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
+ */
+internal inline fun NavController.createGraph(
+    startDestination: Any,
+    route: KClass<*>? = null,
+    typeMap: Map<KType, NavType<*>>? = null,
+    builder: NavGraphBuilder.() -> Unit
+): NavGraph = navigatorProvider.navigation(startDestination, route, typeMap, builder)
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/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..ac306ec 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
@@ -35,6 +35,8 @@
 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
@@ -119,10 +121,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 +146,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 +169,7 @@
 
     @Test
     fun registerSdkSandboxActivityHandler_delegateToSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 3",
-            sdkVersion >= 3
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val catchingHandler = CatchingSdkActivityHandler()
 
@@ -198,10 +191,7 @@
 
     @Test
     fun sdkSandboxActivityHandler_ReceivesLifecycleEventsFromOriginalActivityHolder() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 3",
-            sdkVersion >= 3
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val catchingHandler = CatchingSdkActivityHandler()
 
@@ -226,10 +216,7 @@
 
     @Test
     fun unregisterSdkSandboxActivityHandler_delegateToSdkController() {
-        assumeTrue(
-            "Requires Versions.API_VERSION >= 3",
-            sdkVersion >= 3
-        )
+        assumeFeatureAvailable(ClientFeature.SDK_ACTIVITY_HANDLER)
 
         val handler = CatchingSdkActivityHandler()
 
@@ -242,10 +229,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 +249,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,10 +292,17 @@
         }
     }
 
+    private fun assumeFeatureAvailable(clientFeature: ClientFeature) {
+        assumeTrue(
+            "Requires $clientFeature available (API >= ${clientFeature.availableFrom})",
+            clientFeature.isAvailable(sdkVersion)
+        )
+    }
+
     companion object {
 
         /**
-         * Create test params for each previously released [Versions.API_VERSION] + current one.
+         * Create test params for each supported [ClientApiVersion] + current one.
          * 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].
@@ -322,16 +310,16 @@
         @Parameterized.Parameters(name = "sdk: {0}, version: {1}")
         @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}",
+                            version.apiLevel,
+                        )
                     )
-                )
+                }
             }
 
             add(
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..bf5f248 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.
@@ -63,17 +64,17 @@
     private fun getParentClassLoader(): ClassLoader = appContext.classLoader.parent!!
 
     private fun createLocalSdk(
-        classLoader: ClassLoader,
+        sdkClassLoader: ClassLoader,
         sdkConfig: LocalSdkConfig
     ): 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 +82,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/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/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 84faafc..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()
     }
@@ -67,10 +69,7 @@
     }
 
     @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)
 
@@ -85,6 +84,8 @@
 
     @Test
     fun loadSdk_returnsLoadedSdkFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.LOAD_SDK)
+
         val expectedResult = SandboxedSdkCompat(Binder())
         val stubLocalImpl = TestStubImpl(
             loadSdkResult = expectedResult
@@ -105,6 +106,8 @@
 
     @Test
     fun loadSdk_rethrowsExceptionFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.LOAD_SDK)
+
         val expectedError = LoadSdkCompatException(RuntimeException(), Bundle())
         SdkSandboxControllerCompat.injectLocalImpl(
             TestStubImpl(
@@ -137,10 +140,7 @@
     }
 
     @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(
@@ -160,6 +160,8 @@
 
     @Test
     fun getAppOwnedSdkSandboxInterfaces_returnsListFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.APP_OWNED_INTERFACES)
+
         val expectedResult = listOf(
             AppOwnedSdkSandboxInterfaceCompat(
                 name = "TestSdk",
@@ -180,6 +182,8 @@
 
     @Test
     fun registerSdkSandboxActivityHandler_registerItInLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.SDK_ACTIVITY_HANDLER)
+
         val localImpl = TestStubImpl()
         SdkSandboxControllerCompat.injectLocalImpl(localImpl)
 
@@ -193,6 +197,8 @@
 
     @Test
     fun unregisterSdkSandboxActivityHandler_unregisterItFromLocalImpl() {
+        clientHandShakeForVersionIncluding(ClientFeature.SDK_ACTIVITY_HANDLER)
+
         val localImpl = TestStubImpl()
         SdkSandboxControllerCompat.injectLocalImpl(localImpl)
 
@@ -208,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)
 
@@ -225,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)
 
@@ -241,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/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/internal/ClientApiVersion.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientApiVersion.kt
new file mode 100644
index 0000000..dc31b15
--- /dev/null
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/internal/ClientApiVersion.kt
@@ -0,0 +1,105 @@
+/*
+ * 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]!!
+        }
+
+        /**
+         * Build mapping between [ClientFeature] and version where it first time became available.
+         * Features not added to specific version mapped as [FUTURE_VERSION]
+         */
+        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"
+                            )
+                        }
+                    }
+                }
+                ClientFeature.values().forEach { feature ->
+                    if (!containsKey(feature)) {
+                        put(feature, FUTURE_VERSION)
+                    }
+                }
+            }
+        }
+    }
+}
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/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 116aea1..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
@@ -325,7 +325,8 @@
         val dbFile = instrumentation.targetContext.getDatabasePath("test.db")
         val driverHelper = MigrationTestHelper(
             instrumentation = instrumentation,
-            driver = AndroidSQLiteDriver(dbFile.path),
+            fileName = dbFile.path,
+            driver = AndroidSQLiteDriver(),
             databaseClass = MigrationDbKotlin::class
         )
         assertThrows<IllegalStateException> {
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 e4b1895..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
@@ -34,13 +34,14 @@
 class AutoMigrationTest : BaseAutoMigrationTest() {
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
     private val file = instrumentation.targetContext.getDatabasePath("test.db")
-    private val driver: SQLiteDriver = BundledSQLiteDriver(file.path)
+    private val driver: SQLiteDriver = BundledSQLiteDriver()
 
     @get:Rule
     val migrationTestHelper = MigrationTestHelper(
         instrumentation = instrumentation,
         driver = driver,
-        databaseClass = AutoMigrationDatabase::class
+        databaseClass = AutoMigrationDatabase::class,
+        fileName = file.path
     )
 
     override fun getTestHelper() = migrationTestHelper
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 6a755ab..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
@@ -26,8 +26,9 @@
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
 
     override fun getRoomDatabase(): SampleDatabase {
-        return Room.inMemoryDatabaseBuilder<SampleDatabase>(instrumentation.targetContext)
-            .setDriver(BundledSQLiteDriver(":memory:"))
+        return Room.inMemoryDatabaseBuilder<SampleDatabase>(
+            context = instrumentation.targetContext
+        ).setDriver(BundledSQLiteDriver())
             .setQueryCoroutineContext(Dispatchers.IO)
             .build()
     }
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 19aca6e..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
@@ -25,12 +25,13 @@
 import org.junit.Rule
 
 class AutoMigrationTest : BaseAutoMigrationTest() {
-    private val tempFile = createTempFile("test.db").also { it.toFile().deleteOnExit() }
-    private 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
     )
@@ -38,7 +39,7 @@
     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 ce59cf6..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
@@ -24,7 +24,7 @@
 
     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 7909d4d..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
@@ -27,10 +27,11 @@
 
 class AutoMigrationTest : BaseAutoMigrationTest() {
     private val filename = "/tmp/test-${Random.nextInt()}.db"
-    private 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() }
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 dd4f1fb..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
@@ -29,10 +29,8 @@
     private val filename = "/tmp/test-${Random.nextInt()}.db"
 
     override fun getRoomDatabaseBuilder(): RoomDatabase.Builder<SampleDatabase> {
-        return Room.databaseBuilder<SampleDatabase>(filename) {
-            SampleDatabase::class.instantiateImpl()
-        }
-            .setDriver(BundledSQLiteDriver(filename))
+        return Room.databaseBuilder(filename) { SampleDatabase::class.instantiateImpl() }
+            .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/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 0f15cc8..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
@@ -24,10 +24,8 @@
 class SimpleQueryTest : BaseSimpleQueryTest() {
 
     override fun getRoomDatabase(): SampleDatabase {
-        return Room.inMemoryDatabaseBuilder<SampleDatabase> {
-            SampleDatabase::class.instantiateImpl()
-        }
-            .setDriver(BundledSQLiteDriver(":memory:"))
+        return Room.inMemoryDatabaseBuilder { SampleDatabase::class.instantiateImpl() }
+            .setDriver(BundledSQLiteDriver())
             .setQueryCoroutineContext(Dispatchers.IO)
             .build()
     }
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/RoomConnectionManager.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/RoomConnectionManager.android.kt
index 2083ced..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
@@ -72,11 +72,13 @@
             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()
                 )
@@ -200,7 +202,8 @@
         val supportDriver: SupportSQLiteDriver
     ) : ConnectionPool {
         private val supportConnection by lazy(LazyThreadSafetyMode.PUBLICATION) {
-            SupportPooledConnection(supportDriver.open())
+            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/driver/SupportSQLiteDriver.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteDriver.android.kt
index e0fcabb..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
@@ -24,7 +24,7 @@
 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/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/RoomConnectionManager.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
index 74d9ef5..e8d3c30 100644
--- a/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
+++ b/room/room-runtime/src/commonMain/kotlin/androidx/room/RoomConnectionManager.kt
@@ -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/coroutines/ConnectionPool.kt b/room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPool.kt
index 168aaa0..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
@@ -73,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.
@@ -86,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/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/jvmNativeMain/kotlin/androidx/room/RoomConnectionManager.jvmNative.kt b/room/room-runtime/src/jvmNativeMain/kotlin/androidx/room/RoomConnectionManager.jvmNative.kt
index 2787fbfe..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
@@ -32,11 +32,13 @@
         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/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/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/current.txt b/room/room-testing/api/current.txt
index becc3e0..8699971 100644
--- a/room/room-testing/api/current.txt
+++ b/room/room-testing/api/current.txt
@@ -2,12 +2,12 @@
 package androidx.room.testing {
 
   public class MigrationTestHelper extends org.junit.rules.TestWatcher {
-    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, 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);
     ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
     ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
     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);
diff --git a/room/room-testing/api/restricted_current.txt b/room/room-testing/api/restricted_current.txt
index becc3e0..8699971 100644
--- a/room/room-testing/api/restricted_current.txt
+++ b/room/room-testing/api/restricted_current.txt
@@ -2,12 +2,12 @@
 package androidx.room.testing {
 
   public class MigrationTestHelper extends org.junit.rules.TestWatcher {
-    ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, 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);
     ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass);
     ctor public MigrationTestHelper(android.app.Instrumentation instrumentation, Class<? extends androidx.room.RoomDatabase> databaseClass, java.util.List<? extends androidx.room.migration.AutoMigrationSpec> specs);
     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);
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 2afe271..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
@@ -179,6 +179,7 @@
      * 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.
@@ -190,6 +191,7 @@
      */
     constructor(
         instrumentation: Instrumentation,
+        fileName: String,
         driver: SQLiteDriver,
         databaseClass: KClass<out RoomDatabase>,
         databaseFactory: () -> RoomDatabase = {
@@ -207,6 +209,7 @@
         this.delegate = SQLiteDriverMigrationTestHelper(
             instrumentation = instrumentation,
             assetsFolder = assetsFolder,
+            fileName = fileName,
             driver = driver,
             databaseClass = databaseClass,
             databaseFactory = databaseFactory,
@@ -405,10 +408,11 @@
     protected fun createDatabaseConfiguration(
         container: RoomDatabase.MigrationContainer,
         openFactory: SupportSQLiteOpenHelper.Factory?,
-        sqliteDriver: SQLiteDriver?
+        sqliteDriver: SQLiteDriver?,
+        databaseFileName: String?
     ) = DatabaseConfiguration(
         context = instrumentation.targetContext,
-        name = null,
+        name = databaseFileName,
         sqliteOpenHelperFactory = openFactory,
         migrationContainer = container,
         callbacks = null,
@@ -517,7 +521,7 @@
     }
 
     private fun createConfiguration(container: RoomDatabase.MigrationContainer) =
-        createDatabaseConfiguration(container, openFactory, null)
+        createDatabaseConfiguration(container, openFactory, null, null)
 
     private class SupportTestConnectionManager(
         override val configuration: DatabaseConfiguration,
@@ -537,7 +541,7 @@
             this.driverWrapper = DriverWrapper(supportDriver)
         }
 
-        override fun openConnection() = driverWrapper.open()
+        override fun openConnection() = driverWrapper.open(configuration.name ?: ":memory:")
 
         inner class SupportOpenHelperCallback(
             version: Int
@@ -574,6 +578,7 @@
     private val driver: SQLiteDriver,
     databaseClass: KClass<out RoomDatabase>,
     databaseFactory: () -> RoomDatabase,
+    private val fileName: String,
     private val autoMigrationSpecs: List<AutoMigrationSpec>
 ) : AndroidMigrationTestHelper(instrumentation, assetsFolder) {
 
@@ -607,5 +612,5 @@
     }
 
     private fun createConfiguration(container: RoomDatabase.MigrationContainer) =
-        createDatabaseConfiguration(container, null, driver)
+        createDatabaseConfiguration(container, null, driver, fileName)
 }
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
index 4470798..64ccb0d 100644
--- a/room/room-testing/src/commonMain/kotlin/androidx/room/testing/MigrationTestHelper.kt
+++ b/room/room-testing/src/commonMain/kotlin/androidx/room/testing/MigrationTestHelper.kt
@@ -219,7 +219,7 @@
 
     private val driverWrapper = DriverWrapper(requireNotNull(configuration.sqliteDriver))
 
-    override fun openConnection() = driverWrapper.open()
+    override fun openConnection() = driverWrapper.open(configuration.name ?: ":memory:")
 }
 
 private sealed class TestOpenDelegate(
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
index dc6f6e1..fa527a3 100644
--- 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
@@ -76,6 +76,7 @@
  * 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.
@@ -86,6 +87,7 @@
  */
 actual class MigrationTestHelper(
     private val schemaDirectoryPath: Path,
+    private val databasePath: Path,
     private val driver: SQLiteDriver,
     private val databaseClass: KClass<out RoomDatabase>,
     databaseFactory: () -> RoomDatabase = {
@@ -164,7 +166,7 @@
     private fun createDatabaseConfiguration(
         container: RoomDatabase.MigrationContainer,
     ) = DatabaseConfiguration(
-        name = null,
+        name = databasePath.toString(),
         migrationContainer = container,
         callbacks = null,
         journalMode = RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING,
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
index 90747171..c0e69bc 100644
--- 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
@@ -78,6 +78,7 @@
  * 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.
@@ -88,6 +89,7 @@
  */
 actual class MigrationTestHelper(
     private val schemaDirectoryPath: String,
+    private val fileName: String,
     private val driver: SQLiteDriver,
     private val databaseClass: KClass<out RoomDatabase>,
     databaseFactory: () -> RoomDatabase,
@@ -162,7 +164,7 @@
     private fun createDatabaseConfiguration(
         container: RoomDatabase.MigrationContainer,
     ) = DatabaseConfiguration(
-        name = null,
+        name = fileName,
         migrationContainer = container,
         callbacks = null,
         journalMode = RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING,
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 92b183a..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()
@@ -260,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")
@@ -336,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/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/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 9e31171..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 {
diff --git a/sqlite/sqlite/api/restricted_current.txt b/sqlite/sqlite/api/restricted_current.txt
index 9e31171..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 {
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/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
index a9bcbad..c352f5f 100644
--- a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
+++ b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
@@ -23,6 +23,7 @@
 import androidx.navigation.NavDestinationDsl
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.get
+import kotlin.reflect.KClass
 
 /**
  * Construct a new [TestNavigator.Destination]
@@ -37,6 +38,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 +65,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 +82,5 @@
     @Suppress("DEPRECATION")
     constructor(navigator: TestNavigator, @IdRes id: Int = 0) : super(navigator, id)
     constructor(navigator: TestNavigator, route: String) : super(navigator, route)
+    constructor(navigator: TestNavigator, route: KClass<*>) : super(navigator, route, null)
 }
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index 4982912..9ac320a 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -49,6 +49,8 @@
     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/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/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 bc47ebf..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);
   }
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index bc47ebf..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);
   }
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 a453890..158a9b2 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkUpdateTest.kt
@@ -84,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()
 
@@ -102,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()
@@ -118,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()
@@ -131,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()
@@ -147,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")
@@ -175,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()
@@ -186,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")
@@ -204,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()
 
@@ -225,7 +225,7 @@
     @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()
@@ -239,7 +239,7 @@
 
         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()
@@ -253,11 +253,11 @@
     @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()!!
@@ -269,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()!!
         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)
@@ -285,12 +285,12 @@
     @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()!!
@@ -302,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()!!
         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)
@@ -319,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()
@@ -337,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()
@@ -353,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)
@@ -374,14 +373,14 @@
     @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()
@@ -398,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)
@@ -414,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()
@@ -426,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()
@@ -434,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()
@@ -451,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()
@@ -468,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()
 
@@ -492,13 +491,13 @@
         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()
@@ -516,13 +515,13 @@
         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()
@@ -532,7 +531,7 @@
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .setNextScheduleTimeOverride(overrideScheduleTimeMillis2)
                 .build()
@@ -549,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()
@@ -558,7 +557,7 @@
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .setNextScheduleTimeOverride(overrideScheduleTimeMillis)
                 .build()
@@ -578,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()
@@ -586,7 +585,7 @@
 
         workManager.updateWork(
             PeriodicWorkRequest.Builder(
-                TestWorker::class.java, 1, DAYS
+                TestWorker::class, 1, DAYS
             ).setId(request.id)
                 .clearNextScheduleTimeOverride()
                 .setInitialDelay(2, DAYS)
@@ -610,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/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,