Merge "Pin dependencies on androidx annotation" into androidx-main
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 1bc5806..261e52e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -200,6 +200,8 @@
         }
         project.disallowAccidentalAndroidDependenciesInKmpProject(androidXKmpExtension)
         TaskUpToDateValidator.setup(project, registry)
+
+        project.workaroundPrebuiltTakingPrecedenceOverProject()
     }
 
     private fun Project.registerProjectOrArtifact() {
@@ -1517,6 +1519,13 @@
     }
 }
 
+/** Workaround for https://github.com/gradle/gradle/issues/27407 */
+fun Project.workaroundPrebuiltTakingPrecedenceOverProject() {
+    project.configurations.configureEach { configuration ->
+        configuration.resolutionStrategy.preferProjectModules()
+    }
+}
+
 private fun AndroidConfig.compileSdkInt() = compileSdk.removePrefix("android-").toInt()
 
 private fun Test.configureForRobolectric() {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index 8951ae9..f22d4af 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -30,6 +30,7 @@
 import androidx.build.metalava.versionMetadataUsage
 import androidx.build.multiplatformUsage
 import androidx.build.versionCatalog
+import androidx.build.workaroundPrebuiltTakingPrecedenceOverProject
 import com.android.build.api.attributes.BuildTypeAttr
 import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.LibraryPlugin
@@ -171,6 +172,7 @@
         )
 
         project.configureTaskTimeouts()
+        project.workaroundPrebuiltTakingPrecedenceOverProject()
     }
 
     /**
diff --git a/busytown/androidx_with_metalava.sh b/busytown/androidx_with_metalava.sh
index 7f9356d..56a40a6 100755
--- a/busytown/androidx_with_metalava.sh
+++ b/busytown/androidx_with_metalava.sh
@@ -4,7 +4,7 @@
 
 # Use this flag to temporarily disable `checkApi`
 # while landing Metalava w/ breaking API changes
-METALAVA_INTEGRATION_ENFORCED=false
+METALAVA_INTEGRATION_ENFORCED=true
 
 if $METALAVA_INTEGRATION_ENFORCED
 then
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionScreen.java
index 8d0ead2..0567470 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/userinteractions/RequestPermissionScreen.java
@@ -43,6 +43,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * A screen to show a request for a runtime permission from the user.
@@ -107,10 +108,12 @@
             return new MessageTemplate.Builder(
                     getCarContext().getString(R.string.permissions_granted_msg))
                     .setHeaderAction(headerAction)
-                    .addAction(new Action.Builder()
-                            .setTitle(getCarContext().getString(R.string.close_action_title))
-                            .setOnClickListener(this::finish)
-                            .build())
+                    .addAction(
+                            new Action.Builder()
+                                    .setTitle(
+                                            getCarContext().getString(R.string.close_action_title))
+                                    .setOnClickListener(this::finish)
+                                    .build())
                     .build();
         }
         boolean needsLocationPermission = needsLocationPermission();
@@ -119,15 +122,10 @@
     }
 
     private Template createPermissionPromptTemplate(
-            List<String> permissions,
-            boolean needsLocationPermission,
-            Action headerAction
-    ) {
+            List<String> permissions, boolean needsLocationPermission, Action headerAction) {
         LongMessageTemplate.Builder builder =
-                new LongMessageTemplate.Builder(createRequiredPermissionsMessage(
-                        permissions,
-                        needsLocationPermission
-                ))
+                new LongMessageTemplate.Builder(
+                        createRequiredPermissionsMessage(permissions, needsLocationPermission))
                         .setTitle(getCarContext().getString(R.string.required_permissions_title))
                         .addAction(createGrantPermissionsButton(permissions))
                         .setHeaderAction(headerAction);
@@ -140,9 +138,7 @@
     }
 
     private String createRequiredPermissionsMessage(
-            List<String> permissions,
-            boolean needsLocationPermission
-    ) {
+            List<String> permissions, boolean needsLocationPermission) {
         StringBuilder message = new StringBuilder()
                 .append(getCarContext().getString(R.string.needs_access_msg_prefix));
         for (String permission : permissions) {
@@ -150,7 +146,6 @@
             message.append("\n");
         }
 
-
         if (needsLocationPermission) {
             message.append(
                     getCarContext().getString(R.string.enable_location_permission_on_device_msg));
@@ -163,9 +158,10 @@
     private List<String> findMissingPermissions() throws PackageManager.NameNotFoundException {
         // Possible NameNotFoundException
         PackageInfo info =
-                getCarContext().getPackageManager().getPackageInfo(
-                        getCarContext().getPackageName(),
-                        PackageManager.GET_PERMISSIONS);
+                getCarContext()
+                        .getPackageManager()
+                        .getPackageInfo(getCarContext().getPackageName(),
+                                PackageManager.GET_PERMISSIONS);
 
         String[] declaredPermissions = info.requestedPermissions;
         if (declaredPermissions == null) {
@@ -178,16 +174,20 @@
             if (isAppHostPermission(permission)) {
                 // Don't include permissions against the car app host as they are all normal but
                 // show up as ungranted by the system.
-                Log.d(TAG, String.format("Permission ignored (belongs to host): %s", permission));
+                Log.d(
+                        TAG, String.format(Locale.US, "Permission ignored (belongs to host): %s",
+                                permission));
                 continue;
             }
 
             if (isPermissionGranted(permission)) {
-                Log.d(TAG, String.format("Permission ignored (already granted): %s", permission));
+                Log.d(
+                        TAG, String.format(Locale.US, "Permission ignored (already granted): %s",
+                                permission));
                 continue;
             }
 
-            Log.d(TAG, String.format("Found missing permission: %s", permission));
+            Log.d(TAG, String.format(Locale.US, "Found missing permission: %s", permission));
             missingPermissions.add(permission);
         }
 
@@ -220,16 +220,15 @@
         return new Action.Builder()
                 .setTitle(getCarContext().getString(R.string.enable_location_action_title))
                 .setBackgroundColor(CarColor.BLUE)
-                .setOnClickListener(ParkedOnlyOnClickListener.create(
-                        this::grantLocationPermission))
+                .setOnClickListener(ParkedOnlyOnClickListener.create(this::grantLocationPermission))
                 .build();
     }
 
     private void grantLocationPermission() {
-        getCarContext().startActivity(
-                new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        );
+        getCarContext()
+                .startActivity(
+                        new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
 
         invalidate();
 
@@ -237,9 +236,8 @@
     }
 
     private Action createGrantPermissionsButton(List<String> permissions) {
-        OnClickListener listener = ParkedOnlyOnClickListener.create(
-                () -> requestPermissions(permissions)
-        );
+        OnClickListener listener =
+                ParkedOnlyOnClickListener.create(() -> requestPermissions(permissions));
 
         return new Action.Builder()
                 .setTitle(getCarContext().getString(R.string.grant_access_action_title))
@@ -249,24 +247,26 @@
     }
 
     private void requestPermissions(List<String> permissions) {
-        getCarContext().requestPermissions(
-                permissions,
-                (approved, rejected) -> {
-                    // Log debug info
-                    CarToast.makeText(
-                            getCarContext(),
-                            String.format(
-                                    "Approved: %d Rejected: %d",
-                                    approved.size(),
-                                    rejected.size()
-                            ),
-                            CarToast.LENGTH_LONG
-                    ).show();
-                    Log.i(TAG, String.format("Approved: %s Rejected: %s", approved, rejected));
+        getCarContext()
+                .requestPermissions(
+                        permissions,
+                        (approved, rejected) -> {
+                            // Log debug info
+                            CarToast.makeText(
+                                            getCarContext(),
+                                            String.format(
+                                                    Locale.US, "Approved: %d Rejected: %d",
+                                                    approved.size(),
+                                                    rejected.size()),
+                                            CarToast.LENGTH_LONG)
+                                    .show();
+                            Log.i(TAG,
+                                    String.format(Locale.US, "Approved: %s Rejected: %s", approved,
+                                            rejected));
 
-                    // Update the template
-                    invalidate();
-                });
+                            // Update the template
+                            invalidate();
+                        });
 
         // Prompt AAP users to look at their phone, to grant permissions.
         promptAapUsers(getCarContext().getString(R.string.phone_screen_permission_msg));
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index 070bcc1..b5753d4 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -488,7 +488,6 @@
     method public S getCurrentState();
     method public S getTargetState();
     method public boolean isIdle();
-    method internal void setCurrentState$metalava_module(S);
     method public void setTargetState(S);
     property public S currentState;
     property public final boolean isIdle;
@@ -527,8 +526,6 @@
     method public S getCurrentState();
     method @FloatRange(from=0.0, to=1.0) public float getFraction();
     method public S getTargetState();
-    method internal void setCurrentState$metalava_module(S);
-    method internal void setTargetState$metalava_module(S);
     method public suspend Object? snapTo(optional S targetState, optional @FloatRange(from=0.0, to=1.0) float fraction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public S currentState;
     property @FloatRange(from=0.0, to=1.0) public final float fraction;
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index 6d58658..58ef4a3 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -488,7 +488,6 @@
     method public S getCurrentState();
     method public S getTargetState();
     method public boolean isIdle();
-    method internal void setCurrentState$metalava_module(S);
     method public void setTargetState(S);
     property public S currentState;
     property public final boolean isIdle;
@@ -527,8 +526,6 @@
     method public S getCurrentState();
     method @FloatRange(from=0.0, to=1.0) public float getFraction();
     method public S getTargetState();
-    method internal void setCurrentState$metalava_module(S);
-    method internal void setTargetState$metalava_module(S);
     method public suspend Object? snapTo(optional S targetState, optional @FloatRange(from=0.0, to=1.0) float fraction, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public S currentState;
     property @FloatRange(from=0.0, to=1.0) public final float fraction;
diff --git a/compose/compiler/compiler-hosted/integration-tests/build.gradle b/compose/compiler/compiler-hosted/integration-tests/build.gradle
index 5f14477..8199c23 100644
--- a/compose/compiler/compiler-hosted/integration-tests/build.gradle
+++ b/compose/compiler/compiler-hosted/integration-tests/build.gradle
@@ -39,7 +39,6 @@
                 implementation(libs.kotlinStdlibCommon)
                 implementation(kotlin("test-junit"))
                 implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:ui:ui-util"))
             }
         }
 
@@ -56,9 +55,8 @@
                 implementation(libs.protobufLite)
                 implementation(libs.guavaAndroid)
                 implementation(project(":compose:compiler:compiler-hosted"))
-                implementation(projectOrArtifact(":compose:foundation:foundation"))
-                implementation(projectOrArtifact(":compose:material:material"))
-                implementation(projectOrArtifact(":compose:ui:ui"))
+                implementation("androidx.compose.foundation:foundation:1.6.0-beta02")
+                implementation("androidx.compose.ui:ui:1.6.0-beta02")
                 implementation(project(":compose:runtime:runtime"))
                 implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.4")
                 implementation("com.google.dagger:dagger:2.40.1")
@@ -70,8 +68,8 @@
             dependencies {
                 implementation(libs.kotlinMetadataJvm)
                 implementation(libs.robolectric)
-                implementation(projectOrArtifact(":activity:activity-ktx"))
-                implementation(projectOrArtifact(":core:core-ktx"))
+                implementation("androidx.activity:activity-ktx:1.9.0-alpha01")
+                implementation("androidx.core:core-ktx:1.13.0-alpha03")
                 runtimeOnly(
                         project(":compose:compiler:compiler-hosted:integration-tests:kotlin-compiler-repackaged")
                 )
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index bbb3dc3..00830a4 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/androidUnitTest/kotlin/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -1736,8 +1736,23 @@
     @Test
     fun testComposableColorFunInterfaceExample() = checkApi(
         """
-            import androidx.compose.material.Text
             import androidx.compose.ui.graphics.Color
+            import java.lang.UnsupportedOperationException
+
+            @Composable
+            public fun Text(text: String, color: Color = Color.Unspecified) {}
+
+            @Immutable
+            @kotlin.jvm.JvmInline
+            value class Color(val value: ULong) {
+                companion object {
+                    @Stable
+                    val Red = Color(0xFFFF0000)
+
+                    @Stable
+                    val Blue = Color(0xFF0000FF)
+                }
+            }
 
             @Composable fun condition(): Boolean = true
 
@@ -1756,16 +1771,61 @@
             }
         """,
         """
+            public final class Color {
+              public final getValue-s-VKNKU()J
+              public static toString-impl(J)Ljava/lang/String;
+              public toString()Ljava/lang/String;
+              public static hashCode-impl(J)I
+              public hashCode()I
+              public static equals-impl(JLjava/lang/Object;)Z
+              public equals(Ljava/lang/Object;)Z
+              private synthetic <init>(J)V
+              public static constructor-impl(J)J
+              public final static synthetic box-impl(J)LColor;
+              public final synthetic unbox-impl()J
+              public final static equals-impl0(JJ)Z
+              public final static synthetic access%getRed%cp()J
+              public final static synthetic access%getBlue%cp()J
+              static <clinit>()V
+              public final static LColor%Companion; Companion
+              private final J value
+              private final static J Red
+              private final static J Blue
+              public final static INNERCLASS Color%Companion Color Companion
+            }
+            public final class Color%Companion {
+              private <init>()V
+              public final getRed-0d7_KjU()J
+              public static synthetic getRed-0d7_KjU%annotations()V
+              public final getBlue-0d7_KjU()J
+              public static synthetic getBlue-0d7_KjU%annotations()V
+              public synthetic <init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+              public final static INNERCLASS Color%Companion Color Companion
+            }
             public abstract interface ButtonColors {
               public abstract getColor-WaAFU9c(Landroidx/compose/runtime/Composer;I)J
             }
             public final class TestKt {
+              public final static Text-iJQMabo(Ljava/lang/String;JLandroidx/compose/runtime/Composer;II)V
               public final static condition(Landroidx/compose/runtime/Composer;I)Z
               public final static Button(LButtonColors;Landroidx/compose/runtime/Composer;I)V
               public final static Test(Landroidx/compose/runtime/Composer;I)V
               final static INNERCLASS TestKt%Button%1 null null
               final static INNERCLASS TestKt%Test%1 null null
               final static INNERCLASS TestKt%Test%2 null null
+              final static INNERCLASS TestKt%Text%1 null null
+              public final static INNERCLASS androidx/compose/ui/graphics/Color%Companion androidx/compose/ui/graphics/Color Companion
+            }
+            final class TestKt%Text%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {
+              <init>(Ljava/lang/String;JII)V
+              public final invoke(Landroidx/compose/runtime/Composer;I)V
+              public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+              final synthetic Ljava/lang/String; %text
+              final synthetic J %color
+              final synthetic I %%changed
+              final synthetic I %%default
+              OUTERCLASS TestKt Text-iJQMabo (Ljava/lang/String;JLandroidx/compose/runtime/Composer;II)V
+              final static INNERCLASS TestKt%Text%1 null null
             }
             final class TestKt%Button%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {
               <init>(LButtonColors;I)V
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index 5eea18a..f5a7fe5 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -2115,7 +2115,6 @@
             import androidx.compose.runtime.*
             import androidx.compose.foundation.layout.*
             import androidx.compose.foundation.text.KeyboardActions
-            import androidx.compose.material.*
 
             object Ui {}
 
@@ -2131,6 +2130,17 @@
                     Text("${'$'}keyboardActions2")
                 }
             }
+        """.trimIndent(),
+        extra = """
+            import androidx.compose.runtime.Composable
+
+            @Composable
+            fun Text(
+                text: String,
+                softWrap: Boolean = true,
+                maxLines: Int = Int.MAX_VALUE,
+                minLines: Int = 1,
+            ) {}
         """.trimIndent()
     )
 
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
index 1a46556..eb8198d 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/FunctionBodySkippingTransformTests.kt
@@ -144,7 +144,35 @@
     @Test
     fun testFunInterfaces2(): Unit = comparisonPropagation(
         """
-            import androidx.compose.ui.graphics.Color
+            import androidx.compose.runtime.Immutable
+            import androidx.compose.runtime.Stable
+
+            @Stable
+            fun Color(color: Long): Color {
+                return Color((color shl 32).toULong())
+            }
+
+            @Immutable
+            @kotlin.jvm.JvmInline
+            value class Color(val value: ULong) {
+                companion object {
+                    @Stable
+                    val Red = Color(0xFFFF0000)
+                    @Stable
+                    val Blue = Color(0xFF0000FF)
+                    @Stable
+                    val Transparent = Color(0x00000000)
+                }
+            }
+
+            @Composable
+            public fun Text(
+                text: String,
+                color: Color = Color.Transparent,
+                softWrap: Boolean = true,
+                maxLines: Int = Int.MAX_VALUE,
+                minLines: Int = 1,
+            ) {}
 
             @Composable fun condition(): Boolean = true
 
@@ -153,9 +181,6 @@
             }
         """,
         """
-            import androidx.compose.material.Text
-            import androidx.compose.ui.graphics.Color
-
             @Composable
             fun Button(colors: ButtonColors) {
                 Text("hello world", color = colors.getColor())
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralTransformTests.kt
index d0c9cd5..3dca4d2 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralTransformTests.kt
@@ -434,7 +434,6 @@
             import androidx.compose.runtime.*
             import androidx.compose.foundation.layout.*
             import androidx.compose.foundation.text.KeyboardActions
-            import androidx.compose.material.*
 
             object Ui {}
 
@@ -450,6 +449,17 @@
                     Text("${'$'}keyboardActions2")
                 }
             }
+        """.trimIndent(),
+        extra = """
+            import androidx.compose.runtime.Composable
+
+            @Composable
+            public fun Text(
+                text: String,
+                softWrap: Boolean = true,
+                maxLines: Int = Int.MAX_VALUE,
+                minLines: Int = 1,
+            ) {}
         """.trimIndent()
     )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralV2TransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralV2TransformTests.kt
index 7b01a1b..6adba72 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralV2TransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/LiveLiteralV2TransformTests.kt
@@ -434,7 +434,6 @@
             import androidx.compose.runtime.*
             import androidx.compose.foundation.layout.*
             import androidx.compose.foundation.text.KeyboardActions
-            import androidx.compose.material.*
 
             object Ui {}
 
@@ -450,6 +449,17 @@
                     Text("${'$'}keyboardActions2")
                 }
             }
+        """.trimIndent(),
+        extra = """
+            import androidx.compose.runtime.Composable
+
+            @Composable
+            public fun Text(
+                text: String,
+                softWrap: Boolean = true,
+                maxLines: Int = Int.MAX_VALUE,
+                minLines: Int = 1,
+            ) {}
         """.trimIndent()
     )
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
index e04ca63..84fb7a8 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
@@ -6,7 +6,6 @@
 import androidx.compose.runtime.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.material.*
 
 object Ui {}
 
@@ -61,8 +60,8 @@
     println("t41 insideFunction %keyboardActions2")
     Column(null, null, null, { %composer: Composer?, %changed: Int ->
       sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Text("...>:Test.kt")
-      Text("%isError", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
-      Text("%keyboardActions2", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
+      Text("%isError", false, 0, 0, %composer, 0, 0b1110)
+      Text("%keyboardActions2", false, 0, 0, %composer, 0, 0b1110)
       sourceInformationMarkerEnd(%composer)
     }, %composer, 0, 0b0111)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
index e04ca63..84fb7a8 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.ControlFlowTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
@@ -6,7 +6,6 @@
 import androidx.compose.runtime.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.material.*
 
 object Ui {}
 
@@ -61,8 +60,8 @@
     println("t41 insideFunction %keyboardActions2")
     Column(null, null, null, { %composer: Composer?, %changed: Int ->
       sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Text("...>:Test.kt")
-      Text("%isError", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
-      Text("%keyboardActions2", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
+      Text("%isError", false, 0, 0, %composer, 0, 0b1110)
+      Text("%keyboardActions2", false, 0, 0, %composer, 0, 0b1110)
       sourceInformationMarkerEnd(%composer)
     }, %composer, 0, 0b0111)
     if (isTraceInProgress()) {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt"
index fe95e6c..1e95edb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = false\135.txt"
@@ -7,9 +7,6 @@
 import androidx.compose.runtime.ReadOnlyComposable
 
 
-import androidx.compose.material.Text
-import androidx.compose.ui.graphics.Color
-
 @Composable
 fun Button(colors: ButtonColors) {
     Text("hello world", color = colors.getColor())
@@ -26,7 +23,7 @@
 // ------------------------------------------
 
 @Composable
-@ComposableInferredTarget(scheme = "[androidx.compose.ui.UiComposable[androidx.compose.ui.UiComposable]]")
+@ComposableInferredTarget(scheme = "[0[0]]")
 fun Button(colors: ButtonColors, %composer: Composer?, %changed: Int) {
   %composer = %composer.startRestartGroup(<>)
   sourceInformation(%composer, "C(Button)<getCol...>,<Text("...>:Test.kt")
@@ -43,7 +40,7 @@
     if (isTraceInProgress()) {
       traceEventStart(<>, %dirty, -1, <>)
     }
-    Text("hello world", null, colors.getColor(%composer, 0b1110 and %dirty), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0b0110, 0, 0b00011111111111111010)
+    Text("hello world", colors.getColor(%composer, 0b1110 and %dirty), false, 0, 0, %composer, 0b0110, 0b00011100)
     if (isTraceInProgress()) {
       traceEventEnd()
     }
@@ -55,7 +52,6 @@
   }
 }
 @Composable
-@ComposableTarget(applier = "androidx.compose.ui.UiComposable")
 fun Test(%composer: Composer?, %changed: Int) {
   %composer = %composer.startRestartGroup(<>)
   sourceInformation(%composer, "C(Test)<Button>:Test.kt")
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt"
index fe95e6c..1e95edb 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.FunctionBodySkippingTransformTests/testFunInterfaces2\133useFir = true\135.txt"
@@ -7,9 +7,6 @@
 import androidx.compose.runtime.ReadOnlyComposable
 
 
-import androidx.compose.material.Text
-import androidx.compose.ui.graphics.Color
-
 @Composable
 fun Button(colors: ButtonColors) {
     Text("hello world", color = colors.getColor())
@@ -26,7 +23,7 @@
 // ------------------------------------------
 
 @Composable
-@ComposableInferredTarget(scheme = "[androidx.compose.ui.UiComposable[androidx.compose.ui.UiComposable]]")
+@ComposableInferredTarget(scheme = "[0[0]]")
 fun Button(colors: ButtonColors, %composer: Composer?, %changed: Int) {
   %composer = %composer.startRestartGroup(<>)
   sourceInformation(%composer, "C(Button)<getCol...>,<Text("...>:Test.kt")
@@ -43,7 +40,7 @@
     if (isTraceInProgress()) {
       traceEventStart(<>, %dirty, -1, <>)
     }
-    Text("hello world", null, colors.getColor(%composer, 0b1110 and %dirty), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0b0110, 0, 0b00011111111111111010)
+    Text("hello world", colors.getColor(%composer, 0b1110 and %dirty), false, 0, 0, %composer, 0b0110, 0b00011100)
     if (isTraceInProgress()) {
       traceEventEnd()
     }
@@ -55,7 +52,6 @@
   }
 }
 @Composable
-@ComposableTarget(applier = "androidx.compose.ui.UiComposable")
 fun Test(%composer: Composer?, %changed: Int) {
   %composer = %composer.startRestartGroup(<>)
   sourceInformation(%composer, "C(Test)<Button>:Test.kt")
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
index 3e75554..52765af 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
@@ -6,7 +6,6 @@
 import androidx.compose.runtime.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.material.*
 
 object Ui {}
 
@@ -69,8 +68,8 @@
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println%fun-UiTextField()}%isError")
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println-1%fun-UiTextField()}%keyboardActions2")
     Column(null, null, null, { %composer: Composer?, %changed: Int ->
-      Text("%isError", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
-      Text("%keyboardActions2", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
+      Text("%isError", false, 0, 0, %composer, 0, 0b1110)
+      Text("%keyboardActions2", false, 0, 0, %composer, 0, 0b1110)
     }, %composer, 0, 0b0111)
     if (isTraceInProgress()) {
       traceEventEnd()
@@ -103,7 +102,7 @@
   }
   val Boolean%param-isError%fun-UiTextField: Boolean = false
   var State%Boolean%param-isError%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 292)
+  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 257)
   fun Boolean%param-isError%fun-UiTextField(): Boolean {
     if (!isLiveLiteralsEnabled) {
       return Boolean%param-isError%fun-UiTextField
@@ -120,7 +119,7 @@
   }
   val Boolean%param-keyboardActions2%fun-UiTextField: Boolean = false
   var State%Boolean%param-keyboardActions2%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 331)
+  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 296)
   fun Boolean%param-keyboardActions2%fun-UiTextField(): Boolean {
     if (!isLiveLiteralsEnabled) {
       return Boolean%param-keyboardActions2%fun-UiTextField
@@ -137,7 +136,7 @@
   }
   val String%0%str%arg-0%call-println%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 355)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 320)
   fun String%0%str%arg-0%call-println%fun-UiTextField(): String {
     if (!isLiveLiteralsEnabled) {
       return String%0%str%arg-0%call-println%fun-UiTextField
@@ -154,7 +153,7 @@
   }
   val String%0%str%arg-0%call-println-1%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println-1%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 398)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 363)
   fun String%0%str%arg-0%call-println-1%fun-UiTextField(): String {
     if (!isLiveLiteralsEnabled) {
       return String%0%str%arg-0%call-println-1%fun-UiTextField
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
index 3e75554..52765af 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralTransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
@@ -6,7 +6,6 @@
 import androidx.compose.runtime.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.material.*
 
 object Ui {}
 
@@ -69,8 +68,8 @@
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println%fun-UiTextField()}%isError")
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println-1%fun-UiTextField()}%keyboardActions2")
     Column(null, null, null, { %composer: Composer?, %changed: Int ->
-      Text("%isError", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
-      Text("%keyboardActions2", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
+      Text("%isError", false, 0, 0, %composer, 0, 0b1110)
+      Text("%keyboardActions2", false, 0, 0, %composer, 0, 0b1110)
     }, %composer, 0, 0b0111)
     if (isTraceInProgress()) {
       traceEventEnd()
@@ -103,7 +102,7 @@
   }
   val Boolean%param-isError%fun-UiTextField: Boolean = false
   var State%Boolean%param-isError%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 292)
+  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 257)
   fun Boolean%param-isError%fun-UiTextField(): Boolean {
     if (!isLiveLiteralsEnabled) {
       return Boolean%param-isError%fun-UiTextField
@@ -120,7 +119,7 @@
   }
   val Boolean%param-keyboardActions2%fun-UiTextField: Boolean = false
   var State%Boolean%param-keyboardActions2%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 331)
+  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 296)
   fun Boolean%param-keyboardActions2%fun-UiTextField(): Boolean {
     if (!isLiveLiteralsEnabled) {
       return Boolean%param-keyboardActions2%fun-UiTextField
@@ -137,7 +136,7 @@
   }
   val String%0%str%arg-0%call-println%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 355)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 320)
   fun String%0%str%arg-0%call-println%fun-UiTextField(): String {
     if (!isLiveLiteralsEnabled) {
       return String%0%str%arg-0%call-println%fun-UiTextField
@@ -154,7 +153,7 @@
   }
   val String%0%str%arg-0%call-println-1%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println-1%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 398)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 363)
   fun String%0%str%arg-0%call-println-1%fun-UiTextField(): String {
     if (!isLiveLiteralsEnabled) {
       return String%0%str%arg-0%call-println-1%fun-UiTextField
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
index 779274c..20dca32 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = false\135.txt"
@@ -6,7 +6,6 @@
 import androidx.compose.runtime.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.material.*
 
 object Ui {}
 
@@ -69,8 +68,8 @@
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println%fun-UiTextField()}%isError")
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println-1%fun-UiTextField()}%keyboardActions2")
     Column(null, null, null, { %composer: Composer?, %changed: Int ->
-      Text("%isError", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
-      Text("%keyboardActions2", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
+      Text("%isError", false, 0, 0, %composer, 0, 0b1110)
+      Text("%keyboardActions2", false, 0, 0, %composer, 0, 0b1110)
     }, %composer, 0, 0b0111)
     if (isTraceInProgress()) {
       traceEventEnd()
@@ -104,7 +103,7 @@
   }
   val Boolean%param-isError%fun-UiTextField: Boolean = false
   var State%Boolean%param-isError%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 292)
+  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 257)
   fun Boolean%param-isError%fun-UiTextField(): Boolean {
     if (!enabled) {
       return Boolean%param-isError%fun-UiTextField
@@ -121,7 +120,7 @@
   }
   val Boolean%param-keyboardActions2%fun-UiTextField: Boolean = false
   var State%Boolean%param-keyboardActions2%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 331)
+  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 296)
   fun Boolean%param-keyboardActions2%fun-UiTextField(): Boolean {
     if (!enabled) {
       return Boolean%param-keyboardActions2%fun-UiTextField
@@ -138,7 +137,7 @@
   }
   val String%0%str%arg-0%call-println%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 355)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 320)
   fun String%0%str%arg-0%call-println%fun-UiTextField(): String {
     if (!enabled) {
       return String%0%str%arg-0%call-println%fun-UiTextField
@@ -155,7 +154,7 @@
   }
   val String%0%str%arg-0%call-println-1%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println-1%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 398)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 363)
   fun String%0%str%arg-0%call-println-1%fun-UiTextField(): String {
     if (!enabled) {
       return String%0%str%arg-0%call-println-1%fun-UiTextField
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
index 779274c..20dca32 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.LiveLiteralV2TransformTests/testComposeIrSkippingWithDefaultsRelease\133useFir = true\135.txt"
@@ -6,7 +6,6 @@
 import androidx.compose.runtime.*
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.material.*
 
 object Ui {}
 
@@ -69,8 +68,8 @@
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println%fun-UiTextField()}%isError")
     println("%{LiveLiterals%TestKt.String%0%str%arg-0%call-println-1%fun-UiTextField()}%keyboardActions2")
     Column(null, null, null, { %composer: Composer?, %changed: Int ->
-      Text("%isError", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
-      Text("%keyboardActions2", null, <unsafe-coerce>(0L), <unsafe-coerce>(0L), null, null, null, <unsafe-coerce>(0L), null, null, <unsafe-coerce>(0L), <unsafe-coerce>(0), false, 0, 0, null, null, %composer, 0, 0, 0b00011111111111111110)
+      Text("%isError", false, 0, 0, %composer, 0, 0b1110)
+      Text("%keyboardActions2", false, 0, 0, %composer, 0, 0b1110)
     }, %composer, 0, 0b0111)
     if (isTraceInProgress()) {
       traceEventEnd()
@@ -104,7 +103,7 @@
   }
   val Boolean%param-isError%fun-UiTextField: Boolean = false
   var State%Boolean%param-isError%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 292)
+  @LiveLiteralInfo(key = "Boolean%param-isError%fun-UiTextField", offset = 257)
   fun Boolean%param-isError%fun-UiTextField(): Boolean {
     if (!enabled) {
       return Boolean%param-isError%fun-UiTextField
@@ -121,7 +120,7 @@
   }
   val Boolean%param-keyboardActions2%fun-UiTextField: Boolean = false
   var State%Boolean%param-keyboardActions2%fun-UiTextField: State<Boolean>?
-  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 331)
+  @LiveLiteralInfo(key = "Boolean%param-keyboardActions2%fun-UiTextField", offset = 296)
   fun Boolean%param-keyboardActions2%fun-UiTextField(): Boolean {
     if (!enabled) {
       return Boolean%param-keyboardActions2%fun-UiTextField
@@ -138,7 +137,7 @@
   }
   val String%0%str%arg-0%call-println%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 355)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println%fun-UiTextField", offset = 320)
   fun String%0%str%arg-0%call-println%fun-UiTextField(): String {
     if (!enabled) {
       return String%0%str%arg-0%call-println%fun-UiTextField
@@ -155,7 +154,7 @@
   }
   val String%0%str%arg-0%call-println-1%fun-UiTextField: String = "t41 insideFunction "
   var State%String%0%str%arg-0%call-println-1%fun-UiTextField: State<String>?
-  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 398)
+  @LiveLiteralInfo(key = "String%0%str%arg-0%call-println-1%fun-UiTextField", offset = 363)
   fun String%0%str%arg-0%call-println-1%fun-UiTextField(): String {
     if (!enabled) {
       return String%0%str%arg-0%call-println-1%fun-UiTextField
diff --git a/compose/compiler/snapshot-compose-compiler.sh b/compose/compiler/snapshot-compose-compiler.sh
index 277ae3d..7436318 100755
--- a/compose/compiler/snapshot-compose-compiler.sh
+++ b/compose/compiler/snapshot-compose-compiler.sh
@@ -18,4 +18,4 @@
 VERIFICATION_METADATA="$(dirname $0)"/../../gradle/verification-metadata.xml
 REPOSITORY_LOCATION="$(dirname $0)"/compose-compiler-snapshot-repository
 COMPOSE_CUSTOM_VERSION=99.0.0 $GRADLEW_LOCATION -Dmaven.repo.local=$REPOSITORY_LOCATION -Pandroidx.versionExtraCheckEnabled=false :compose:compiler:compiler:publishToMavenLocal --stacktrace
-rm -r $VERIFICATION_METADATA
+rm -f $VERIFICATION_METADATA
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazySwitchingStatesBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazySwitchingStatesBenchmark.kt
new file mode 100644
index 0000000..2cc62ed
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazySwitchingStatesBenchmark.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.benchmark.lazy
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.testutils.ComposeTestCase
+import androidx.compose.testutils.assertNoPendingChanges
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.setupContent
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class LazySwitchingStatesBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun lazyColumn_switchingItems_composition() {
+        benchmarkRule.runBenchmark(composition = true)
+    }
+
+    @Test
+    fun lazyColumn_switchingItems_measure() {
+        benchmarkRule.runBenchmark(composition = false)
+    }
+
+    private fun ComposeBenchmarkRule.runBenchmark(composition: Boolean) {
+        runBenchmarkFor(
+            { LazyColumnSwitchingItemsCase(readInComposition = composition) },
+        ) {
+            runOnUiThread {
+                setupContent()
+                doFramesUntilIdle()
+            }
+
+            measureRepeatedOnUiThread {
+                runWithTimingDisabled {
+                    assertNoPendingChanges()
+                    repeat(getTestCase().items.size) {
+                        getTestCase().toggle(it)
+                    }
+                    doFramesUntilIdle()
+                    assertNoPendingChanges()
+                }
+
+                repeat(getTestCase().items.size) {
+                    getTestCase().toggle(it)
+                }
+                doFramesUntilIdle()
+            }
+        }
+    }
+}
+
+class LazyColumnSwitchingItemsCase(
+    private val readInComposition: Boolean = false
+) : ComposeTestCase {
+    // The number is based on height of items below (20 visible + 5 extra).
+    val items = List(25) {
+        mutableStateOf(false)
+    }
+
+    @Composable
+    override fun Content() {
+        LazyColumn(
+            Modifier
+                .requiredHeight(400.dp)
+                .fillMaxWidth(),
+            flingBehavior = NoFlingBehavior
+        ) {
+            items(items) { state ->
+                val color = if (readInComposition) {
+                   if (state.value) Color.Blue else Color.Red
+                } else {
+                    Color.Red
+                }
+                Box(
+                    Modifier
+                        .width(20.dp)
+                        .height(20.dp)
+                        .drawBehind {
+                            val rectColor = if (readInComposition) {
+                                color
+                            } else {
+                                if (state.value) Color.Blue else Color.Red
+                            }
+                            drawRoundRect(rectColor, cornerRadius = CornerRadius(20f))
+                        }
+                )
+            }
+        }
+    }
+
+    fun toggle(index: Int) {
+        Snapshot.withoutReadObservation {
+            items[index].value = !items[index].value
+        }
+    }
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/ReuseBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/ReuseBenchmark.kt
index f116792..07de7e3 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/ReuseBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/ReuseBenchmark.kt
@@ -242,7 +242,7 @@
     }
 }
 
-private fun ComposeExecutionControl.doFramesUntilIdle() {
+internal fun ComposeExecutionControl.doFramesUntilIdle() {
     do {
         doFrame()
     } while (hasPendingChanges() || hasPendingMeasureOrLayout())
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/LastClippedCharacterDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/LastClippedCharacterDemo.kt
new file mode 100644
index 0000000..fcd5d96
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/LastClippedCharacterDemo.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.text
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Preview
+@Composable
+fun LastClippedCharacterDemo() {
+    var lastCharacterBox by remember { mutableStateOf<Rect?>(null) }
+
+    var overflow by remember { mutableStateOf(false) }
+    var height by remember { mutableIntStateOf(20) }
+    Column {
+        Box(modifier = Modifier
+            .width(100.dp)
+            .height(height.dp)
+            .border(1.dp, Color.Red)) {
+            BasicText(
+                text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
+                overflow = if (!overflow) { TextOverflow.Clip } else { TextOverflow.Visible },
+                modifier = Modifier.drawWithContent {
+                    drawContent()
+                    lastCharacterBox?.let { box ->
+                        drawRoundRect(
+                            Color.Green,
+                            box.topLeft,
+                            box.size,
+                            CornerRadius(4f, 4f),
+                            Stroke(2f)
+                        )
+                    }
+                },
+                onTextLayout = { textLayoutResult ->
+                    val lastNonClippedLine = textLayoutResult.findLastNonClippedLine()
+                    // this finds the newline
+                    val actualLastCharacter = textLayoutResult
+                        .getOffsetForPosition(
+                            Offset(
+                                textLayoutResult.size.width.toFloat(),
+                                textLayoutResult.lineVerticalMiddle(lastNonClippedLine),
+                            ))
+                    lastCharacterBox = textLayoutResult.getBoundingBox(actualLastCharacter)
+                        .translate(Offset(0f, textLayoutResult.getLineTop(lastNonClippedLine)))
+                },
+            )
+        }
+        Spacer(modifier = Modifier.height(200.dp))
+        Button(onClick = { overflow = !overflow }) {
+            Text("Show overflow")
+        }
+        Button(onClick = { height += 5 }) {
+            Text("Increase clip height (5)")
+        }
+        Button(onClick = { height = 2 }) {
+            Text("Reset height (+5)")
+        }
+
+        Text("For more information see b/319500907")
+        Text("Note that this demo considers a line non-clipped when any pixel of the line is" +
+            " not clipped, change the logic in findLastNonClippedLine to fit your needs")
+    }
+}
+
+private fun TextLayoutResult.lineVerticalMiddle(line: Int): Float {
+    return (getLineBottom(line) - getLineTop(line)) / 2
+}
+
+private fun TextLayoutResult.findLastNonClippedLine(): Int {
+    // if N>>10 write a binary search here, but assuming N~=1 a linear walk is fine
+    var cur = 0
+    val localLineCount = lineCount
+    val height = size.height
+    // walk while lines don't exceed height
+
+    // this will consider a line non-clipped as soon as 1px is non-clipped
+    // however, it is likely most lines won't draw anything in this region - you may want
+    // to adjust this to have an offset based on total line vertical area
+    // (getLineBottom - getLineTop) if you want some pixels to show before considering a line
+    // non-clipped
+    while (cur < localLineCount && getLineTop(cur) < height) {
+        cur++
+    }
+    // we walked one too far, go back a line
+    return (cur - 1).coerceAtLeast(0)
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 3b3a16d..8fe74bf 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -85,6 +85,9 @@
                             TextOverflowVisibleInPopupDemo()
                         },
                         ComposableDemo("Min/max lines") { BasicTextMinMaxLinesDemo() },
+                        ComposableDemo("Get last character after clip") {
+                            LastClippedCharacterDemo()
+                        }
                     )
                 ),
                 ComposableDemo("IncludeFontPadding & Clip") { TextFontPaddingDemo() },
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
index eab1c4f..74dde03 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerOffscreenPageLimitPlacingTest.kt
@@ -16,12 +16,19 @@
 
 package androidx.compose.foundation.pager
 
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.performTouchInput
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth
+import kotlin.math.roundToInt
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -117,6 +124,52 @@
         confirmPageIsInCorrectPosition(5)
     }
 
+    @Test
+    fun offsetPageLimitIsUsed_visiblePagesDidNotChange_shouldNotRemeasure() {
+        val pageSizePx = 100
+        val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+
+        val delta = (pageSizePx / 3f).roundToInt()
+        val initialIndex = 0
+        createPager(
+            initialPage = initialIndex,
+            pageCount = { DefaultPageCount },
+            modifier = Modifier.size(pageSizeDp * 1.5f),
+            pageSize = { PageSize.Fixed(pageSizeDp) },
+            outOfBoundsPageCount = 2
+        )
+
+        val lastVisible = pagerState.layoutInfo.visiblePagesInfo.last().index
+        // Assert
+        rule.runOnIdle {
+            Truth.assertThat(placed).contains(lastVisible + 1)
+            Truth.assertThat(placed).contains(lastVisible + 2)
+        }
+        val previousNumberOfRemeasurementPasses = pagerState.layoutWithMeasurement
+        runBlocking {
+            withContext(Dispatchers.Main + AutoTestFrameClock()) {
+                // small enough scroll to not cause any new items to be composed or
+                // old ones disposed.
+                pagerState.scrollBy(delta.toFloat())
+            }
+            rule.runOnIdle {
+                Truth.assertThat(pagerState.firstVisiblePageOffset).isEqualTo(delta)
+                Truth.assertThat(pagerState.layoutWithMeasurement)
+                    .isEqualTo(previousNumberOfRemeasurementPasses)
+            }
+            confirmPageIsInCorrectPosition(
+                pagerState.currentPage,
+                lastVisible + 1,
+                pagerState.currentPageOffsetFraction
+            )
+            confirmPageIsInCorrectPosition(
+                pagerState.currentPage,
+                lastVisible + 2,
+                pagerState.currentPageOffsetFraction
+            )
+        }
+    }
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextStyleInvalidationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextStyleInvalidationTest.kt
index 4f27a5c..065dfa4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextStyleInvalidationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/TextStyleInvalidationTest.kt
@@ -47,7 +47,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.sp
 import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -66,6 +66,7 @@
         val invalidatesMeasure: Boolean = false,
         val invalidatesPlacement: Boolean = false,
         val invalidatesDraw: Boolean = false,
+        val recompose: Boolean = true,
     ) {
         override fun toString(): String = buildString {
             append(description)
@@ -82,7 +83,7 @@
         @Parameters(name = "{0}")
         @JvmStatic
         fun parameters() = arrayOf(
-            Config("nothing", { it }),
+            Config("nothing", { it }, recompose = false),
             Config(
                 "color",
                 { it.copy(color = Color.Blue) },
@@ -297,6 +298,7 @@
                 textIndent = null,
             ).let(config.initializeStyle)
         )
+        var compositions = 0
         var measures = 0
         var placements = 0
         var draws = 0
@@ -318,30 +320,39 @@
                         draws++
                     }
             )
+            compositions++
         }
 
         rule.waitForIdle()
         val initialMeasures = measures
         val initialPlacements = placements
         val initialDraws = draws
+        val initialCompositions = compositions
 
         style = config.updateStyle(style)
 
         rule.runOnIdle {
+            if (config.recompose) {
+                assertWithMessage("recompose").that(compositions).isGreaterThan(initialCompositions)
+            }
+
             if (config.invalidatesMeasure) {
-                assertThat(measures).isGreaterThan(initialMeasures)
+                assertWithMessage("invalidate measure")
+                    .that(measures).isGreaterThan(initialMeasures)
             }
             if (config.invalidatesPlacement) {
-                assertThat(placements).isGreaterThan(initialPlacements)
+                assertWithMessage("invalidate placements")
+                    .that(placements).isGreaterThan(initialPlacements)
 
                 // If measure is invalidated, placement will also always be invalidated, so ensure
                 // that placement was also invalidated separately from measurement.
                 if (config.invalidatesMeasure) {
-                    assertThat(placements).isGreaterThan(measures)
+                    assertWithMessage("invalidate measure")
+                        .that(placements).isGreaterThan(measures)
                 }
             }
             if (config.invalidatesDraw) {
-                assertThat(draws).isGreaterThan(initialDraws)
+                assertWithMessage("invalidate draw").that(draws).isGreaterThan(initialDraws)
             }
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
index 802f191..83698a63 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
@@ -399,6 +399,15 @@
             (it.index >= visiblePages.first().index && it.index <= visiblePages.last().index)
         }
 
+        val positionedPagesBefore =
+            if (extraPagesBefore.isEmpty()) emptyList() else positionedPages.fastFilter {
+                it.index < visiblePages.first().index
+            }
+
+        val positionedPagesAfter =
+            if (extraPagesAfter.isEmpty()) emptyList() else positionedPages.fastFilter {
+                it.index > visiblePages.last().index
+            }
         val newCurrentPage =
             calculateNewCurrentPage(
                 if (orientation == Orientation.Vertical) layoutHeight else layoutWidth,
@@ -460,7 +469,9 @@
             currentPage = newCurrentPage,
             currentPageOffsetFraction = currentPageOffsetFraction,
             snapPosition = snapPosition,
-            remeasureNeeded = remeasureNeeded
+            remeasureNeeded = remeasureNeeded,
+            extraPagesBefore = positionedPagesBefore,
+            extraPagesAfter = positionedPagesAfter
         )
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
index 4e96917..1495575 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
@@ -43,6 +43,8 @@
     measureResult: MeasureResult,
     /** True when extra remeasure is required. */
     val remeasureNeeded: Boolean,
+    val extraPagesBefore: List<MeasuredPage> = emptyList(),
+    val extraPagesAfter: List<MeasuredPage> = emptyList()
 ) : PagerLayoutInfo, MeasureResult by measureResult {
     override val viewportSize: IntSize
         get() = IntSize(width, height)
@@ -86,8 +88,11 @@
             return false
         }
 
-        val first = visiblePagesInfo.first()
-        val last = visiblePagesInfo.last()
+        val first =
+            if (extraPagesBefore.isEmpty()) visiblePagesInfo.first() else extraPagesBefore.first()
+        val last =
+            if (extraPagesAfter.isEmpty()) visiblePagesInfo.last() else extraPagesAfter.last()
+
         val canApply = if (delta < 0) {
             // scrolling forward
             val deltaToFirstItemChange =
@@ -109,8 +114,14 @@
             visiblePagesInfo.fastForEach {
                 it.applyScrollDelta(delta)
             }
+            extraPagesBefore.fastForEach {
+                it.applyScrollDelta(delta)
+            }
+            extraPagesAfter.fastForEach {
+                it.applyScrollDelta(delta)
+            }
             if (!canScrollForward && delta > 0) {
-                // we scrolled backward, so now we can scroll forward
+                // we scrolled backward, so now we can scroll forward.
                 canScrollForward = true
             }
             true
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 90fd291..3d00697 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -263,10 +263,12 @@
             )
             // we don't need to remeasure, so we only trigger re-placement:
             placementScopeInvalidator.invalidateScope()
+            layoutWithoutMeasurement++
         } else {
             debugLog { "Will Apply With Remeasure" }
             scrollPosition.applyScrollDelta(scrollDelta.toInt())
             remeasurement?.forceRemeasure()
+            layoutWithMeasurement++
         }
 
         // Return the consumed value.
@@ -276,7 +278,12 @@
     /**
      * Only used for testing to confirm that we're not making too many measure passes
      */
-    internal var numMeasurePasses: Int = 0
+    internal val numMeasurePasses: Int get() = layoutWithMeasurement + layoutWithoutMeasurement
+
+    internal var layoutWithMeasurement: Int = 0
+        private set
+
+    internal var layoutWithoutMeasurement: Int = 0
         private set
 
     /**
@@ -656,7 +663,6 @@
         pagerLayoutInfoState.value = result
         canScrollForward = result.canScrollForward
         canScrollBackward = result.canScrollBackward
-        numMeasurePasses++
         result.firstVisiblePage?.let { firstVisiblePage = it.index }
         firstVisiblePageOffset = result.firstVisiblePageScrollOffset
         tryRunPrefetch(result)
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
index 339c299..cc8ef54 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -159,7 +159,7 @@
             } != null
         }
         // The parameter is in a source declaration
-        else -> (toUElement() as UParameter).typeReference!!.isComposable
+        else -> (toUElement() as? UParameter)?.typeReference?.isComposable == true
     }
 
 /**
diff --git a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
index e528318..12369ad 100644
--- a/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
+++ b/compose/material3/material3/src/androidUnitTest/kotlin/androidx/compose/material3/carousel/KeylineTest.kt
@@ -92,6 +92,46 @@
         assertThat(keylineList.getKeylineAfter(Float.MAX_VALUE)).isEqualTo(keylineList.last())
     }
 
+    @Test
+    fun testKeylineListLerp() {
+        val carouselMainAxisSize = StrategyTest.large + StrategyTest.medium + StrategyTest.small
+        val from = keylineListOf(carouselMainAxisSize, 1, StrategyTest.large / 2) {
+            add(StrategyTest.xSmall, isAnchor = true)
+            add(StrategyTest.large)
+            add(StrategyTest.medium)
+            add(StrategyTest.small)
+            add(StrategyTest.xSmall, isAnchor = true)
+        }
+        val to = keylineListOf(
+            carouselMainAxisSize,
+            2,
+            StrategyTest.small + (StrategyTest.large / 2)
+        ) {
+            add(StrategyTest.xSmall, isAnchor = true)
+            add(StrategyTest.small)
+            add(StrategyTest.large)
+            add(StrategyTest.medium)
+            add(StrategyTest.xSmall, isAnchor = true)
+        }
+
+        // Create the expected interpolated KeylineList by using the KeylineList class' constructor
+        // directly. Otherwise, keylineListOf will set offsets and unadjusted offsets based on the
+        // pivot offset and will differ than the directly interpolated output of lerp.
+        val half = KeylineList(
+            listOf(
+                Keyline(StrategyTest.xSmall, -2.5f, -90f, false, true, false, 0f),
+                Keyline(60f, 30f, 10f, false, false, false, 0f),
+                Keyline(80f, 100f, 110f, true, false, true, 0f),
+                Keyline(40f, 160f, 210f, false, false, false, 0f),
+                Keyline(StrategyTest.xSmall, 182.5f, 310f, false, true, false, 0f)
+            )
+        )
+
+        assertThat(lerp(from, to, 0f)).isEqualTo(from)
+        assertThat(lerp(from, to, 1f)).isEqualTo(to)
+        assertThat(lerp(from, to, .5f)).isEqualTo(half)
+    }
+
     companion object {
         private const val LargeSize = 100f
         private const val SmallSize = 20f
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 27998ae..c2acd24 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
@@ -266,42 +266,6 @@
             .isEqualTo(endSteps[2])
     }
 
-    @Test
-    fun testKeylineListLerp() {
-        val carouselMainAxisSize = large + medium + small
-        val from = keylineListOf(carouselMainAxisSize, 1, large / 2) {
-            add(xSmall, isAnchor = true)
-            add(large)
-            add(medium)
-            add(small)
-            add(xSmall, isAnchor = true)
-        }
-        val to = keylineListOf(carouselMainAxisSize, 2, small + (large / 2)) {
-            add(xSmall, isAnchor = true)
-            add(small)
-            add(large)
-            add(medium)
-            add(xSmall, isAnchor = true)
-        }
-
-        // Create the expected interpolated KeylineList by using the KeylineList class' constructor
-        // directly. Otherwise, keylineListOf will set offsets and unadjusted offsets based on the
-        // pivot offset and will differ than the directly interpolated output of lerp.
-        val half = KeylineList(
-            listOf(
-                Keyline(xSmall, -2.5f, -90f, false, true, false, 0f),
-                Keyline(60f, 30f, 10f, false, false, false, 0f),
-                Keyline(80f, 100f, 110f, true, false, true, 0f),
-                Keyline(40f, 160f, 210f, false, false, false, 0f),
-                Keyline(xSmall, 182.5f, 310f, false, true, false, 0f)
-            )
-        )
-
-        assertThat(lerp(from, to, 0f)).isEqualTo(from)
-        assertThat(lerp(from, to, 1f)).isEqualTo(to)
-        assertThat(lerp(from, to, .5f)).isEqualTo(half)
-    }
-
     private fun assertEqualWithFloatTolerance(
         tolerance: Float,
         actual: Keyline,
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
new file mode 100644
index 0000000..8500938
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Carousel.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.carousel
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ShapeDefaults
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * A enumeration of ways items can be aligned along a carousel's main axis
+ */
+internal enum class CarouselAlignment {
+    /** Start aligned carousels place focal items at the start/top of the container */
+    Start,
+    /** Center aligned carousels place focal items in the middle of the container */
+    Center,
+    /** End aligned carousels place focal items at the end/bottom of the container */
+    End
+}
+
+/**
+ * A modifier that handles clipping and translating an item as it moves along the scrolling axis
+ * of a Carousel.
+ *
+ * @param index the index of the item in the carousel
+ * @param viewportSize the size of the carousel container
+ * @param orientation the orientation of the carousel
+ * @param itemsCount the total number of items in the carousel
+ * @param scrollOffset the amount the carousel has been scrolled in pixels
+ * @param strategy the strategy used to mask and translate items in the carousel
+ */
+internal fun Modifier.carouselItem(
+    index: Int,
+    itemsCount: Int,
+    viewportSize: IntSize,
+    orientation: Orientation,
+    scrollOffset: Float,
+    strategy: Strategy
+): Modifier {
+    val isVertical = orientation == Orientation.Vertical
+    val mainAxisCarouselSize = if (isVertical) viewportSize.height else viewportSize.width
+    if (mainAxisCarouselSize == 0) {
+        return this
+    }
+    val maxScrollOffset =
+        itemsCount * strategy.itemMainAxisSize - mainAxisCarouselSize
+    val keylines = strategy.getKeylineListForScrollOffset(scrollOffset, maxScrollOffset)
+
+    // Find center of the item at this index
+    val unadjustedCenter =
+        (index * strategy.itemMainAxisSize) + (strategy.itemMainAxisSize / 2f) - scrollOffset
+
+    // Find the keyline before and after this item's center and create an interpolated
+    // keyline that the item should use for its clip shape and offset
+    val keylineBefore =
+        keylines.getKeylineBefore(unadjustedCenter)
+    val keylineAfter =
+        keylines.getKeylineAfter(unadjustedCenter)
+    val progress = getProgress(keylineBefore, keylineAfter, unadjustedCenter)
+    val interpolatedKeyline = lerp(keylineBefore, keylineAfter, progress)
+    val isOutOfKeylineBounds = keylineBefore == keylineAfter
+
+    return this then layout { measurable, constraints ->
+        // Force the item to use the strategy's itemMainAxisSize along its main axis
+        val mainAxisSize = strategy.itemMainAxisSize
+        val itemConstraints = if (isVertical) {
+            constraints.copy(
+                minWidth = constraints.minWidth,
+                maxWidth = constraints.maxWidth,
+                minHeight = mainAxisSize.roundToInt(),
+                maxHeight = mainAxisSize.roundToInt()
+            )
+        } else {
+            constraints.copy(
+                minWidth = mainAxisSize.roundToInt(),
+                maxWidth = mainAxisSize.roundToInt(),
+                minHeight = constraints.minHeight,
+                maxHeight = constraints.maxHeight
+            )
+        }
+
+        val placeable = measurable.measure(itemConstraints)
+        layout(placeable.width, placeable.height) {
+            placeable.place(0, 0)
+        }
+    } then graphicsLayer {
+        // Clip the item
+        clip = true
+        shape = object : Shape {
+            // TODO: Find a way to use the shape of the item set by the client for each item
+            val roundedCornerShape = RoundedCornerShape(ShapeDefaults.ExtraLarge.topStart)
+            override fun createOutline(
+                size: Size,
+                layoutDirection: LayoutDirection,
+                density: Density
+            ): Outline {
+                val centerX =
+                    if (isVertical) size.height / 2f else strategy.itemMainAxisSize / 2f
+                val centerY =
+                    if (isVertical) strategy.itemMainAxisSize / 2f else size.height / 2f
+                val halfMaskWidth =
+                    if (isVertical) size.width / 2f else interpolatedKeyline.size / 2f
+                val halfMaskHeight =
+                    if (isVertical) interpolatedKeyline.size / 2f else size.height / 2f
+                val rect = Rect(
+                    left = centerX - halfMaskWidth,
+                    top = centerY - halfMaskHeight,
+                    right = centerX + halfMaskWidth,
+                    bottom = centerY + halfMaskHeight
+                )
+                val cornerSize =
+                    roundedCornerShape.topStart.toPx(
+                        Size(rect.width, rect.height),
+                        density
+                    )
+                val cornerRadius = CornerRadius(cornerSize)
+                return Outline.Rounded(
+                    RoundRect(
+                        rect = rect,
+                        topLeft = cornerRadius,
+                        topRight = cornerRadius,
+                        bottomRight = cornerRadius,
+                        bottomLeft = cornerRadius
+                    )
+                )
+            }
+        }
+
+        // After clipping, the items will have white space between them. Translate the items to
+        // pin their edges together
+        var translation = interpolatedKeyline.offset - unadjustedCenter
+        if (isOutOfKeylineBounds) {
+            // If this item is beyond the first or last keyline, continue to offset the item
+            // by cutting its unadjustedOffset according to its masked size.
+            val outOfBoundsOffset = (unadjustedCenter - interpolatedKeyline.unadjustedOffset) /
+                interpolatedKeyline.size
+            translation += outOfBoundsOffset
+        }
+        if (isVertical) {
+            translationY = translation
+        } else {
+            translationX = translation
+        }
+    }
+}
+
+/**
+ * Returns a float between 0 and 1 that represents how far [unadjustedOffset] is between
+ * [before] and [after].
+ *
+ * @param before the first keyline whose unadjustedOffset is less than [unadjustedOffset]
+ * @param after the first keyline whose unadjustedOffset is greater than [unadjustedOffset]
+ * @param unadjustedOffset the unadjustedOffset between [before] and [after]'s unadjustedOffset that
+ * a progress value will be returned for
+ */
+private fun getProgress(before: Keyline, after: Keyline, unadjustedOffset: Float): Float {
+    if (before == after) {
+        return 1f
+    }
+
+    val total = after.unadjustedOffset - before.unadjustedOffset
+    return (unadjustedOffset - before.unadjustedOffset) / total
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt
index 472a0eb..63c612e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3.carousel
 
 import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastMapIndexed
 
 /**
  * A structure that is fixed at a specific [offset] along a scrolling axis and
@@ -202,10 +203,6 @@
     }
 }
 
-internal enum class CarouselAlignment {
-    Start, Center, End
-}
-
 /**
  * Returns a [KeylineList] by aligning the focal range relative to the carousel container.
  */
@@ -468,3 +465,41 @@
             offset + (size / 2) > carouselMainAxisSize
     }
 }
+
+/**
+ * Returns an interpolated [Keyline] whose values are all interpolated based on [fraction]
+ * between the [start] and [end] keylines.
+ */
+internal fun lerp(start: Keyline, end: Keyline, fraction: Float): Keyline {
+    return Keyline(
+        size = androidx.compose.ui.util.lerp(start.size, end.size, fraction),
+        offset = androidx.compose.ui.util.lerp(start.offset, end.offset, fraction),
+        unadjustedOffset = androidx.compose.ui.util.lerp(
+            start.unadjustedOffset,
+            end.unadjustedOffset,
+            fraction
+        ),
+        isFocal = if (fraction < .5f) start.isFocal else end.isFocal,
+        isAnchor = if (fraction < .5f) start.isAnchor else end.isAnchor,
+        isPivot = if (fraction < .5f) start.isPivot else end.isPivot,
+        cutoff = androidx.compose.ui.util.lerp(start.cutoff, end.cutoff, fraction)
+    )
+}
+
+/**
+ * Returns an interpolated KeylineList between [from] and [to].
+ *
+ * Unlike creating a [KeylineList] using [keylineListOf], this method does not set unadjusted
+ * offsets by calculating them from a pivot index. This method simply interpolates all values of
+ * all keylines between the given pair.
+ */
+internal fun lerp(
+    from: KeylineList,
+    to: KeylineList,
+    fraction: Float
+): KeylineList {
+    val interpolatedKeylines = from.fastMapIndexed { i, k ->
+        lerp(k, to[i], fraction)
+    }
+    return KeylineList(interpolatedKeylines)
+}
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 269d0c5..4febab9 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
@@ -16,12 +16,10 @@
 
 package androidx.compose.material3.carousel
 
-import androidx.annotation.VisibleForTesting
 import androidx.collection.FloatList
 import androidx.collection.mutableFloatListOf
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMapIndexed
 import androidx.compose.ui.util.lerp
 import kotlin.math.roundToInt
 
@@ -86,6 +84,11 @@
 ) {
 
     /**
+     * The size of items when in focus and fully unmasked.
+     */
+    internal val itemMainAxisSize = defaultKeylines.firstFocal.size
+
+    /**
      * Returns the [KeylineList] that should be used for the current [scrollOffset].
      *
      * @param scrollOffset the current scroll offset of the scrollable component
@@ -163,7 +166,7 @@
          * @param carouselMainAxisSize the size of the carousel container in scrolling axis
          * @param keylineList the default keylines that will be used to create the strategy
          */
-        internal fun create(
+        fun create(
             /** The size of the carousel in the main axis. */
             carouselMainAxisSize: Float,
             /** The keylines along the main axis */
@@ -455,42 +458,6 @@
     }
 }
 
-/**
- * Returns an interpolated [Keyline] whose values are all interpolated based on [fraction]
- * between the [start] and [end] keylines.
- */
-@VisibleForTesting
-internal fun lerp(start: Keyline, end: Keyline, fraction: Float): Keyline {
-    return Keyline(
-        size = lerp(start.size, end.size, fraction),
-        offset = lerp(start.offset, end.offset, fraction),
-        unadjustedOffset = lerp(start.unadjustedOffset, end.unadjustedOffset, fraction),
-        isFocal = if (fraction < .5f) start.isFocal else end.isFocal,
-        isAnchor = if (fraction < .5f) start.isAnchor else end.isAnchor,
-        isPivot = if (fraction < .5f) start.isPivot else end.isPivot,
-        cutoff = lerp(start.cutoff, end.cutoff, fraction)
-    )
-}
-
-/**
- * Returns an interpolated KeylineList between [from] and [to].
- *
- * Unlike creating a [KeylineList] using [keylineListOf], this method does not set unadjusted
- * offsets by calculating them from a pivot index. This method simply interpolates all values of
- * all keylines between the given pair.
- */
-@VisibleForTesting
-internal fun lerp(
-    from: KeylineList,
-    to: KeylineList,
-    fraction: Float
-): KeylineList {
-    val interpolatedKeylines = from.fastMapIndexed { i, k ->
-        lerp(k, to[i], fraction)
-    }
-    return KeylineList(interpolatedKeylines)
-}
-
 private fun lerp(
     outputMin: Float,
     outputMax: Float,
diff --git a/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/JvmCompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/JvmCompositionTests.kt
index 33ad94a..b76b810 100644
--- a/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/JvmCompositionTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/JvmCompositionTests.kt
@@ -29,6 +29,7 @@
 import kotlin.concurrent.thread
 import kotlin.test.AfterTest
 import kotlin.test.BeforeTest
+import kotlin.test.Ignore
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
@@ -203,6 +204,7 @@
     }
 
     // regression test for b/319810819
+    @Ignore("b/320688836")
     @Test
     fun remember_functionReference_key() = compositionTest {
         var state by mutableIntStateOf(0)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt
index 03d4ecc..1a2d45d 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/TextInputServiceAndroidOnStateUpdateTest.kt
@@ -18,7 +18,6 @@
 
 package androidx.compose.ui.input
 
-import android.view.Choreographer
 import android.view.View
 import android.view.inputmethod.EditorInfo
 import androidx.compose.ui.text.TextRange
@@ -27,12 +26,11 @@
 import androidx.compose.ui.text.input.RecordingInputConnection
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.input.TextInputServiceAndroid
-import androidx.compose.ui.text.input.asExecutor
-import androidx.test.espresso.Espresso
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -56,13 +54,14 @@
     fun setup() {
         val view = View(InstrumentationRegistry.getInstrumentation().context)
         inputMethodManager = mock()
-        // Choreographer must be retrieved on main thread.
-        val choreographer = Espresso.onIdle { Choreographer.getInstance() }
+        // we never want the event queue to run during these test cases, as it will cause errant
+        // interactions based off an uncontrolled frame clock (causes flakes)
+        val neverExecutor = Executor { println("not running $it") }
         textInputService = TextInputServiceAndroid(
                 view,
                 mock(),
                 inputMethodManager,
-                inputCommandProcessorExecutor = choreographer.asExecutor()
+                inputCommandProcessorExecutor = neverExecutor
             )
         textInputService.startInput(
             value = TextFieldValue(""),
diff --git a/core/core-splashscreen/OWNERS b/core/core-splashscreen/OWNERS
index e2fdb97..5067ff1 100644
--- a/core/core-splashscreen/OWNERS
+++ b/core/core-splashscreen/OWNERS
@@ -1,6 +1,6 @@
 # Bug component: 1163699
-caen@google.com
+wilsonshih@google.com
 jjaggi@google.com
 cinek@google.com
 yaraki@google.com
-rgl@google.com
+rgl@google.com
\ No newline at end of file
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index b3803d1..47c9b19 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -2416,8 +2416,8 @@
     method @RequiresApi(21) public static androidx.core.util.SizeFCompat toSizeFCompat(android.util.SizeF);
   }
 
-  public interface Supplier<T> {
-    method public T! get();
+  public fun interface Supplier<T> {
+    method public T get();
   }
 
   public class TypedValueCompat {
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 433ec8e..e4afbb05 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -2833,8 +2833,8 @@
     method @RequiresApi(21) public static androidx.core.util.SizeFCompat toSizeFCompat(android.util.SizeF);
   }
 
-  public interface Supplier<T> {
-    method public T! get();
+  public fun interface Supplier<T> {
+    method public T get();
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TimeUtils {
diff --git a/core/core/src/main/java/androidx/core/util/Supplier.java b/core/core/src/main/java/androidx/core/util/Supplier.kt
similarity index 78%
rename from core/core/src/main/java/androidx/core/util/Supplier.java
rename to core/core/src/main/java/androidx/core/util/Supplier.kt
index 9deba96..2e0bf69 100644
--- a/core/core/src/main/java/androidx/core/util/Supplier.java
+++ b/core/core/src/main/java/androidx/core/util/Supplier.kt
@@ -13,19 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package androidx.core.util;
+package androidx.core.util
 
 /**
- * Compat version of {@link java.util.function.Supplier}
- * @param <T> the type of the input to the operation
+ * Compat version of [java.util.function.Supplier]
+ * @param T the type of the input to the operation
  */
-public interface Supplier<T> {
-
+fun interface Supplier<T> {
     /**
      * Gets a result.
      *
      * @return a result
      */
-    T get();
+    fun get(): T
 }
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index b2bfce9..14c676d 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
 # Disable docs
 androidx.enableDocumentation=false
 androidx.playground.snapshotBuildId=11307821
-androidx.playground.metalavaBuildId=11295842
+androidx.playground.metalavaBuildId=11319541
 androidx.studio.type=playground
diff --git a/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta03.txt b/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta03.txt
index 18004f9..232abf5 100644
--- a/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta03.txt
+++ b/privacysandbox/ads/ads-adservices-java/api/1.1.0-beta03.txt
@@ -17,8 +17,13 @@
 
   public abstract class AdSelectionManagerFutures {
     method public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome> getAdSelectionDataAsync(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> persistAdSelectionResultAsync(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportEventAsync(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportImpressionAsync(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> updateAdCounterHistogramAsync(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest);
     field public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion Companion;
   }
 
@@ -45,6 +50,7 @@
 package androidx.privacysandbox.ads.adservices.java.customaudience {
 
   public abstract class CustomAudienceManagerFutures {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> fetchAndJoinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request);
     method public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> joinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> leaveCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request);
diff --git a/privacysandbox/ads/ads-adservices-java/api/current.txt b/privacysandbox/ads/ads-adservices-java/api/current.txt
index 18004f9..232abf5 100644
--- a/privacysandbox/ads/ads-adservices-java/api/current.txt
+++ b/privacysandbox/ads/ads-adservices-java/api/current.txt
@@ -17,8 +17,13 @@
 
   public abstract class AdSelectionManagerFutures {
     method public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome> getAdSelectionDataAsync(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> persistAdSelectionResultAsync(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportEventAsync(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportImpressionAsync(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> updateAdCounterHistogramAsync(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest);
     field public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion Companion;
   }
 
@@ -45,6 +50,7 @@
 package androidx.privacysandbox.ads.adservices.java.customaudience {
 
   public abstract class CustomAudienceManagerFutures {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> fetchAndJoinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request);
     method public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> joinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> leaveCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request);
diff --git a/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta03.txt b/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta03.txt
index 18004f9..232abf5 100644
--- a/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta03.txt
+++ b/privacysandbox/ads/ads-adservices-java/api/restricted_1.1.0-beta03.txt
@@ -17,8 +17,13 @@
 
   public abstract class AdSelectionManagerFutures {
     method public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome> getAdSelectionDataAsync(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> persistAdSelectionResultAsync(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportEventAsync(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportImpressionAsync(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> updateAdCounterHistogramAsync(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest);
     field public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion Companion;
   }
 
@@ -45,6 +50,7 @@
 package androidx.privacysandbox.ads.adservices.java.customaudience {
 
   public abstract class CustomAudienceManagerFutures {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> fetchAndJoinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request);
     method public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> joinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> leaveCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request);
diff --git a/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt b/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt
index 18004f9..232abf5 100644
--- a/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt
+++ b/privacysandbox/ads/ads-adservices-java/api/restricted_current.txt
@@ -17,8 +17,13 @@
 
   public abstract class AdSelectionManagerFutures {
     method public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures? from(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome> getAdSelectionDataAsync(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> persistAdSelectionResultAsync(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportEventAsync(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> reportImpressionAsync(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome> selectAdsAsync(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> updateAdCounterHistogramAsync(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest);
     field public static final androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion Companion;
   }
 
@@ -45,6 +50,7 @@
 package androidx.privacysandbox.ads.adservices.java.customaudience {
 
   public abstract class CustomAudienceManagerFutures {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> fetchAndJoinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request);
     method public static final androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures? from(android.content.Context context);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> joinCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract com.google.common.util.concurrent.ListenableFuture<kotlin.Unit> leaveCustomAudienceAsync(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request);
diff --git a/privacysandbox/ads/ads-adservices-java/build.gradle b/privacysandbox/ads/ads-adservices-java/build.gradle
index 6cd503f..0da0d9d 100644
--- a/privacysandbox/ads/ads-adservices-java/build.gradle
+++ b/privacysandbox/ads/ads-adservices-java/build.gradle
@@ -52,6 +52,7 @@
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
+    androidTestImplementation(project(":internal-testutils-truth"))
 
     androidTestImplementation(libs.mockitoCore4, excludes.bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(libs.dexmakerMockitoInline, excludes.bytebuddy) // DexMaker has it"s own MockMaker
@@ -63,6 +64,7 @@
         multiDexEnabled = true
     }
 
+    compileSdkVersion = "android-34-ext10"
     namespace "androidx.privacysandbox.ads.adservices.java"
 }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt
index afc5c68..e7f6741 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt
@@ -19,21 +19,33 @@
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
+import android.view.InputEvent
+import android.view.KeyEvent
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig
+import androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome
+import androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest
+import androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest
+import androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest
 import androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest
+import androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters
 import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion.from
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
 import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
@@ -50,6 +62,7 @@
 import org.mockito.invocation.InvocationOnMock
 import org.mockito.quality.Strictness
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
 @SmallTest
 @SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
@@ -79,9 +92,9 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 30)
     fun testAdSelectionOlderVersions() {
-        Assume.assumeFalse("maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
+        Assume.assumeFalse("maxSdkVersion = API 33/34 ext 3 or API 31/32 ext 8",
             VersionCompatUtil.isTestableVersion(
                 /* minAdServicesVersion=*/ 4,
                 /* minExtServicesVersion=*/ 9))
@@ -89,6 +102,180 @@
     }
 
     @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testUpdateAdCounterHistogramOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          VersionCompatUtil.isTestableVersion(
+                              /* minAdServicesVersion= */ 4,
+                              /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 33/34 ext 7 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 8,
+                /* minExtServicesVersion=*/ 9))
+
+        val managerCompat = from(mContext)
+        val updateAdCounterHistogramRequest = UpdateAdCounterHistogramRequest(
+            adSelectionId,
+            adEventType,
+            seller
+        )
+
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.updateAdCounterHistogramAsync(updateAdCounterHistogramRequest).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("API is unsupported. Min version is API 33 ext 8 or " +
+            "API 31/32 ext 9")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testReportEventOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          VersionCompatUtil.isTestableVersion(
+                              /* minAdServicesVersion= */ 4,
+                              /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 33/34 ext 7 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 8,
+                /* minExtServicesVersion=*/ 9))
+
+        val managerCompat = from(mContext)
+        val reportEventRequest = ReportEventRequest(
+            adSelectionId,
+            eventKey,
+            eventData,
+            reportingDestinations
+        )
+
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.reportEventAsync(reportEventRequest).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("API is unsupported. Min version is API 33 ext 8 or " +
+            "API 31/32 ext 9")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testGetAdSelectionDataOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          VersionCompatUtil.isTestableVersion(
+                              /* minAdServicesVersion= */ 4,
+                              /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 31-34 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        val managerCompat = from(mContext)
+        val getAdSelectionDataRequest = GetAdSelectionDataRequest(seller)
+
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.getAdSelectionDataAsync(getAdSelectionDataRequest).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testPersistAdSelectionResultOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          VersionCompatUtil.isTestableVersion(
+                              /* minAdServicesVersion= */ 4,
+                              /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 31-34 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        val managerCompat = from(mContext)
+        val persistAdSelectionResultRequest = PersistAdSelectionResultRequest(
+            adSelectionId,
+            seller,
+            adSelectionData
+        )
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.persistAdSelectionResultAsync(persistAdSelectionResultRequest).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testReportImpressionOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          VersionCompatUtil.isTestableVersion(
+                              /* minAdServicesVersion= */ 4,
+                              /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 31-34 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        val managerCompat = from(mContext)
+        val reportImpressionRequest = ReportImpressionRequest(adSelectionId)
+
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.reportImpressionAsync(reportImpressionRequest).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("adSelectionConfig is mandatory for" +
+            "API versions lower than ext 10")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testSelectAdsFromOutcomesOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 31-34 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        val managerCompat = from(mContext)
+
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.selectAdsAsync(adSelectionFromOutcomesConfig).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
     fun testSelectAds() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             VersionCompatUtil.isTestableVersion(
@@ -116,6 +303,33 @@
     }
 
     @Test
+    fun testSelectAdsFromOutcomes() {
+        Assume.assumeTrue("minSdkVersion = API 31 ext 10",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupAdSelectionFromOutcomesResponse(adSelectionManager)
+        val managerCompat = from(mContext)
+
+        // Actually invoke the compat code.
+        val result: ListenableFuture<AdSelectionOutcome> =
+            managerCompat!!.selectAdsAsync(adSelectionFromOutcomesConfig)
+
+        // Verify that the result of the compat call is correct.
+        verifyResponse(result.get())
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.AdSelectionFromOutcomesConfig::class.java)
+        verify(adSelectionManager).selectAds(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyRequest(captor.value)
+    }
+
+    @Test
     @SuppressWarnings("NewApi")
     fun testReportImpression() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
@@ -140,6 +354,101 @@
         verifyReportImpressionRequest(captor.value)
     }
 
+    @Test
+    fun testUpdateAdCounterHistogram() {
+        Assume.assumeTrue("minSdkVersion = API 33 ext 8 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 8,
+                /* minExtServicesVersion=*/ 9))
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupUpdateAdCounterHistogramResponse(adSelectionManager)
+        val managerCompat = from(mContext)
+        val updateAdCounterHistogramRequest = UpdateAdCounterHistogramRequest(
+            adSelectionId,
+            adEventType,
+            seller
+        )
+
+        // Actually invoke the compat code.
+        runBlocking {
+            managerCompat!!.updateAdCounterHistogramAsync(updateAdCounterHistogramRequest)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.UpdateAdCounterHistogramRequest::class.java)
+        verify(adSelectionManager).updateAdCounterHistogram(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyUpdateAdCounterHistogramRequest(captor.value)
+    }
+
+    @Test
+    fun testReportEvent() {
+        Assume.assumeTrue("minSdkVersion = API 33 ext 8 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 8,
+                /* minExtServicesVersion=*/ 9))
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupReportEventResponse(adSelectionManager)
+        val managerCompat = from(mContext)
+        val reportEventRequest = ReportEventRequest(
+            adSelectionId,
+            eventKey,
+            eventData,
+            reportingDestinations,
+            inputEvent
+        )
+
+        // Actually invoke the compat code.
+        runBlocking {
+            managerCompat!!.reportEventAsync(reportEventRequest)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.ReportEventRequest::class.java)
+        verify(adSelectionManager).reportEvent(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyReportEventRequest(captor.value)
+    }
+
+    @Test
+    fun testPersistAdSelectionResult() {
+        Assume.assumeTrue("minSdkVersion = API 31 ext 10",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupGetAdSelectionResponse(adSelectionManager)
+
+        val managerCompat = from(mContext)
+        val persistAdSelectionResultRequest = PersistAdSelectionResultRequest(
+            adSelectionId,
+            seller,
+            adSelectionData
+        )
+
+        // Actually invoke the compat code.
+        val result = runBlocking {
+            managerCompat!!.persistAdSelectionResultAsync(persistAdSelectionResultRequest)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.PersistAdSelectionResultRequest::class.java)
+        verify(adSelectionManager).persistAdSelectionResult(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyPersistAdSelectionResultRequest(captor.value)
+
+        verifyResponse(result.get())
+    }
+
     @SuppressWarnings("NewApi")
     @SdkSuppress(minSdkVersion = 30)
     companion object {
@@ -165,6 +474,21 @@
             sellerSignals,
             perBuyerSignals,
             trustedScoringSignalsUri)
+        private const val adEventType = FrequencyCapFilters.AD_EVENT_TYPE_VIEW
+        private const val eventKey = "click"
+        private const val eventData = "{\"key\":\"value\"}"
+        private const val reportingDestinations =
+            ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER
+        private val adSelectionData = byteArrayOf(0x01, 0x02, 0x03, 0x04)
+        private val inputEvent: InputEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1)
+        private val adSelectionIds: List<Long> = listOf(10, 11, 12)
+        private val selectionLogicUri: Uri = Uri.parse("www.abc.com")
+        private val adSelectionFromOutcomesConfig = AdSelectionFromOutcomesConfig(
+            seller,
+            adSelectionIds,
+            adSelectionSignals,
+            selectionLogicUri
+        )
 
         // Response.
         private val renderUri = Uri.parse("render-uri.com")
@@ -204,7 +528,7 @@
             }
             doAnswer(answer)
                 .`when`(adSelectionManager).selectAds(
-                    any(),
+                    any<android.adservices.adselection.AdSelectionConfig>(),
                     any(),
                     any()
                 )
@@ -217,6 +541,84 @@
             doAnswer(answer2).`when`(adSelectionManager).reportImpression(any(), any(), any())
         }
 
+        private fun setupAdSelectionFromOutcomesResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // Set up the response that AdSelectionManager will return when the compat code calls
+            // it.
+            val response = android.adservices.adselection.AdSelectionOutcome.Builder()
+                .setAdSelectionId(adSelectionId)
+                .setRenderUri(renderUri)
+                .build()
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<
+                    android.adservices.adselection.AdSelectionOutcome, Exception>>(2)
+                receiver.onResult(response)
+                null
+            }
+            doAnswer(answer)
+                .`when`(adSelectionManager).selectAds(
+                    any<android.adservices.adselection.AdSelectionFromOutcomesConfig>(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupUpdateAdCounterHistogramResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // Set up the response that AdSelectionManager will return when the compat code calls
+            // UpdateAdCounterHistogramResponse().
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
+                receiver.onResult(Object())
+                null
+            }
+            doAnswer(answer)
+                .`when`(adSelectionManager).updateAdCounterHistogram(
+                    any(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupReportEventResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // Set up the response that AdSelectionManager will return when the compat code calls
+            // ReportEvent().
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
+                receiver.onResult(Object())
+                null
+            }
+            doAnswer(answer)
+                .`when`(adSelectionManager).reportEvent(
+                    any(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupGetAdSelectionResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // There is no way to create a GetAdSelectionDataOutcome instance outside of adservices
+
+            val response2 = android.adservices.adselection.AdSelectionOutcome.Builder()
+                .setAdSelectionId(adSelectionId)
+                .setRenderUri(renderUri)
+                .build()
+            val answer2 = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<
+                    android.adservices.adselection.AdSelectionOutcome, Exception>>(2)
+                receiver.onResult(response2)
+                null
+            }
+            doAnswer(answer2)
+                .`when`(adSelectionManager).persistAdSelectionResult(any(), any(), any())
+        }
+
         private fun verifyRequest(request: android.adservices.adselection.AdSelectionConfig) {
             // Set up the request that we expect the compat code to invoke.
             val expectedRequest = getPlatformAdSelectionConfig()
@@ -224,11 +626,20 @@
             Assert.assertEquals(expectedRequest, request)
         }
 
+        private fun verifyRequest(
+            request: android.adservices.adselection.AdSelectionFromOutcomesConfig
+        ) {
+            // Set up the request that we expect the compat code to invoke.
+            val expectedRequest = getPlatformAdSelectionFromOutcomesConfig()
+
+            Assert.assertEquals(expectedRequest, request)
+        }
+
         private fun verifyResponse(
-            outcome: androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome
+            outcome: AdSelectionOutcome
         ) {
             val expectedOutcome =
-                androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome(
+                AdSelectionOutcome(
                     adSelectionId,
                     renderUri)
             Assert.assertEquals(expectedOutcome, outcome)
@@ -252,6 +663,19 @@
                 .build()
         }
 
+        private fun getPlatformAdSelectionFromOutcomesConfig():
+            android.adservices.adselection.AdSelectionFromOutcomesConfig {
+            val adTechIdentifier = android.adservices.common.AdTechIdentifier.fromString(adId)
+            return android.adservices.adselection.AdSelectionFromOutcomesConfig.Builder()
+                .setSelectionSignals(
+                    android.adservices.common.AdSelectionSignals.fromString(adSelectionSignalsStr)
+                )
+                .setAdSelectionIds(adSelectionIds)
+                .setSelectionLogicUri(selectionLogicUri)
+                .setSeller(adTechIdentifier)
+                .build()
+        }
+
         private fun verifyReportImpressionRequest(
             request: android.adservices.adselection.ReportImpressionRequest
         ) {
@@ -261,5 +685,55 @@
             Assert.assertEquals(expectedRequest.adSelectionId, request.adSelectionId)
             Assert.assertEquals(expectedRequest.adSelectionConfig, request.adSelectionConfig)
         }
+
+        private fun verifyUpdateAdCounterHistogramRequest(
+            request: android.adservices.adselection.UpdateAdCounterHistogramRequest
+        ) {
+            val adTechIdentifier = android.adservices.common.AdTechIdentifier.fromString(adId)
+            val expectedRequest = android.adservices.adselection.UpdateAdCounterHistogramRequest
+                .Builder(adSelectionId, adEventType, adTechIdentifier)
+                .build()
+            Assert.assertEquals(expectedRequest, request)
+        }
+
+        private fun verifyReportEventRequest(
+            request: android.adservices.adselection.ReportEventRequest
+        ) {
+            val checkInputEvent = VersionCompatUtil.isTestableVersion(10, 10)
+            val expectedRequestBuilder = android.adservices.adselection.ReportEventRequest.Builder(
+                adSelectionId,
+                eventKey,
+                eventData,
+                reportingDestinations)
+
+            if (checkInputEvent)
+                expectedRequestBuilder.setInputEvent(inputEvent)
+
+            val expectedRequest = expectedRequestBuilder.build()
+            Assert.assertEquals(expectedRequest.adSelectionId, request.adSelectionId)
+            Assert.assertEquals(expectedRequest.key, request.key)
+            Assert.assertEquals(expectedRequest.data, request.data)
+            Assert.assertEquals(expectedRequest.reportingDestinations,
+                request.reportingDestinations)
+            if (checkInputEvent)
+                Assert.assertEquals(expectedRequest.inputEvent,
+                    request.inputEvent)
+        }
+
+        private fun verifyPersistAdSelectionResultRequest(
+            request: android.adservices.adselection.PersistAdSelectionResultRequest
+        ) {
+            val adTechIdentifier = android.adservices.common.AdTechIdentifier.fromString(adId)
+            val expectedRequest = android.adservices.adselection.PersistAdSelectionResultRequest
+                .Builder()
+                .setAdSelectionId(adSelectionId)
+                .setSeller(adTechIdentifier)
+                .setAdSelectionResult(adSelectionData)
+                .build()
+            Assert.assertEquals(expectedRequest.adSelectionId, request.adSelectionId)
+            Assert.assertEquals(expectedRequest.seller, request.seller)
+            Assert.assertTrue(expectedRequest.adSelectionResult
+                .contentEquals(request.adSelectionResult))
+        }
     }
 }
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt
index 9da1419..a86db9e 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt
@@ -23,7 +23,9 @@
 import androidx.privacysandbox.ads.adservices.common.AdData
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudience
+import androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData
@@ -33,10 +35,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
 import java.time.Instant
+import java.util.concurrent.ExecutionException
 import org.junit.After
 import org.junit.Assume
 import org.junit.Before
@@ -52,6 +56,7 @@
 import org.mockito.invocation.InvocationOnMock
 import org.mockito.quality.Strictness
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
 @SmallTest
 @SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
@@ -91,6 +96,40 @@
     }
 
     @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testFetchAndJoinCustomAudienceOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          VersionCompatUtil.isTestableVersion(
+                              /* minAdServicesVersion= */ 4,
+                              /* minExtServicesVersion=*/ 9))
+
+        /* API is not available */
+        Assume.assumeFalse("maxSdkVersion = API 31-34 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        val managerCompat = from(mContext)
+
+        // Actually invoke the compat code.
+        val request = FetchAndJoinCustomAudienceRequest(
+            uri,
+            name,
+            activationTime,
+            expirationTime,
+            userBiddingSignals
+        )
+
+        // Verify that it throws an exception
+        val exception = assertThrows(ExecutionException::class.java) {
+            managerCompat!!.fetchAndJoinCustomAudienceAsync(request).get()
+        }.hasCauseThat()
+        exception.isInstanceOf(UnsupportedOperationException::class.java)
+        exception.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
     fun testJoinCustomAudience() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             VersionCompatUtil.isTestableVersion(
@@ -123,6 +162,38 @@
     }
 
     @Test
+    fun testFetchAndJoinCustomAudience() {
+        Assume.assumeTrue("minSdkVersion = API 31 ext 10",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 10,
+                /* minExtServicesVersion=*/ 10))
+
+        val customAudienceManager =
+            mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupFetchAndJoinResponse(customAudienceManager)
+        val managerCompat = from(mContext)
+
+        // Actually invoke the compat code.
+        val request = FetchAndJoinCustomAudienceRequest(
+            uri,
+            name,
+            activationTime,
+            expirationTime,
+            userBiddingSignals
+        )
+        managerCompat!!.fetchAndJoinCustomAudienceAsync(request).get()
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.customaudience.FetchAndJoinCustomAudienceRequest::class.java
+        )
+        verify(customAudienceManager).fetchAndJoinCustomAudience(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyFetchAndJoinCustomAudienceRequest(captor.value)
+    }
+
+    @Test
     fun testLeaveCustomAudience() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             VersionCompatUtil.isTestableVersion(
@@ -161,6 +232,8 @@
         private val trustedBiddingSignals: TrustedBiddingData = TrustedBiddingData(uri, keys)
         private const val metadata = "metadata"
         private val ads: List<AdData> = listOf(AdData(uri, metadata))
+        private val activationTime: Instant = Instant.ofEpochSecond(5)
+        private val expirationTime: Instant = Instant.ofEpochSecond(10)
 
         private fun mockCustomAudienceManager(
             spyContext: Context,
@@ -187,6 +260,16 @@
             doAnswer(answer).`when`(customAudienceManager).leaveCustomAudience(any(), any(), any())
         }
 
+        private fun setupFetchAndJoinResponse(customAudienceManager: CustomAudienceManager) {
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
+                receiver.onResult(Object())
+                null
+            }
+            doAnswer(answer).`when`(customAudienceManager)
+                .fetchAndJoinCustomAudience(any(), any(), any())
+        }
+
         private fun verifyJoinCustomAudienceRequest(
             joinCustomAudienceRequest: android.adservices.customaudience.JoinCustomAudienceRequest
         ) {
@@ -245,6 +328,27 @@
                 signals).isTrue()
         }
 
+        private fun verifyFetchAndJoinCustomAudienceRequest(
+            fetchAndJoinCustomAudienceRequest:
+            android.adservices.customaudience.FetchAndJoinCustomAudienceRequest
+        ) {
+            // Set up the request that we expect the compat code to invoke.
+            val userBiddingSignals = android.adservices.common.AdSelectionSignals.fromString(
+                signals
+            )
+            val expectedRequest = android.adservices.customaudience
+                .FetchAndJoinCustomAudienceRequest
+                .Builder(uri)
+                .setName(name)
+                .setActivationTime(activationTime)
+                .setExpirationTime(expirationTime)
+                .setUserBiddingSignals(userBiddingSignals)
+                .build()
+
+            // Verify that the actual request matches the expected one.
+            Truth.assertThat(expectedRequest == fetchAndJoinCustomAudienceRequest).isTrue()
+        }
+
         private fun verifyLeaveCustomAudienceRequest(
             leaveCustomAudienceRequest: android.adservices.customaudience.LeaveCustomAudienceRequest
         ) {
diff --git a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFutures.kt b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFutures.kt
index d5c4ed3..0c4a0ed 100644
--- a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFutures.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFutures.kt
@@ -23,10 +23,17 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresPermission
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig
+import androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion.obtain
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome
+import androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome
+import androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest
+import androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest
+import androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest
 import androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest
+import androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.java.internal.asListenableFuture
 import com.google.common.util.concurrent.ListenableFuture
 import java.util.concurrent.TimeoutException
@@ -38,6 +45,7 @@
  * This class provides APIs to select ads and report impressions.
  * This class can be used by Java clients.
  */
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
 abstract class AdSelectionManagerFutures internal constructor() {
 
     /**
@@ -71,10 +79,62 @@
     ): ListenableFuture<AdSelectionOutcome>
 
     /**
+     * Selects an ad from the results of previously ran ad selections.
+     *
+     * @param adSelectionFromOutcomesConfig is provided by the Ads SDK and the
+     * [AdSelectionFromOutcomesConfig] object is transferred via a Binder call. For this reason, the
+     * total size of these objects is bound to the Android IPC limitations. Failures to transfer the
+     * [AdSelectionFromOutcomesConfig] will throw an [TransactionTooLargeException].
+     *
+     * The output is passed by the receiver, which either returns an [AdSelectionOutcome]
+     * for a successful run, or an [Exception] includes the type of the exception thrown and
+     * the corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to run the ad selection.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [TimeoutException] is thrown, it is caused when a timeout is encountered
+     * during bidding, scoring, or overall selection process to find winning Ad.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract fun selectAdsAsync(
+        adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig
+    ): ListenableFuture<AdSelectionOutcome>
+
+    /**
      * Report the given impression. The [ReportImpressionRequest] is provided by the Ads SDK.
      * The receiver either returns a {@code void} for a successful run, or an [Exception]
      * indicates the error.
      *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to report the impression.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support [ReportImpressionRequest] with null
+     * {@code AdSelectionConfig}
+     *
      * @param reportImpressionRequest the request for reporting impression.
      */
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
@@ -82,6 +142,164 @@
         reportImpressionRequest: ReportImpressionRequest
     ): ListenableFuture<Unit>
 
+    /**
+     * Notifies the service that there is a new ad event to report for the ad selected by the
+     * ad-selection run identified by {@code adSelectionId}. An ad event is any occurrence that
+     * happens to an ad associated with the given {@code adSelectionId}. There is no guarantee about
+     * when the ad event will be reported. The event reporting could be delayed and reports could be
+     * batched.
+     *
+     * Using [ReportEventRequest#getKey()], the service will fetch the {@code reportingUri}
+     * that was registered in {@code registerAdBeacon}. See documentation of [reportImpressionAsync]
+     * for more details regarding {@code registerAdBeacon}. Then, the service will attach
+     * [ReportEventRequest#getData()] to the request body of a POST request and send the request.
+     * The body of the POST request will have the {@code content-type} of {@code text/plain}, and
+     * the data will be transmitted in {@code charset=UTF-8}.
+     *
+     * The output is passed by the receiver, which either returns an empty [Object] for a
+     * successful run, or an [Exception] includes the type of the exception thrown and the
+     * corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to report the ad event.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * Events will be reported at most once as a best-effort attempt.
+     *
+     * @param reportEventRequest the request for reporting event.
+     */
+    @ExperimentalFeatures.Ext8OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract fun reportEventAsync(
+        reportEventRequest: ReportEventRequest
+    ): ListenableFuture<Unit>
+
+    /**
+     * Updates the counter histograms for an ad which was previously selected by a call to
+     * [selectAdsAsync].
+     *
+     * The counter histograms are used in ad selection to inform frequency cap filtering on
+     * candidate ads, where ads whose frequency caps are met or exceeded are removed from the
+     * bidding process during ad selection.
+     *
+     * Counter histograms can only be updated for ads specified by the given {@code
+     * adSelectionId} returned by a recent call to Protected Audience API ad selection from the same
+     * caller app.
+     *
+     * A [SecurityException] is returned if:
+     *
+     * <ol>
+     *   <li>the app has not declared the correct permissions in its manifest, or
+     *   <li>the app or entity identified by the {@code callerAdTechIdentifier} are not authorized
+     *       to use the API.
+     * </ol>
+     *
+     * An [IllegalStateException] is returned if the call does not come from an app with a
+     * foreground activity.
+     *
+     * A [LimitExceededException] is returned if the call exceeds the calling app's API throttle.
+     *
+     * An [UnsupportedOperationException] is returned if the Android API level and AdServices module
+     * versions don't support this API.
+     *
+     * In all other failure cases, it will return an empty [Object]. Note that to protect user
+     * privacy, internal errors will not be sent back via an exception.
+     *
+     * @param updateAdCounterHistogramRequest the request for updating the ad counter histogram.
+     */
+    @ExperimentalFeatures.Ext8OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract fun
+        updateAdCounterHistogramAsync(
+        updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest
+    ): ListenableFuture<Unit>
+
+    /**
+     * Collects custom audience data from device. Returns a compressed and encrypted blob to send to
+     * auction servers for ad selection.
+     *
+     * Custom audience ads must have a {@code ad_render_id} to be eligible for to be collected.
+     *
+     * See [AdSelectionManager#persistAdSelectionResult] for how to process the results of
+     * the ad selection run on server-side with the blob generated by this API.
+     *
+     * The output is passed by the receiver, which either returns an [GetAdSelectionDataOutcome]
+     * for a successful run, or an [Exception] includes the type of
+     * the exception thrown and the corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to run the ad selection.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [TimeoutException] is thrown, it is caused when a timeout is encountered
+     * during bidding, scoring, or overall selection process to find winning Ad.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * @param getAdSelectionDataRequest the request for get ad selection data.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract fun getAdSelectionDataAsync(
+        getAdSelectionDataRequest: GetAdSelectionDataRequest
+    ): ListenableFuture<GetAdSelectionDataOutcome>
+
+    /**
+     * Persists the ad selection results from the server-side.
+     *
+     * See [AdSelectionManager#getAdSelectionData] for how to generate an encrypted blob to
+     * run an ad selection on the server side.
+     *
+     * The output is passed by the receiver, which either returns an [AdSelectionOutcome]
+     * for a successful run, or an [Exception] includes the type of the exception thrown and
+     * the corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to run the ad selection.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [TimeoutException] is thrown, it is caused when a timeout is encountered
+     * during bidding, scoring, or overall selection process to find winning Ad.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * @param persistAdSelectionResultRequest the request for persist ad selection result.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract fun persistAdSelectionResultAsync(
+        persistAdSelectionResultRequest: PersistAdSelectionResultRequest
+    ): ListenableFuture<AdSelectionOutcome>
+
     private class Api33Ext4JavaImpl(
         private val mAdSelectionManager: AdSelectionManager?
     ) : AdSelectionManagerFutures() {
@@ -95,6 +313,17 @@
             }.asListenableFuture()
         }
 
+        @OptIn(ExperimentalFeatures.Ext10OptIn::class)
+        @DoNotInline
+        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+        override fun selectAdsAsync(
+            adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig
+        ): ListenableFuture<AdSelectionOutcome> {
+            return CoroutineScope(Dispatchers.Default).async {
+                mAdSelectionManager!!.selectAds(adSelectionFromOutcomesConfig)
+            }.asListenableFuture()
+        }
+
         @DoNotInline
         @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
         override fun reportImpressionAsync(
@@ -104,6 +333,49 @@
                 mAdSelectionManager!!.reportImpression(reportImpressionRequest)
             }.asListenableFuture()
         }
+
+        @DoNotInline
+        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+        override fun updateAdCounterHistogramAsync(
+            updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest
+        ): ListenableFuture<Unit> {
+            return CoroutineScope(Dispatchers.Default).async {
+                mAdSelectionManager!!.updateAdCounterHistogram(updateAdCounterHistogramRequest)
+            }.asListenableFuture()
+        }
+
+        @OptIn(ExperimentalFeatures.Ext8OptIn::class)
+        @DoNotInline
+        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+        override fun reportEventAsync(
+            reportEventRequest: ReportEventRequest
+        ): ListenableFuture<Unit> {
+            return CoroutineScope(Dispatchers.Default).async {
+                mAdSelectionManager!!.reportEvent(reportEventRequest)
+            }.asListenableFuture()
+        }
+
+        @OptIn(ExperimentalFeatures.Ext10OptIn::class)
+        @DoNotInline
+        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+        override fun getAdSelectionDataAsync(
+            getAdSelectionDataRequest: GetAdSelectionDataRequest
+        ): ListenableFuture<GetAdSelectionDataOutcome> {
+            return CoroutineScope(Dispatchers.Default).async {
+                mAdSelectionManager!!.getAdSelectionData(getAdSelectionDataRequest)
+            }.asListenableFuture()
+        }
+
+        @OptIn(ExperimentalFeatures.Ext10OptIn::class)
+        @DoNotInline
+        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+        override fun persistAdSelectionResultAsync(
+            persistAdSelectionResultRequest: PersistAdSelectionResultRequest
+        ): ListenableFuture<AdSelectionOutcome> {
+            return CoroutineScope(Dispatchers.Default).async {
+                mAdSelectionManager!!.persistAdSelectionResult(persistAdSelectionResultRequest)
+            }.asListenableFuture()
+        }
     }
 
     companion object {
diff --git a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFutures.kt b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFutures.kt
index 4aac7df..ee6d8a3 100644
--- a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFutures.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFutures.kt
@@ -21,9 +21,11 @@
 import android.os.LimitExceededException
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresPermission
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudience
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager.Companion.obtain
+import androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.java.internal.asListenableFuture
@@ -76,6 +78,46 @@
     ): ListenableFuture<Unit>
 
     /**
+     * Adds the user to the [CustomAudience] fetched from a {@code fetchUri}.
+     *
+     * An attempt to register the user for a custom audience with the same combination of {@code
+     * ownerPackageName}, {@code buyer}, and {@code name} will cause the existing custom audience's
+     * information to be overwritten, including the list of ads data.
+     *
+     * Note that the ads list can be completely overwritten by the daily background fetch job.
+     *
+     * This call fails with an [SecurityException] if
+     *
+     * <ol>
+     *   <li>the {@code ownerPackageName} is not calling app's package name and/or
+     *   <li>the buyer is not authorized to use the API.
+     * </ol>
+     *
+     * This call fails with an [IllegalArgumentException] if
+     *
+     * <ol>
+     *   <li>the storage limit has been exceeded by the calling application and/or
+     *   <li>any URI parameters in the [CustomAudience] given are not authenticated with the
+     *       [CustomAudience] buyer.
+     * </ol>
+     *
+     * This call fails with [LimitExceededException] if the calling package exceeds the
+     * allowed rate limits and is throttled.
+     *
+     * This call fails with an [IllegalStateException] if an internal service error is encountered.
+     *
+     * This call fails with an [UnsupportedOperationException] if the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * @param request The request to fetch and join custom audience.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract fun fetchAndJoinCustomAudienceAsync(
+        request: FetchAndJoinCustomAudienceRequest
+    ): ListenableFuture<Unit>
+
+    /**
      * Attempts to remove a user from a custom audience by deleting any existing
      * [CustomAudience] data, identified by {@code ownerPackageName}, {@code buyer}, and {@code
      * name}.
@@ -114,6 +156,17 @@
             }.asListenableFuture()
         }
 
+        @OptIn(ExperimentalFeatures.Ext10OptIn::class)
+        @DoNotInline
+        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+        override fun fetchAndJoinCustomAudienceAsync(
+            request: FetchAndJoinCustomAudienceRequest
+        ): ListenableFuture<Unit> {
+            return CoroutineScope(Dispatchers.Default).async {
+                mCustomAudienceManager!!.fetchAndJoinCustomAudience(request)
+            }.asListenableFuture()
+        }
+
         @DoNotInline
         @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
         override fun leaveCustomAudienceAsync(
diff --git a/privacysandbox/ads/ads-adservices/api/1.1.0-beta03.txt b/privacysandbox/ads/ads-adservices/api/1.1.0-beta03.txt
index ab2bd03..32149c9 100644
--- a/privacysandbox/ads/ads-adservices/api/1.1.0-beta03.txt
+++ b/privacysandbox/ads/ads-adservices/api/1.1.0-beta03.txt
@@ -40,10 +40,28 @@
     property public final android.net.Uri trustedScoringSignalsUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class AdSelectionFromOutcomesConfig {
+    ctor public AdSelectionFromOutcomesConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, java.util.List<java.lang.Long> adSelectionIds, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, android.net.Uri selectionLogicUri);
+    method public java.util.List<java.lang.Long> getAdSelectionIds();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+    method public android.net.Uri getSelectionLogicUri();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+    method public void setSelectionLogicUri(android.net.Uri);
+    property public final java.util.List<java.lang.Long> adSelectionIds;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+    property public final android.net.Uri selectionLogicUri;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+  }
+
   public abstract class AdSelectionManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? getAdSelectionData(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome>);
     method public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? persistAdSelectionResult(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? reportEvent(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? reportImpression(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? updateAdCounterHistogram(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion Companion;
   }
 
@@ -55,11 +73,62 @@
     ctor public AdSelectionOutcome(long adSelectionId, android.net.Uri renderUri);
     method public long getAdSelectionId();
     method public android.net.Uri getRenderUri();
+    method @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public boolean hasOutcome();
     property public final long adSelectionId;
     property public final android.net.Uri renderUri;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion Companion;
+    field @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome NO_OUTCOME;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final class AdSelectionOutcome.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataOutcome {
+    ctor public GetAdSelectionDataOutcome(long adSelectionId, optional byte[]? adSelectionData);
+    method public byte[]? getAdSelectionData();
+    method public long getAdSelectionId();
+    property public final byte[]? adSelectionData;
+    property public final long adSelectionId;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
+    ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller);
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
+    ctor public PersistAdSelectionResultRequest(long adSelectionId, optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional byte[]? adSelectionResult);
+    method public long getAdSelectionId();
+    method public byte[]? getAdSelectionResult();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final long adSelectionId;
+    property public final byte[]? adSelectionResult;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class ReportEventRequest {
+    ctor public ReportEventRequest(long adSelectionId, String eventKey, String eventData, int reportingDestinations, optional android.view.InputEvent? inputEvent);
+    method public long getAdSelectionId();
+    method public String getEventData();
+    method public String getEventKey();
+    method public android.view.InputEvent? getInputEvent();
+    method public int getReportingDestinations();
+    property public final long adSelectionId;
+    property public final String eventData;
+    property public final String eventKey;
+    property public final android.view.InputEvent? inputEvent;
+    property public final int reportingDestinations;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest.Companion Companion;
+    field public static final int FLAG_REPORTING_DESTINATION_BUYER = 2; // 0x2
+    field public static final int FLAG_REPORTING_DESTINATION_SELLER = 1; // 0x1
+  }
+
+  public static final class ReportEventRequest.Companion {
   }
 
   public final class ReportImpressionRequest {
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public ReportImpressionRequest(long adSelectionId);
     ctor public ReportImpressionRequest(long adSelectionId, androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
     method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig getAdSelectionConfig();
     method public long getAdSelectionId();
@@ -67,6 +136,16 @@
     property public final long adSelectionId;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class UpdateAdCounterHistogramRequest {
+    ctor public UpdateAdCounterHistogramRequest(long adSelectionId, int adEventType, androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech);
+    method public int getAdEventType();
+    method public long getAdSelectionId();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getCallerAdTech();
+    property public final int adEventType;
+    property public final long adSelectionId;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.appsetid {
@@ -101,12 +180,26 @@
 
   public final class AdData {
     ctor public AdData(android.net.Uri renderUri, String metadata);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters, optional String? adRenderId);
+    method public java.util.Set<java.lang.Integer> getAdCounterKeys();
+    method public androidx.privacysandbox.ads.adservices.common.AdFilters? getAdFilters();
+    method public String? getAdRenderId();
     method public String getMetadata();
     method public android.net.Uri getRenderUri();
+    property public final java.util.Set<java.lang.Integer> adCounterKeys;
+    property public final androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters;
+    property public final String? adRenderId;
     property public final String metadata;
     property public final android.net.Uri renderUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class AdFilters {
+    ctor public AdFilters(androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters);
+    method public androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? getFrequencyCapFilters();
+    property public final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters;
+  }
+
   public final class AdSelectionSignals {
     ctor public AdSelectionSignals(String signals);
     method public String getSignals();
@@ -122,9 +215,49 @@
   public sealed interface ExperimentalFeatures {
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class FrequencyCapFilters {
+    ctor public FrequencyCapFilters();
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents);
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents();
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents;
+    field public static final int AD_EVENT_TYPE_CLICK = 3; // 0x3
+    field public static final int AD_EVENT_TYPE_IMPRESSION = 1; // 0x1
+    field public static final int AD_EVENT_TYPE_VIEW = 2; // 0x2
+    field public static final int AD_EVENT_TYPE_WIN = 0; // 0x0
+    field public static final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters.Companion Companion;
+  }
+
+  public static final class FrequencyCapFilters.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class KeyedFrequencyCap {
+    ctor public KeyedFrequencyCap(int adCounterKey, int maxCount, java.time.Duration interval);
+    method public int getAdCounterKey();
+    method public java.time.Duration getInterval();
+    method public int getMaxCount();
+    property public final int adCounterKey;
+    property public final java.time.Duration interval;
+    property public final int maxCount;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.customaudience {
@@ -166,6 +299,7 @@
   }
 
   public abstract class CustomAudienceManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? fetchAndJoinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? joinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? leaveCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
@@ -176,6 +310,20 @@
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class FetchAndJoinCustomAudienceRequest {
+    ctor public FetchAndJoinCustomAudienceRequest(android.net.Uri fetchUri, optional String? name, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals);
+    method public java.time.Instant? getActivationTime();
+    method public java.time.Instant? getExpirationTime();
+    method public android.net.Uri getFetchUri();
+    method public String? getName();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+    property public final java.time.Instant? activationTime;
+    property public final java.time.Instant? expirationTime;
+    property public final android.net.Uri fetchUri;
+    property public final String? name;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+  }
+
   public final class JoinCustomAudienceRequest {
     ctor public JoinCustomAudienceRequest(androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience);
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience getCustomAudience();
diff --git a/privacysandbox/ads/ads-adservices/api/current.ignore b/privacysandbox/ads/ads-adservices/api/current.ignore
index 7d3e9cf..05e0309 100644
--- a/privacysandbox/ads/ads-adservices/api/current.ignore
+++ b/privacysandbox/ads/ads-adservices/api/current.ignore
@@ -5,3 +5,14 @@
 
 RemovedMethod: androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest#getShouldRecordObservation():
     Removed method androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.getShouldRecordObservation()
+
+
+AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdCounterKeys():
+    Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdCounterKeys()
+AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdFilters():
+    Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdFilters()
+AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdRenderId():
+    Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdRenderId()
+
+AddedField: androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome#Companion:
+    Added field androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion
diff --git a/privacysandbox/ads/ads-adservices/api/current.txt b/privacysandbox/ads/ads-adservices/api/current.txt
index ab2bd03..32149c9 100644
--- a/privacysandbox/ads/ads-adservices/api/current.txt
+++ b/privacysandbox/ads/ads-adservices/api/current.txt
@@ -40,10 +40,28 @@
     property public final android.net.Uri trustedScoringSignalsUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class AdSelectionFromOutcomesConfig {
+    ctor public AdSelectionFromOutcomesConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, java.util.List<java.lang.Long> adSelectionIds, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, android.net.Uri selectionLogicUri);
+    method public java.util.List<java.lang.Long> getAdSelectionIds();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+    method public android.net.Uri getSelectionLogicUri();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+    method public void setSelectionLogicUri(android.net.Uri);
+    property public final java.util.List<java.lang.Long> adSelectionIds;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+    property public final android.net.Uri selectionLogicUri;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+  }
+
   public abstract class AdSelectionManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? getAdSelectionData(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome>);
     method public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? persistAdSelectionResult(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? reportEvent(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? reportImpression(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? updateAdCounterHistogram(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion Companion;
   }
 
@@ -55,11 +73,62 @@
     ctor public AdSelectionOutcome(long adSelectionId, android.net.Uri renderUri);
     method public long getAdSelectionId();
     method public android.net.Uri getRenderUri();
+    method @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public boolean hasOutcome();
     property public final long adSelectionId;
     property public final android.net.Uri renderUri;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion Companion;
+    field @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome NO_OUTCOME;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final class AdSelectionOutcome.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataOutcome {
+    ctor public GetAdSelectionDataOutcome(long adSelectionId, optional byte[]? adSelectionData);
+    method public byte[]? getAdSelectionData();
+    method public long getAdSelectionId();
+    property public final byte[]? adSelectionData;
+    property public final long adSelectionId;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
+    ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller);
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
+    ctor public PersistAdSelectionResultRequest(long adSelectionId, optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional byte[]? adSelectionResult);
+    method public long getAdSelectionId();
+    method public byte[]? getAdSelectionResult();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final long adSelectionId;
+    property public final byte[]? adSelectionResult;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class ReportEventRequest {
+    ctor public ReportEventRequest(long adSelectionId, String eventKey, String eventData, int reportingDestinations, optional android.view.InputEvent? inputEvent);
+    method public long getAdSelectionId();
+    method public String getEventData();
+    method public String getEventKey();
+    method public android.view.InputEvent? getInputEvent();
+    method public int getReportingDestinations();
+    property public final long adSelectionId;
+    property public final String eventData;
+    property public final String eventKey;
+    property public final android.view.InputEvent? inputEvent;
+    property public final int reportingDestinations;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest.Companion Companion;
+    field public static final int FLAG_REPORTING_DESTINATION_BUYER = 2; // 0x2
+    field public static final int FLAG_REPORTING_DESTINATION_SELLER = 1; // 0x1
+  }
+
+  public static final class ReportEventRequest.Companion {
   }
 
   public final class ReportImpressionRequest {
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public ReportImpressionRequest(long adSelectionId);
     ctor public ReportImpressionRequest(long adSelectionId, androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
     method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig getAdSelectionConfig();
     method public long getAdSelectionId();
@@ -67,6 +136,16 @@
     property public final long adSelectionId;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class UpdateAdCounterHistogramRequest {
+    ctor public UpdateAdCounterHistogramRequest(long adSelectionId, int adEventType, androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech);
+    method public int getAdEventType();
+    method public long getAdSelectionId();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getCallerAdTech();
+    property public final int adEventType;
+    property public final long adSelectionId;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.appsetid {
@@ -101,12 +180,26 @@
 
   public final class AdData {
     ctor public AdData(android.net.Uri renderUri, String metadata);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters, optional String? adRenderId);
+    method public java.util.Set<java.lang.Integer> getAdCounterKeys();
+    method public androidx.privacysandbox.ads.adservices.common.AdFilters? getAdFilters();
+    method public String? getAdRenderId();
     method public String getMetadata();
     method public android.net.Uri getRenderUri();
+    property public final java.util.Set<java.lang.Integer> adCounterKeys;
+    property public final androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters;
+    property public final String? adRenderId;
     property public final String metadata;
     property public final android.net.Uri renderUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class AdFilters {
+    ctor public AdFilters(androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters);
+    method public androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? getFrequencyCapFilters();
+    property public final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters;
+  }
+
   public final class AdSelectionSignals {
     ctor public AdSelectionSignals(String signals);
     method public String getSignals();
@@ -122,9 +215,49 @@
   public sealed interface ExperimentalFeatures {
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class FrequencyCapFilters {
+    ctor public FrequencyCapFilters();
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents);
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents();
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents;
+    field public static final int AD_EVENT_TYPE_CLICK = 3; // 0x3
+    field public static final int AD_EVENT_TYPE_IMPRESSION = 1; // 0x1
+    field public static final int AD_EVENT_TYPE_VIEW = 2; // 0x2
+    field public static final int AD_EVENT_TYPE_WIN = 0; // 0x0
+    field public static final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters.Companion Companion;
+  }
+
+  public static final class FrequencyCapFilters.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class KeyedFrequencyCap {
+    ctor public KeyedFrequencyCap(int adCounterKey, int maxCount, java.time.Duration interval);
+    method public int getAdCounterKey();
+    method public java.time.Duration getInterval();
+    method public int getMaxCount();
+    property public final int adCounterKey;
+    property public final java.time.Duration interval;
+    property public final int maxCount;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.customaudience {
@@ -166,6 +299,7 @@
   }
 
   public abstract class CustomAudienceManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? fetchAndJoinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? joinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? leaveCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
@@ -176,6 +310,20 @@
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class FetchAndJoinCustomAudienceRequest {
+    ctor public FetchAndJoinCustomAudienceRequest(android.net.Uri fetchUri, optional String? name, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals);
+    method public java.time.Instant? getActivationTime();
+    method public java.time.Instant? getExpirationTime();
+    method public android.net.Uri getFetchUri();
+    method public String? getName();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+    property public final java.time.Instant? activationTime;
+    property public final java.time.Instant? expirationTime;
+    property public final android.net.Uri fetchUri;
+    property public final String? name;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+  }
+
   public final class JoinCustomAudienceRequest {
     ctor public JoinCustomAudienceRequest(androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience);
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience getCustomAudience();
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta03.txt b/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta03.txt
index ab2bd03..32149c9 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta03.txt
+++ b/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta03.txt
@@ -40,10 +40,28 @@
     property public final android.net.Uri trustedScoringSignalsUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class AdSelectionFromOutcomesConfig {
+    ctor public AdSelectionFromOutcomesConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, java.util.List<java.lang.Long> adSelectionIds, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, android.net.Uri selectionLogicUri);
+    method public java.util.List<java.lang.Long> getAdSelectionIds();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+    method public android.net.Uri getSelectionLogicUri();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+    method public void setSelectionLogicUri(android.net.Uri);
+    property public final java.util.List<java.lang.Long> adSelectionIds;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+    property public final android.net.Uri selectionLogicUri;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+  }
+
   public abstract class AdSelectionManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? getAdSelectionData(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome>);
     method public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? persistAdSelectionResult(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? reportEvent(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? reportImpression(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? updateAdCounterHistogram(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion Companion;
   }
 
@@ -55,11 +73,62 @@
     ctor public AdSelectionOutcome(long adSelectionId, android.net.Uri renderUri);
     method public long getAdSelectionId();
     method public android.net.Uri getRenderUri();
+    method @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public boolean hasOutcome();
     property public final long adSelectionId;
     property public final android.net.Uri renderUri;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion Companion;
+    field @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome NO_OUTCOME;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final class AdSelectionOutcome.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataOutcome {
+    ctor public GetAdSelectionDataOutcome(long adSelectionId, optional byte[]? adSelectionData);
+    method public byte[]? getAdSelectionData();
+    method public long getAdSelectionId();
+    property public final byte[]? adSelectionData;
+    property public final long adSelectionId;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
+    ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller);
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
+    ctor public PersistAdSelectionResultRequest(long adSelectionId, optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional byte[]? adSelectionResult);
+    method public long getAdSelectionId();
+    method public byte[]? getAdSelectionResult();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final long adSelectionId;
+    property public final byte[]? adSelectionResult;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class ReportEventRequest {
+    ctor public ReportEventRequest(long adSelectionId, String eventKey, String eventData, int reportingDestinations, optional android.view.InputEvent? inputEvent);
+    method public long getAdSelectionId();
+    method public String getEventData();
+    method public String getEventKey();
+    method public android.view.InputEvent? getInputEvent();
+    method public int getReportingDestinations();
+    property public final long adSelectionId;
+    property public final String eventData;
+    property public final String eventKey;
+    property public final android.view.InputEvent? inputEvent;
+    property public final int reportingDestinations;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest.Companion Companion;
+    field public static final int FLAG_REPORTING_DESTINATION_BUYER = 2; // 0x2
+    field public static final int FLAG_REPORTING_DESTINATION_SELLER = 1; // 0x1
+  }
+
+  public static final class ReportEventRequest.Companion {
   }
 
   public final class ReportImpressionRequest {
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public ReportImpressionRequest(long adSelectionId);
     ctor public ReportImpressionRequest(long adSelectionId, androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
     method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig getAdSelectionConfig();
     method public long getAdSelectionId();
@@ -67,6 +136,16 @@
     property public final long adSelectionId;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class UpdateAdCounterHistogramRequest {
+    ctor public UpdateAdCounterHistogramRequest(long adSelectionId, int adEventType, androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech);
+    method public int getAdEventType();
+    method public long getAdSelectionId();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getCallerAdTech();
+    property public final int adEventType;
+    property public final long adSelectionId;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.appsetid {
@@ -101,12 +180,26 @@
 
   public final class AdData {
     ctor public AdData(android.net.Uri renderUri, String metadata);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters, optional String? adRenderId);
+    method public java.util.Set<java.lang.Integer> getAdCounterKeys();
+    method public androidx.privacysandbox.ads.adservices.common.AdFilters? getAdFilters();
+    method public String? getAdRenderId();
     method public String getMetadata();
     method public android.net.Uri getRenderUri();
+    property public final java.util.Set<java.lang.Integer> adCounterKeys;
+    property public final androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters;
+    property public final String? adRenderId;
     property public final String metadata;
     property public final android.net.Uri renderUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class AdFilters {
+    ctor public AdFilters(androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters);
+    method public androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? getFrequencyCapFilters();
+    property public final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters;
+  }
+
   public final class AdSelectionSignals {
     ctor public AdSelectionSignals(String signals);
     method public String getSignals();
@@ -122,9 +215,49 @@
   public sealed interface ExperimentalFeatures {
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class FrequencyCapFilters {
+    ctor public FrequencyCapFilters();
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents);
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents();
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents;
+    field public static final int AD_EVENT_TYPE_CLICK = 3; // 0x3
+    field public static final int AD_EVENT_TYPE_IMPRESSION = 1; // 0x1
+    field public static final int AD_EVENT_TYPE_VIEW = 2; // 0x2
+    field public static final int AD_EVENT_TYPE_WIN = 0; // 0x0
+    field public static final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters.Companion Companion;
+  }
+
+  public static final class FrequencyCapFilters.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class KeyedFrequencyCap {
+    ctor public KeyedFrequencyCap(int adCounterKey, int maxCount, java.time.Duration interval);
+    method public int getAdCounterKey();
+    method public java.time.Duration getInterval();
+    method public int getMaxCount();
+    property public final int adCounterKey;
+    property public final java.time.Duration interval;
+    property public final int maxCount;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.customaudience {
@@ -166,6 +299,7 @@
   }
 
   public abstract class CustomAudienceManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? fetchAndJoinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? joinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? leaveCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
@@ -176,6 +310,20 @@
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class FetchAndJoinCustomAudienceRequest {
+    ctor public FetchAndJoinCustomAudienceRequest(android.net.Uri fetchUri, optional String? name, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals);
+    method public java.time.Instant? getActivationTime();
+    method public java.time.Instant? getExpirationTime();
+    method public android.net.Uri getFetchUri();
+    method public String? getName();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+    property public final java.time.Instant? activationTime;
+    property public final java.time.Instant? expirationTime;
+    property public final android.net.Uri fetchUri;
+    property public final String? name;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+  }
+
   public final class JoinCustomAudienceRequest {
     ctor public JoinCustomAudienceRequest(androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience);
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience getCustomAudience();
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_current.ignore b/privacysandbox/ads/ads-adservices/api/restricted_current.ignore
index 7d3e9cf..05e0309 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_current.ignore
+++ b/privacysandbox/ads/ads-adservices/api/restricted_current.ignore
@@ -5,3 +5,14 @@
 
 RemovedMethod: androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest#getShouldRecordObservation():
     Removed method androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest.getShouldRecordObservation()
+
+
+AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdCounterKeys():
+    Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdCounterKeys()
+AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdFilters():
+    Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdFilters()
+AddedMethod: androidx.privacysandbox.ads.adservices.common.AdData#getAdRenderId():
+    Added method androidx.privacysandbox.ads.adservices.common.AdData.getAdRenderId()
+
+AddedField: androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome#Companion:
+    Added field androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_current.txt b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
index ab2bd03..32149c9 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_current.txt
+++ b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
@@ -40,10 +40,28 @@
     property public final android.net.Uri trustedScoringSignalsUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class AdSelectionFromOutcomesConfig {
+    ctor public AdSelectionFromOutcomesConfig(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, java.util.List<java.lang.Long> adSelectionIds, androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals, android.net.Uri selectionLogicUri);
+    method public java.util.List<java.lang.Long> getAdSelectionIds();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals getAdSelectionSignals();
+    method public android.net.Uri getSelectionLogicUri();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
+    method public void setSelectionLogicUri(android.net.Uri);
+    property public final java.util.List<java.lang.Long> adSelectionIds;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals adSelectionSignals;
+    property public final android.net.Uri selectionLogicUri;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
+  }
+
   public abstract class AdSelectionManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? getAdSelectionData(androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest getAdSelectionDataRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome>);
     method public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager? obtain(android.content.Context context);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? persistAdSelectionResult(androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest persistAdSelectionResultRequest, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? reportEvent(androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest reportEventRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? reportImpression(androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest reportImpressionRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? selectAds(androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, kotlin.coroutines.Continuation<? super androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome>);
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public abstract suspend Object? updateAdCounterHistogram(androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion Companion;
   }
 
@@ -55,11 +73,62 @@
     ctor public AdSelectionOutcome(long adSelectionId, android.net.Uri renderUri);
     method public long getAdSelectionId();
     method public android.net.Uri getRenderUri();
+    method @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public boolean hasOutcome();
     property public final long adSelectionId;
     property public final android.net.Uri renderUri;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome.Companion Companion;
+    field @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome NO_OUTCOME;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public static final class AdSelectionOutcome.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataOutcome {
+    ctor public GetAdSelectionDataOutcome(long adSelectionId, optional byte[]? adSelectionData);
+    method public byte[]? getAdSelectionData();
+    method public long getAdSelectionId();
+    property public final byte[]? adSelectionData;
+    property public final long adSelectionId;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
+    ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller);
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
+    ctor public PersistAdSelectionResultRequest(long adSelectionId, optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional byte[]? adSelectionResult);
+    method public long getAdSelectionId();
+    method public byte[]? getAdSelectionResult();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+    property public final long adSelectionId;
+    property public final byte[]? adSelectionResult;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class ReportEventRequest {
+    ctor public ReportEventRequest(long adSelectionId, String eventKey, String eventData, int reportingDestinations, optional android.view.InputEvent? inputEvent);
+    method public long getAdSelectionId();
+    method public String getEventData();
+    method public String getEventKey();
+    method public android.view.InputEvent? getInputEvent();
+    method public int getReportingDestinations();
+    property public final long adSelectionId;
+    property public final String eventData;
+    property public final String eventKey;
+    property public final android.view.InputEvent? inputEvent;
+    property public final int reportingDestinations;
+    field public static final androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest.Companion Companion;
+    field public static final int FLAG_REPORTING_DESTINATION_BUYER = 2; // 0x2
+    field public static final int FLAG_REPORTING_DESTINATION_SELLER = 1; // 0x1
+  }
+
+  public static final class ReportEventRequest.Companion {
   }
 
   public final class ReportImpressionRequest {
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public ReportImpressionRequest(long adSelectionId);
     ctor public ReportImpressionRequest(long adSelectionId, androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig adSelectionConfig);
     method public androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig getAdSelectionConfig();
     method public long getAdSelectionId();
@@ -67,6 +136,16 @@
     property public final long adSelectionId;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class UpdateAdCounterHistogramRequest {
+    ctor public UpdateAdCounterHistogramRequest(long adSelectionId, int adEventType, androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech);
+    method public int getAdEventType();
+    method public long getAdSelectionId();
+    method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getCallerAdTech();
+    property public final int adEventType;
+    property public final long adSelectionId;
+    property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier callerAdTech;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.appsetid {
@@ -101,12 +180,26 @@
 
   public final class AdData {
     ctor public AdData(android.net.Uri renderUri, String metadata);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters);
+    ctor @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public AdData(android.net.Uri renderUri, String metadata, optional java.util.Set<java.lang.Integer> adCounterKeys, optional androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters, optional String? adRenderId);
+    method public java.util.Set<java.lang.Integer> getAdCounterKeys();
+    method public androidx.privacysandbox.ads.adservices.common.AdFilters? getAdFilters();
+    method public String? getAdRenderId();
     method public String getMetadata();
     method public android.net.Uri getRenderUri();
+    property public final java.util.Set<java.lang.Integer> adCounterKeys;
+    property public final androidx.privacysandbox.ads.adservices.common.AdFilters? adFilters;
+    property public final String? adRenderId;
     property public final String metadata;
     property public final android.net.Uri renderUri;
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class AdFilters {
+    ctor public AdFilters(androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters);
+    method public androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? getFrequencyCapFilters();
+    property public final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters? frequencyCapFilters;
+  }
+
   public final class AdSelectionSignals {
     ctor public AdSelectionSignals(String signals);
     method public String getSignals();
@@ -122,9 +215,49 @@
   public sealed interface ExperimentalFeatures {
   }
 
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext10 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext10OptIn {
+  }
+
+  @SuppressCompatibility @kotlin.RequiresOptIn(message="The Ext8 API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.Ext8OptIn {
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This API is experimental.", level=kotlin.RequiresOptIn.Level.WARNING) public static @interface ExperimentalFeatures.RegisterSourceOptIn {
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class FrequencyCapFilters {
+    ctor public FrequencyCapFilters();
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents);
+    ctor public FrequencyCapFilters(optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents, optional java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents);
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents();
+    method public java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents();
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForClickEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForViewEvents;
+    property public final java.util.List<androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap> keyedFrequencyCapsForWinEvents;
+    field public static final int AD_EVENT_TYPE_CLICK = 3; // 0x3
+    field public static final int AD_EVENT_TYPE_IMPRESSION = 1; // 0x1
+    field public static final int AD_EVENT_TYPE_VIEW = 2; // 0x2
+    field public static final int AD_EVENT_TYPE_WIN = 0; // 0x0
+    field public static final androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters.Companion Companion;
+  }
+
+  public static final class FrequencyCapFilters.Companion {
+  }
+
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext8OptIn public final class KeyedFrequencyCap {
+    ctor public KeyedFrequencyCap(int adCounterKey, int maxCount, java.time.Duration interval);
+    method public int getAdCounterKey();
+    method public java.time.Duration getInterval();
+    method public int getMaxCount();
+    property public final int adCounterKey;
+    property public final java.time.Duration interval;
+    property public final int maxCount;
+  }
+
 }
 
 package androidx.privacysandbox.ads.adservices.customaudience {
@@ -166,6 +299,7 @@
   }
 
   public abstract class CustomAudienceManager {
+    method @SuppressCompatibility @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public abstract suspend Object? fetchAndJoinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.FetchAndJoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? joinCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission(android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) public abstract suspend Object? leaveCustomAudience(androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public static final androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
@@ -176,6 +310,20 @@
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager? obtain(android.content.Context context);
   }
 
+  @SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class FetchAndJoinCustomAudienceRequest {
+    ctor public FetchAndJoinCustomAudienceRequest(android.net.Uri fetchUri, optional String? name, optional java.time.Instant? activationTime, optional java.time.Instant? expirationTime, optional androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals);
+    method public java.time.Instant? getActivationTime();
+    method public java.time.Instant? getExpirationTime();
+    method public android.net.Uri getFetchUri();
+    method public String? getName();
+    method public androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? getUserBiddingSignals();
+    property public final java.time.Instant? activationTime;
+    property public final java.time.Instant? expirationTime;
+    property public final android.net.Uri fetchUri;
+    property public final String? name;
+    property public final androidx.privacysandbox.ads.adservices.common.AdSelectionSignals? userBiddingSignals;
+  }
+
   public final class JoinCustomAudienceRequest {
     ctor public JoinCustomAudienceRequest(androidx.privacysandbox.ads.adservices.customaudience.CustomAudience customAudience);
     method public androidx.privacysandbox.ads.adservices.customaudience.CustomAudience getCustomAudience();
diff --git a/privacysandbox/ads/ads-adservices/build.gradle b/privacysandbox/ads/ads-adservices/build.gradle
index f13566ed..b6bf4fe 100644
--- a/privacysandbox/ads/ads-adservices/build.gradle
+++ b/privacysandbox/ads/ads-adservices/build.gradle
@@ -52,6 +52,7 @@
 }
 
 android {
+    compileSdkVersion = "android-34-ext10"
     namespace "androidx.privacysandbox.ads.adservices"
 }
 
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionFromOutcomesConfigTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionFromOutcomesConfigTest.kt
new file mode 100644
index 0000000..765abab
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionFromOutcomesConfigTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.net.Uri
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AdSelectionFromOutcomesConfigTest {
+    private val seller: AdTechIdentifier = AdTechIdentifier("1234")
+    private val adSelectionIds: List<Long> = listOf(10, 11, 12)
+    private val adSelectionSignals: AdSelectionSignals = AdSelectionSignals("adSelSignals")
+    private val selectionLogicUri: Uri = Uri.parse("www.abc.com")
+    @Test
+    fun testToString() {
+        val result = "AdSelectionFromOutcomesConfig: seller=$seller, " +
+            "adSelectionIds='$adSelectionIds', adSelectionSignals=$adSelectionSignals, " +
+            "selectionLogicUri=$selectionLogicUri"
+        val request = AdSelectionFromOutcomesConfig(
+            seller,
+            adSelectionIds,
+            adSelectionSignals,
+            selectionLogicUri)
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val adSelectionFromOutcomesConfig = AdSelectionFromOutcomesConfig(
+            seller,
+            adSelectionIds,
+            adSelectionSignals,
+            selectionLogicUri)
+        var adSelectionFromOutcomesConfig2 = AdSelectionFromOutcomesConfig(
+            AdTechIdentifier("1234"),
+            adSelectionIds,
+            adSelectionSignals,
+            Uri.parse("www.abc.com"))
+        Truth.assertThat(adSelectionFromOutcomesConfig == adSelectionFromOutcomesConfig2).isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt
index 8f4b4a24..fbfde14 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt
@@ -16,18 +16,22 @@
 
 package androidx.privacysandbox.ads.adservices.adselection
 
-import android.adservices.adselection.AdSelectionOutcome
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
+import android.view.InputEvent
+import android.view.KeyEvent
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion.obtain
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
@@ -48,6 +52,7 @@
 import org.mockito.invocation.InvocationOnMock
 import org.mockito.quality.Strictness
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
 @SmallTest
 @SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
@@ -67,7 +72,7 @@
             mSession = mockitoSession()
                 .mockStatic(android.adservices.adselection.AdSelectionManager::class.java)
                 .strictness(Strictness.LENIENT)
-                .startMocking();
+                .startMocking()
         }
     }
 
@@ -77,14 +82,159 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 30)
     fun testAdSelectionOlderVersions() {
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
+        Assume.assumeTrue("maxSdkVersion = API 33/34 ext 3", !mValidAdServicesSdkExtVersion)
         Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         assertThat(obtain(mContext)).isEqualTo(null)
     }
 
     @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testUpdateAdCounterHistogramOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 33/34 ext 7",
+            AdServicesInfo.adServicesVersion() < 8)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
+
+        val managerCompat = obtain(mContext)
+        val updateAdCounterHistogramRequest = UpdateAdCounterHistogramRequest(
+            adSelectionId,
+            adEventType,
+            seller
+        )
+
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                managerCompat!!.updateAdCounterHistogram(updateAdCounterHistogramRequest)
+            }
+        }.hasMessageThat().contains("API is unsupported. Min version is API 33 ext 8 or " +
+            "API 31/32 ext 9")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testReportEventOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 33/34 ext 7",
+            AdServicesInfo.adServicesVersion() < 8)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
+
+        val managerCompat = obtain(mContext)
+        val reportEventRequest = ReportEventRequest(
+            adSelectionId,
+            eventKey,
+            eventData,
+            reportingDestinations
+        )
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                managerCompat!!.reportEvent(reportEventRequest)
+            }
+        }.hasMessageThat().contains("API is unsupported. Min version is API 33 ext 8 or " +
+            "API 31/32 ext 9")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testGetAdSelectionDataOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 31-34 ext 9",
+            AdServicesInfo.adServicesVersion() < 10 && AdServicesInfo.extServicesVersion() < 10)
+
+        val managerCompat = obtain(mContext)
+        val getAdSelectionDataRequest = GetAdSelectionDataRequest(seller)
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                managerCompat!!.getAdSelectionData(getAdSelectionDataRequest)
+            }
+        }.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testPersistAdSelectionResultOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 31-34 ext 9",
+            AdServicesInfo.adServicesVersion() < 10 && AdServicesInfo.extServicesVersion() < 10)
+
+        val managerCompat = obtain(mContext)
+        val persistAdSelectionResultRequest = PersistAdSelectionResultRequest(
+            adSelectionId,
+            seller,
+            adSelectionData
+        )
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                managerCompat!!.persistAdSelectionResult(persistAdSelectionResultRequest)
+            }
+        }.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testReportImpressionOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 31-34 ext 9",
+            AdServicesInfo.adServicesVersion() < 10 && AdServicesInfo.extServicesVersion() < 10)
+
+        val managerCompat = obtain(mContext)
+        val reportImpressionRequest = ReportImpressionRequest(adSelectionId)
+
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                managerCompat!!.reportImpression(reportImpressionRequest)
+            }
+        }.hasMessageThat().contains("adSelectionConfig is mandatory for" +
+            "API versions lower than ext 10")
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testSelectAdsFromOutcomesOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 31-34 ext 9",
+            AdServicesInfo.adServicesVersion() < 10 && AdServicesInfo.extServicesVersion() < 10)
+
+        val managerCompat = obtain(mContext)
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                managerCompat!!.selectAds(adSelectionFromOutcomesConfig)
+            }
+        }.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
     fun testSelectAds() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
@@ -111,6 +261,32 @@
     }
 
     @Test
+    fun testSelectAdsFromOutcomes() {
+        Assume.assumeTrue("minSdkVersion = API 31 ext 10",
+            AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10)
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupAdSelectionFromOutcomesResponse(adSelectionManager)
+        val managerCompat = obtain(mContext)
+
+        // Actually invoke the compat code.
+        val result = runBlocking {
+            managerCompat!!.selectAds(adSelectionFromOutcomesConfig)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.AdSelectionFromOutcomesConfig::class.java)
+        verify(adSelectionManager).selectAds(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyRequest(captor.value)
+
+        // Verify that the result of the compat call is correct.
+        verifyResponse(result)
+    }
+
+    @Test
     fun testReportImpression() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
@@ -135,6 +311,98 @@
         verifyReportImpressionRequest(captor.value)
     }
 
+    @Test
+    fun testUpdateAdCounterHistogram() {
+        Assume.assumeTrue("minSdkVersion = API 33 ext 8 or API 31/32 ext 9",
+            AdServicesInfo.adServicesVersion() >= 8 || mValidAdExtServicesSdkExtVersion)
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupUpdateAdCounterHistogramResponse(adSelectionManager)
+
+        val managerCompat = obtain(mContext)
+        val updateAdCounterHistogramRequest = UpdateAdCounterHistogramRequest(
+            adSelectionId,
+            adEventType,
+            seller
+        )
+
+        // Actually invoke the compat code.
+        runBlocking {
+            managerCompat!!.updateAdCounterHistogram(updateAdCounterHistogramRequest)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.UpdateAdCounterHistogramRequest::class.java)
+        verify(adSelectionManager).updateAdCounterHistogram(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyUpdateAdCounterHistogramRequest(captor.value)
+    }
+
+    @Test
+    fun testReportEvent() {
+        Assume.assumeTrue("minSdkVersion = API 33 ext 8 or API 31/32 ext 9",
+            AdServicesInfo.adServicesVersion() >= 8 || mValidAdExtServicesSdkExtVersion)
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupReportEventResponse(adSelectionManager)
+
+        val managerCompat = obtain(mContext)
+        val reportEventRequest = ReportEventRequest(
+            adSelectionId,
+            eventKey,
+            eventData,
+            reportingDestinations,
+            inputEvent
+        )
+
+        // Actually invoke the compat code.
+        runBlocking {
+            managerCompat!!.reportEvent(reportEventRequest)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.ReportEventRequest::class.java)
+        verify(adSelectionManager).reportEvent(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyReportEventRequest(captor.value)
+    }
+
+    @Test
+    fun testPersistAdSelectionResult() {
+        Assume.assumeTrue("minSdkVersion = API 31 ext 10",
+            AdServicesInfo.adServicesVersion() >= 10 ||
+                AdServicesInfo.extServicesVersion() >= 10)
+
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupGetAdSelectionResponse(adSelectionManager)
+
+        val managerCompat = obtain(mContext)
+        val persistAdSelectionResultRequest = PersistAdSelectionResultRequest(
+            adSelectionId,
+            seller,
+            adSelectionData
+        )
+
+        // Actually invoke the compat code.
+        val result = runBlocking {
+            managerCompat!!.persistAdSelectionResult(persistAdSelectionResultRequest)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.adselection.PersistAdSelectionResultRequest::class.java)
+        verify(adSelectionManager).persistAdSelectionResult(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyPersistAdSelectionResultRequest(captor.value)
+
+        verifyResponse(result)
+    }
+
     @SdkSuppress(minSdkVersion = 30)
     companion object {
         private lateinit var mContext: Context
@@ -159,6 +427,21 @@
             sellerSignals,
             perBuyerSignals,
             trustedScoringSignalsUri)
+        private const val adEventType = FrequencyCapFilters.AD_EVENT_TYPE_IMPRESSION
+        private const val eventKey = "click"
+        private const val eventData = "{\"key\":\"value\"}"
+        private const val reportingDestinations =
+            ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER
+        private val adSelectionData = byteArrayOf(0x01, 0x02, 0x03, 0x04)
+        private val inputEvent: InputEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1)
+        private val adSelectionIds: List<Long> = listOf(10, 11, 12)
+        private val selectionLogicUri: Uri = Uri.parse("www.abc.com")
+        private val adSelectionFromOutcomesConfig = AdSelectionFromOutcomesConfig(
+            seller,
+            adSelectionIds,
+            adSelectionSignals,
+            selectionLogicUri
+        )
 
         // Response.
         private val renderUri = Uri.parse("render-uri.com")
@@ -186,18 +469,19 @@
         ) {
             // Set up the response that AdSelectionManager will return when the compat code calls
             // it.
-            val response = AdSelectionOutcome.Builder()
+            val response = android.adservices.adselection.AdSelectionOutcome.Builder()
                 .setAdSelectionId(adSelectionId)
                 .setRenderUri(renderUri)
                 .build()
             val answer = { args: InvocationOnMock ->
-                val receiver = args.getArgument<OutcomeReceiver<AdSelectionOutcome, Exception>>(2)
+                val receiver = args.getArgument<OutcomeReceiver<
+                    android.adservices.adselection.AdSelectionOutcome, Exception>>(2)
                 receiver.onResult(response)
                 null
             }
             doAnswer(answer)
                 .`when`(adSelectionManager).selectAds(
-                    any(),
+                    any<android.adservices.adselection.AdSelectionConfig>(),
                     any(),
                     any()
                 )
@@ -210,6 +494,84 @@
             doAnswer(answer2).`when`(adSelectionManager).reportImpression(any(), any(), any())
         }
 
+        private fun setupAdSelectionFromOutcomesResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // Set up the response that AdSelectionManager will return when the compat code calls
+            // it.
+            val response = android.adservices.adselection.AdSelectionOutcome.Builder()
+                .setAdSelectionId(adSelectionId)
+                .setRenderUri(renderUri)
+                .build()
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<
+                    android.adservices.adselection.AdSelectionOutcome, Exception>>(2)
+                receiver.onResult(response)
+                null
+            }
+            doAnswer(answer)
+                .`when`(adSelectionManager).selectAds(
+                    any<android.adservices.adselection.AdSelectionFromOutcomesConfig>(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupUpdateAdCounterHistogramResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // Set up the response that AdSelectionManager will return when the compat code calls
+            // UpdateAdCounterHistogramResponse().
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
+                receiver.onResult(Object())
+                null
+            }
+            doAnswer(answer)
+                .`when`(adSelectionManager).updateAdCounterHistogram(
+                    any(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupReportEventResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // Set up the response that AdSelectionManager will return when the compat code calls
+            // ReportEvent().
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
+                receiver.onResult(Object())
+                null
+            }
+            doAnswer(answer)
+                .`when`(adSelectionManager).reportEvent(
+                    any(),
+                    any(),
+                    any()
+                )
+        }
+
+        private fun setupGetAdSelectionResponse(
+            adSelectionManager: android.adservices.adselection.AdSelectionManager
+        ) {
+            // There is no way to create a GetAdSelectionDataOutcome instance outside of adservices
+
+            val response2 = android.adservices.adselection.AdSelectionOutcome.Builder()
+                .setAdSelectionId(adSelectionId)
+                .setRenderUri(renderUri)
+                .build()
+            val answer2 = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<
+                    android.adservices.adselection.AdSelectionOutcome, Exception>>(2)
+                receiver.onResult(response2)
+                null
+            }
+            doAnswer(answer2)
+                .`when`(adSelectionManager).persistAdSelectionResult(any(), any(), any())
+        }
+
         private fun verifyRequest(request: android.adservices.adselection.AdSelectionConfig) {
             // Set up the request that we expect the compat code to invoke.
             val expectedRequest = getPlatformAdSelectionConfig()
@@ -217,11 +579,20 @@
             Assert.assertEquals(expectedRequest, request)
         }
 
+        private fun verifyRequest(
+            request: android.adservices.adselection.AdSelectionFromOutcomesConfig
+        ) {
+            // Set up the request that we expect the compat code to invoke.
+            val expectedRequest = getPlatformAdSelectionFromOutcomesConfig()
+
+            Assert.assertEquals(expectedRequest, request)
+        }
+
         private fun verifyResponse(
-            outcome: androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome
+            outcome: AdSelectionOutcome
         ) {
             val expectedOutcome =
-                androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome(
+                AdSelectionOutcome(
                     adSelectionId,
                     renderUri)
             Assert.assertEquals(expectedOutcome, outcome)
@@ -245,6 +616,19 @@
                 .build()
         }
 
+        private fun getPlatformAdSelectionFromOutcomesConfig():
+            android.adservices.adselection.AdSelectionFromOutcomesConfig {
+            val adTechIdentifier = android.adservices.common.AdTechIdentifier.fromString(adId)
+            return android.adservices.adselection.AdSelectionFromOutcomesConfig.Builder()
+                .setSelectionSignals(
+                    android.adservices.common.AdSelectionSignals.fromString(adSelectionSignalsStr)
+                )
+                .setAdSelectionIds(adSelectionIds)
+                .setSelectionLogicUri(selectionLogicUri)
+                .setSeller(adTechIdentifier)
+                .build()
+        }
+
         private fun verifyReportImpressionRequest(
             request: android.adservices.adselection.ReportImpressionRequest
         ) {
@@ -254,5 +638,56 @@
             Assert.assertEquals(expectedRequest.adSelectionId, request.adSelectionId)
             Assert.assertEquals(expectedRequest.adSelectionConfig, request.adSelectionConfig)
         }
+
+        private fun verifyUpdateAdCounterHistogramRequest(
+            request: android.adservices.adselection.UpdateAdCounterHistogramRequest
+        ) {
+            val adTechIdentifier = android.adservices.common.AdTechIdentifier.fromString(adId)
+            val expectedRequest = android.adservices.adselection.UpdateAdCounterHistogramRequest
+                .Builder(adSelectionId, adEventType, adTechIdentifier)
+                .build()
+            Assert.assertEquals(expectedRequest, request)
+        }
+
+        private fun verifyReportEventRequest(
+            request: android.adservices.adselection.ReportEventRequest
+        ) {
+            val checkInputEvent = AdServicesInfo.adServicesVersion() >= 10 ||
+                AdServicesInfo.extServicesVersion() >= 10
+            val expectedRequestBuilder = android.adservices.adselection.ReportEventRequest.Builder(
+                adSelectionId,
+                eventKey,
+                eventData,
+                reportingDestinations)
+
+            if (checkInputEvent)
+                expectedRequestBuilder.setInputEvent(inputEvent)
+
+            val expectedRequest = expectedRequestBuilder.build()
+            Assert.assertEquals(expectedRequest.adSelectionId, request.adSelectionId)
+            Assert.assertEquals(expectedRequest.key, request.key)
+            Assert.assertEquals(expectedRequest.data, request.data)
+            Assert.assertEquals(expectedRequest.reportingDestinations,
+                request.reportingDestinations)
+            if (checkInputEvent)
+                Assert.assertEquals(expectedRequest.inputEvent,
+                    request.inputEvent)
+        }
+
+        private fun verifyPersistAdSelectionResultRequest(
+            request: android.adservices.adselection.PersistAdSelectionResultRequest
+        ) {
+            val adTechIdentifier = android.adservices.common.AdTechIdentifier.fromString(adId)
+            val expectedRequest = android.adservices.adselection.PersistAdSelectionResultRequest
+                .Builder()
+                .setAdSelectionId(adSelectionId)
+                .setSeller(adTechIdentifier)
+                .setAdSelectionResult(adSelectionData)
+                .build()
+            Assert.assertEquals(expectedRequest.adSelectionId, request.adSelectionId)
+            Assert.assertEquals(expectedRequest.seller, request.seller)
+            Assert.assertTrue(expectedRequest.adSelectionResult
+                .contentEquals(request.adSelectionResult))
+        }
     }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcomeTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcomeTest.kt
index 3d1bf4d..d18556e 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcomeTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcomeTest.kt
@@ -17,12 +17,14 @@
 package androidx.privacysandbox.ads.adservices.adselection
 
 import android.net.Uri
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AdSelectionOutcomeTest {
@@ -41,4 +43,13 @@
         var adSelectionOutcome2 = AdSelectionOutcome(adSelectionId, Uri.parse("abc.com"))
         Truth.assertThat(adSelectionOutcome == adSelectionOutcome2).isTrue()
     }
+
+    @Test
+    fun testHasOutcome() {
+        val adSelectionOutcome = AdSelectionOutcome(adSelectionId, renderUri)
+        Truth.assertThat(adSelectionOutcome.hasOutcome()).isTrue()
+
+        val emptyAdSelectionOutcome = AdSelectionOutcome(0, Uri.EMPTY)
+        Truth.assertThat(emptyAdSelectionOutcome.hasOutcome()).isFalse()
+    }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataOutcomeTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataOutcomeTest.kt
new file mode 100644
index 0000000..af65dd1
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataOutcomeTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GetAdSelectionDataOutcomeTest {
+    private val adSelectionId = 1234L
+    private val adSelectionData = byteArrayOf(0x01, 0x02, 0x03, 0x04)
+    @Test
+    fun testToString() {
+        val result = "GetAdSelectionDataOutcome: adSelectionId=$adSelectionId, " +
+            "adSelectionData=$adSelectionData"
+        val request = GetAdSelectionDataOutcome(adSelectionId, adSelectionData)
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val getAdSelectionDataOutcome = GetAdSelectionDataOutcome(adSelectionId, adSelectionData)
+        var getAdSelectionDataOutcome2 = GetAdSelectionDataOutcome(
+            adSelectionId,
+            byteArrayOf(0x01, 0x02, 0x03, 0x04)
+        )
+        Truth.assertThat(getAdSelectionDataOutcome == getAdSelectionDataOutcome2).isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequestTest.kt
new file mode 100644
index 0000000..1ef1194
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequestTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GetAdSelectionDataRequestTest {
+    private val seller: AdTechIdentifier = AdTechIdentifier("1234")
+
+    @Test
+    fun testToString() {
+        val result = "GetAdSelectionDataRequest: seller=$seller"
+        val request = GetAdSelectionDataRequest(seller)
+
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val reportEventRequest = GetAdSelectionDataRequest(seller)
+        var reportEventRequest2 = GetAdSelectionDataRequest(AdTechIdentifier("1234"))
+
+        Truth.assertThat(reportEventRequest == reportEventRequest2).isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/PersistAdSelectionResultRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/PersistAdSelectionResultRequestTest.kt
new file mode 100644
index 0000000..3810b70
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/PersistAdSelectionResultRequestTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PersistAdSelectionResultRequestTest {
+    private val adSelectionId = 1234L
+    private val seller: AdTechIdentifier = AdTechIdentifier("1234")
+    private val adSelectionResult = byteArrayOf(0x01, 0x02, 0x03, 0x04)
+    @Test
+    fun testToString() {
+        val result = "PersistAdSelectionResultRequest: adSelectionId=$adSelectionId, " +
+            "seller=$seller, adSelectionResult=$adSelectionResult"
+        val request = PersistAdSelectionResultRequest(adSelectionId, seller, adSelectionResult)
+
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val persistAdSelectionResultRequest = PersistAdSelectionResultRequest(
+            adSelectionId,
+            seller,
+            adSelectionResult
+        )
+        var persistAdSelectionResultRequest2 = PersistAdSelectionResultRequest(
+            1234L,
+            AdTechIdentifier("1234"),
+            byteArrayOf(0x01, 0x02, 0x03, 0x04)
+        )
+
+        Truth.assertThat(persistAdSelectionResultRequest == persistAdSelectionResultRequest2)
+            .isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequestTest.kt
new file mode 100644
index 0000000..5bed67e
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequestTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.view.InputEvent
+import android.view.KeyEvent
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ReportEventRequestTest {
+    private val adSelectionId: Long = 1234L
+    private val eventKey: String = "click"
+    private val eventData: String = "{\"key\":\"value\"}"
+    private val reportingDestinations: Int = ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER
+    private val inputEvent: InputEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1)
+
+    @Test
+    fun testToString() {
+        val result = "ReportEventRequest: adSelectionId=$adSelectionId, eventKey=$eventKey, " +
+            "eventData=$eventData, reportingDestinations=$reportingDestinations" +
+            "inputEvent=$inputEvent"
+        val request = ReportEventRequest(
+            adSelectionId,
+            eventKey,
+            eventData,
+            reportingDestinations,
+            inputEvent
+        )
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val reportEventRequest = ReportEventRequest(
+            adSelectionId,
+            eventKey,
+            eventData,
+            reportingDestinations,
+            inputEvent
+        )
+        var reportEventRequest2 = ReportEventRequest(
+            1234L,
+            "click",
+            "{\"key\":\"value\"}",
+            ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER,
+            inputEvent
+        )
+
+        Truth.assertThat(reportEventRequest == reportEventRequest2).isTrue()
+    }
+
+    @Test
+    fun testInvalidReportingDestinations() {
+        assertThrows<IllegalArgumentException> {
+            ReportEventRequest(
+                adSelectionId,
+                eventKey,
+                eventData,
+                0 /* unset reporting destinations */
+            )
+        }.hasMessageThat().contains("Invalid reporting destinations bitfield.")
+
+        assertThrows<IllegalArgumentException> {
+            ReportEventRequest(
+                adSelectionId,
+                eventKey,
+                eventData,
+                4 /* undefined reporting destinations */
+            )
+        }.hasMessageThat().contains("Invalid reporting destinations bitfield.")
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequestTest.kt
new file mode 100644
index 0000000..270d286
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequestTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UpdateAdCounterHistogramRequestTest {
+    private val adSelectionId: Long = 1234L
+    private val adEventType: Int = FrequencyCapFilters.AD_EVENT_TYPE_CLICK
+    private val callerAdTech: AdTechIdentifier = AdTechIdentifier("1234")
+
+    @Test
+    fun testToString() {
+        val result = "UpdateAdCounterHistogramRequest: adSelectionId=$adSelectionId, " +
+            "adEventType=AD_EVENT_TYPE_CLICK, callerAdTech=$callerAdTech"
+        val request = UpdateAdCounterHistogramRequest(adSelectionId, adEventType, callerAdTech)
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val updateAdCounterHistogramRequest1 = UpdateAdCounterHistogramRequest(
+            adSelectionId,
+            adEventType,
+            callerAdTech
+        )
+        var updateAdCounterHistogramRequest2 = UpdateAdCounterHistogramRequest(
+            1234L,
+            FrequencyCapFilters.AD_EVENT_TYPE_CLICK,
+            AdTechIdentifier("1234")
+        )
+        Truth.assertThat(updateAdCounterHistogramRequest1 == updateAdCounterHistogramRequest2)
+            .isTrue()
+
+        var updateAdCounterHistogramRequest3 = UpdateAdCounterHistogramRequest(
+            1234L,
+            FrequencyCapFilters.AD_EVENT_TYPE_VIEW,
+            AdTechIdentifier("1234")
+        )
+        Truth.assertThat(updateAdCounterHistogramRequest1 == updateAdCounterHistogramRequest3)
+            .isFalse()
+    }
+
+    @Test
+    fun testInvalidAdEventType() {
+        assertThrows<IllegalArgumentException> {
+            UpdateAdCounterHistogramRequest(
+                1234L,
+                -1 /* Invalid adEventType */,
+                AdTechIdentifier("1234")
+            )
+        }.hasMessageThat().contains(
+            "Ad event type must be one of AD_EVENT_TYPE_IMPRESSION, " +
+                "AD_EVENT_TYPE_VIEW, or AD_EVENT_TYPE_CLICK"
+        )
+    }
+
+    @Test
+    fun testExceptionWinAdEventType() {
+        assertThrows<IllegalArgumentException> {
+            UpdateAdCounterHistogramRequest(
+                1234L,
+                FrequencyCapFilters.AD_EVENT_TYPE_WIN,
+                AdTechIdentifier("1234")
+            )
+        }.hasMessageThat().contains("Win event types cannot be manually updated.")
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdDataTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdDataTest.kt
index c140524..ab5293b 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdDataTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdDataTest.kt
@@ -18,28 +18,51 @@
 
 import android.net.Uri
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
+import java.time.Duration
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
 @SmallTest
+@SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
 class AdDataTest {
     private val uri: Uri = Uri.parse("abc.com")
     private val metadata = "metadata"
+    private val adCounterKeys: Set<Int> = setOf<Int>(1, 2, 3)
+    private val adFilters: AdFilters = AdFilters(FrequencyCapFilters(
+        listOf(KeyedFrequencyCap(1, 3, Duration.ofSeconds(1))),
+        listOf(KeyedFrequencyCap(2, 4, Duration.ofSeconds(2))),
+        listOf(KeyedFrequencyCap(3, 3, Duration.ofSeconds(3))),
+        listOf(KeyedFrequencyCap(4, 4, Duration.ofSeconds(4)),
+            KeyedFrequencyCap(5, 3, Duration.ofSeconds(5)),
+            KeyedFrequencyCap(6, 4, Duration.ofSeconds(6)))))
+    private val adRenderId: String = "ad-render-id"
 
     @Test
     fun testToString() {
-        val result = "AdData: renderUri=$uri, metadata='$metadata'"
-        val request = AdData(uri, metadata)
+        val result = "AdData: renderUri=$uri, metadata='$metadata', " +
+            "adCounterKeys=$adCounterKeys, adFilters=$adFilters, adRenderId=$adRenderId"
+        val request = AdData(uri, metadata, adCounterKeys, adFilters, adRenderId)
         Truth.assertThat(request.toString()).isEqualTo(result)
     }
 
     @Test
     fun testEquals() {
-        val adData1 = AdData(uri, metadata)
-        var adData2 = AdData(Uri.parse("abc.com"), "metadata")
+        val adData1 = AdData(uri, metadata, adCounterKeys, adFilters, adRenderId)
+        var adData2 = AdData(Uri.parse("abc.com"), "metadata", setOf<Int>(1, 2, 3),
+            AdFilters(FrequencyCapFilters(
+                listOf(KeyedFrequencyCap(1, 3, Duration.ofSeconds(1))),
+                listOf(KeyedFrequencyCap(2, 4, Duration.ofSeconds(2))),
+                listOf(KeyedFrequencyCap(3, 3, Duration.ofSeconds(3))),
+                listOf(KeyedFrequencyCap(4, 4, Duration.ofSeconds(4)),
+                    KeyedFrequencyCap(5, 3, Duration.ofSeconds(5)),
+                    KeyedFrequencyCap(6, 4, Duration.ofSeconds(6))))),
+            "ad-render-id")
         Truth.assertThat(adData1 == adData2).isTrue()
     }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdFiltersTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdFiltersTest.kt
new file mode 100644
index 0000000..7e97960
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/AdFiltersTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import java.time.Duration
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
+@SmallTest
+@SuppressWarnings("NewApi")
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class AdFiltersTest {
+    private val frequencyCapFilters: FrequencyCapFilters = FrequencyCapFilters(
+        listOf(KeyedFrequencyCap(1, 3, Duration.ofSeconds(1))),
+        listOf(KeyedFrequencyCap(2, 4, Duration.ofSeconds(2))),
+        listOf(KeyedFrequencyCap(3, 3, Duration.ofSeconds(3))),
+        listOf(KeyedFrequencyCap(4, 4, Duration.ofSeconds(4)),
+            KeyedFrequencyCap(5, 3, Duration.ofSeconds(5)),
+            KeyedFrequencyCap(6, 4, Duration.ofSeconds(6))))
+
+    @Test
+    fun testToString() {
+        val result = "AdFilters: frequencyCapFilters=$frequencyCapFilters"
+        val request = AdFilters(frequencyCapFilters)
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val adFilters1 = AdFilters(frequencyCapFilters)
+        var adFilters2 = AdFilters(FrequencyCapFilters(
+            listOf(KeyedFrequencyCap(1, 3, Duration.ofSeconds(1))),
+            listOf(KeyedFrequencyCap(2, 4, Duration.ofSeconds(2))),
+            listOf(KeyedFrequencyCap(3, 3, Duration.ofSeconds(3))),
+            listOf(KeyedFrequencyCap(4, 4, Duration.ofSeconds(4)),
+                KeyedFrequencyCap(5, 3, Duration.ofSeconds(5)),
+                KeyedFrequencyCap(6, 4, Duration.ofSeconds(6)))))
+        Truth.assertThat(adFilters1 == adFilters2).isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFiltersTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFiltersTest.kt
new file mode 100644
index 0000000..94d55af
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFiltersTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import java.time.Duration
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class FrequencyCapFiltersTest {
+    private val keyedFrequencyCapsForWinEvents: List<KeyedFrequencyCap> =
+        listOf(KeyedFrequencyCap(1, 3, Duration.ofSeconds(1)))
+    private val keyedFrequencyCapsForImpressionEvents: List<KeyedFrequencyCap> =
+        listOf(KeyedFrequencyCap(2, 4, Duration.ofSeconds(2)))
+    private val keyedFrequencyCapsForViewEvents: List<KeyedFrequencyCap> =
+        listOf(KeyedFrequencyCap(3, 3, Duration.ofSeconds(3)))
+    private val keyedFrequencyCapsForClickEvents: List<KeyedFrequencyCap> =
+        listOf(KeyedFrequencyCap(4, 4, Duration.ofSeconds(4)),
+            KeyedFrequencyCap(5, 3, Duration.ofSeconds(5)),
+            KeyedFrequencyCap(6, 4, Duration.ofSeconds(6)))
+
+    @Test
+    fun testToString() {
+        val result = "FrequencyCapFilters: " +
+            "keyedFrequencyCapsForWinEvents=$keyedFrequencyCapsForWinEvents, " +
+            "keyedFrequencyCapsForImpressionEvents=$keyedFrequencyCapsForImpressionEvents, " +
+            "keyedFrequencyCapsForViewEvents=$keyedFrequencyCapsForViewEvents, " +
+            "keyedFrequencyCapsForClickEvents=$keyedFrequencyCapsForClickEvents"
+        val request = FrequencyCapFilters(keyedFrequencyCapsForWinEvents,
+            keyedFrequencyCapsForImpressionEvents,
+            keyedFrequencyCapsForViewEvents,
+            keyedFrequencyCapsForClickEvents)
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val frequencyCapFilters1 = FrequencyCapFilters(keyedFrequencyCapsForWinEvents,
+            keyedFrequencyCapsForImpressionEvents,
+            keyedFrequencyCapsForViewEvents,
+            keyedFrequencyCapsForClickEvents)
+        var frequencyCapFilters2 = FrequencyCapFilters(
+            listOf(KeyedFrequencyCap(1, 3, Duration.ofSeconds(1))),
+            listOf(KeyedFrequencyCap(2, 4, Duration.ofSeconds(2))),
+            listOf(KeyedFrequencyCap(3, 3, Duration.ofSeconds(3))),
+            listOf(KeyedFrequencyCap(4, 4, Duration.ofSeconds(4)),
+                KeyedFrequencyCap(5, 3, Duration.ofSeconds(5)),
+                KeyedFrequencyCap(6, 4, Duration.ofSeconds(6))))
+        Truth.assertThat(frequencyCapFilters1 == frequencyCapFilters2).isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCapTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCapTest.kt
new file mode 100644
index 0000000..79fedda
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCapTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import java.time.Duration
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class KeyedFrequencyCapTest {
+    private val adCounterKey: Int = 1
+    private val maxCount: Int = 3
+    private val interval: Duration = Duration.ofSeconds(1)
+
+    @Test
+    fun testToString() {
+        val result = "KeyedFrequencyCap: adCounterKey=$adCounterKey, maxCount=$maxCount, " +
+            "interval=$interval"
+        val request = KeyedFrequencyCap(adCounterKey, maxCount, interval)
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val keyedFrequencyCap1 = KeyedFrequencyCap(adCounterKey, maxCount, interval)
+        var keyedFrequencyCap2 = KeyedFrequencyCap(1, 3, Duration.ofSeconds(1))
+        Truth.assertThat(keyedFrequencyCap1 == keyedFrequencyCap2).isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt
index 53d4f16..ff9bb2c 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt
@@ -23,12 +23,14 @@
 import androidx.privacysandbox.ads.adservices.common.AdData
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager.Companion.obtain
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.testutils.assertThrows
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
@@ -49,10 +51,11 @@
 import org.mockito.invocation.InvocationOnMock
 import org.mockito.quality.Strictness
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
 @SmallTest
 @SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 30)
+@SdkSuppress(minSdkVersion = 31)
 class CustomAudienceManagerTest {
 
     private var mSession: StaticMockitoSession? = null
@@ -79,7 +82,7 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
+    @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 31)
     fun testOlderVersions() {
         Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
         Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
@@ -87,6 +90,34 @@
     }
 
     @Test
+    @SdkSuppress(maxSdkVersion = 34, minSdkVersion = 31)
+    fun testFetchAndJoinCustomAudienceOlderVersions() {
+        /* AdServices or ExtServices are present */
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+                          mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
+
+        /* API is not available */
+        Assume.assumeTrue("maxSdkVersion = API 31-34 ext 9",
+            AdServicesInfo.adServicesVersion() < 10 && AdServicesInfo.extServicesVersion() < 10)
+        mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
+        val managerCompat = obtain(mContext)
+
+        // Verify that it throws an exception
+        assertThrows(UnsupportedOperationException::class.java) {
+            runBlocking {
+                val request = FetchAndJoinCustomAudienceRequest(
+                    uri,
+                    name,
+                    activationTime,
+                    expirationTime,
+                    userBiddingSignals
+                )
+                managerCompat!!.fetchAndJoinCustomAudience(request)
+            }
+        }.hasMessageThat().contains("API is not available. Min version is API 31 ext 10")
+    }
+
+    @Test
     fun testJoinCustomAudience() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
@@ -119,6 +150,39 @@
     }
 
     @Test
+    fun testFetchAndJoinCustomAudience() {
+        Assume.assumeTrue("minSdkVersion = API 31 ext 10",
+            AdServicesInfo.adServicesVersion() >= 10 ||
+                AdServicesInfo.extServicesVersion() >= 10)
+
+        val customAudienceManager =
+            mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
+        setupFetchAndJoinResponse(customAudienceManager)
+        val managerCompat = obtain(mContext)
+
+        // Actually invoke the compat code.
+        runBlocking {
+            val request = FetchAndJoinCustomAudienceRequest(
+                uri,
+                name,
+                activationTime,
+                expirationTime,
+                userBiddingSignals
+            )
+            managerCompat!!.fetchAndJoinCustomAudience(request)
+        }
+
+        // Verify that the compat code was invoked correctly.
+        val captor = ArgumentCaptor.forClass(
+            android.adservices.customaudience.FetchAndJoinCustomAudienceRequest::class.java
+        )
+        verify(customAudienceManager).fetchAndJoinCustomAudience(captor.capture(), any(), any())
+
+        // Verify that the request that the compat code makes to the platform is correct.
+        verifyFetchAndJoinCustomAudienceRequest(captor.value)
+    }
+
+    @Test
     fun testLeaveCustomAudience() {
         Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
             mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
@@ -144,7 +208,7 @@
         verifyLeaveCustomAudienceRequest(captor.value)
     }
 
-    @SdkSuppress(minSdkVersion = 30)
+    @SdkSuppress(minSdkVersion = 31)
     companion object {
         private lateinit var mContext: Context
         private val uri: Uri = Uri.parse("abc.com")
@@ -157,6 +221,8 @@
         private val trustedBiddingSignals: TrustedBiddingData = TrustedBiddingData(uri, keys)
         private const val metadata = "metadata"
         private val ads: List<AdData> = listOf(AdData(uri, metadata))
+        private val activationTime: Instant = Instant.ofEpochSecond(5)
+        private val expirationTime: Instant = Instant.ofEpochSecond(10)
 
         private fun mockCustomAudienceManager(
             spyContext: Context,
@@ -182,6 +248,16 @@
             doAnswer(answer).`when`(customAudienceManager).leaveCustomAudience(any(), any(), any())
         }
 
+        private fun setupFetchAndJoinResponse(customAudienceManager: CustomAudienceManager) {
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
+                receiver.onResult(Object())
+                null
+            }
+            doAnswer(answer).`when`(customAudienceManager)
+                .fetchAndJoinCustomAudience(any(), any(), any())
+        }
+
         private fun verifyJoinCustomAudienceRequest(
             joinCustomAudienceRequest: android.adservices.customaudience.JoinCustomAudienceRequest
         ) {
@@ -240,6 +316,24 @@
                 signals).isTrue()
         }
 
+        private fun verifyFetchAndJoinCustomAudienceRequest(
+            fetchAndJoinCustomAudienceRequest:
+                android.adservices.customaudience.FetchAndJoinCustomAudienceRequest
+        ) {
+            // Set up the request that we expect the compat code to invoke.
+            val expectedRequest = android.adservices.customaudience
+                .FetchAndJoinCustomAudienceRequest
+                .Builder(uri)
+                .setName(name)
+                .setActivationTime(activationTime)
+                .setExpirationTime(expirationTime)
+                .setUserBiddingSignals(userBiddingSignals.convertToAdServices())
+                .build()
+
+            // Verify that the actual request matches the expected one.
+            Truth.assertThat(expectedRequest == fetchAndJoinCustomAudienceRequest).isTrue()
+        }
+
         private fun verifyLeaveCustomAudienceRequest(
             leaveCustomAudienceRequest: android.adservices.customaudience.LeaveCustomAudienceRequest
         ) {
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceTest.kt
index f941dacd..f6da70b 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceTest.kt
@@ -18,19 +18,25 @@
 
 import android.net.Uri
 import androidx.privacysandbox.ads.adservices.common.AdData
+import androidx.privacysandbox.ads.adservices.common.AdFilters
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters
+import androidx.privacysandbox.ads.adservices.common.KeyedFrequencyCap
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
+import java.time.Duration
 import java.time.Instant
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 26)
+@SdkSuppress(minSdkVersion = 31)
 class CustomAudienceTest {
     private val uri: Uri = Uri.parse("abc.com")
     private val buyer: AdTechIdentifier = AdTechIdentifier("1234")
@@ -40,7 +46,17 @@
     private val userBiddingSignals: AdSelectionSignals = AdSelectionSignals("signals")
     private val keys: List<String> = listOf("key1", "key2")
     private val trustedBiddingSignals: TrustedBiddingData = TrustedBiddingData(uri, keys)
-    private val ads: List<AdData> = listOf(AdData(uri, "metadata"))
+    private val adCounterKeys: Set<Int> = setOf<Int>(1, 2, 3)
+    private val interval = Duration.ofSeconds(1)
+    private val adFilters: AdFilters = AdFilters(
+        FrequencyCapFilters(
+            keyedFrequencyCapsForViewEvents =
+            listOf(KeyedFrequencyCap(1, 3, interval)),
+        )
+    )
+    private val ads: List<AdData> = listOf(
+        AdData(uri, "metadata", adCounterKeys, adFilters)
+    )
 
     @Test
     fun testToStringAndEquals() {
@@ -49,7 +65,13 @@
             "userBiddingSignals=AdSelectionSignals: signals, " +
             "trustedBiddingSignals=TrustedBiddingData: trustedBiddingUri=abc.com " +
             "trustedBiddingKeys=[key1, key2], biddingLogicUri=abc.com, " +
-            "ads=[AdData: renderUri=abc.com, metadata='metadata']"
+            "ads=[AdData: renderUri=abc.com, metadata='metadata', adCounterKeys=[1, 2, 3], " +
+            "adFilters=AdFilters: frequencyCapFilters=FrequencyCapFilters: " +
+            "keyedFrequencyCapsForWinEvents=[], " +
+            "keyedFrequencyCapsForImpressionEvents=[], " +
+            "keyedFrequencyCapsForViewEvents=" +
+            "[KeyedFrequencyCap: adCounterKey=1, maxCount=3, interval=$interval], " +
+            "keyedFrequencyCapsForClickEvents=[], adRenderId=null]"
 
         val customAudience = CustomAudience(
             buyer,
@@ -60,7 +82,8 @@
             activationTime,
             expirationTime,
             userBiddingSignals,
-            trustedBiddingSignals)
+            trustedBiddingSignals
+        )
         Truth.assertThat(customAudience.toString()).isEqualTo(result)
 
         // Verify Builder.
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/FetchAndJoinCustomAudienceRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/FetchAndJoinCustomAudienceRequestTest.kt
new file mode 100644
index 0000000..4c024d6
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/FetchAndJoinCustomAudienceRequestTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.customaudience
+
+import android.net.Uri
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import java.time.Instant
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 31)
+class FetchAndJoinCustomAudienceRequestTest {
+    private val fetchUri: Uri = Uri.parse("abc.com")
+    private val name: String = "abc"
+    private val activationTime: Instant = Instant.ofEpochSecond(5)
+    private val expirationTime: Instant = Instant.ofEpochSecond(10)
+    private val userBiddingSignals: AdSelectionSignals = AdSelectionSignals("signals")
+
+    @Test
+    fun testToString() {
+        val request = FetchAndJoinCustomAudienceRequest(
+            fetchUri,
+            name,
+            activationTime,
+            expirationTime,
+            userBiddingSignals)
+        val result = "FetchAndJoinCustomAudienceRequest: fetchUri=$fetchUri, " +
+            "name=$name, activationTime=$activationTime, " +
+            "expirationTime=$expirationTime, userBiddingSignals=$userBiddingSignals"
+        Truth.assertThat(request.toString()).isEqualTo(result)
+    }
+
+    @Test
+    fun testEquals() {
+        val fetchAndJoinCustomAudienceRequest1 = FetchAndJoinCustomAudienceRequest(
+            fetchUri,
+            name,
+            activationTime,
+            expirationTime,
+            userBiddingSignals
+        )
+        var fetchAndJoinCustomAudienceRequest2 = FetchAndJoinCustomAudienceRequest(
+            Uri.parse("abc.com"),
+            "abc",
+            Instant.ofEpochSecond(5),
+            Instant.ofEpochSecond(10),
+            AdSelectionSignals("signals")
+        )
+        Truth.assertThat(fetchAndJoinCustomAudienceRequest1 == fetchAndJoinCustomAudienceRequest2)
+            .isTrue()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/JoinCustomAudienceRequestTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/JoinCustomAudienceRequestTest.kt
index 3ce38c7..4563164 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/JoinCustomAudienceRequestTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/JoinCustomAudienceRequestTest.kt
@@ -20,6 +20,7 @@
 import androidx.privacysandbox.ads.adservices.common.AdData
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -28,9 +29,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 26)
+@SdkSuppress(minSdkVersion = 31)
 class JoinCustomAudienceRequestTest {
     private val uri: Uri = Uri.parse("abc.com")
     private val buyer: AdTechIdentifier = AdTechIdentifier("1234")
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionConfig.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionConfig.kt
index c3630f1..65e5d31 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionConfig.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionConfig.kt
@@ -16,7 +16,12 @@
 
 package androidx.privacysandbox.ads.adservices.adselection
 
+import android.annotation.SuppressLint
 import android.net.Uri
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
 
@@ -42,6 +47,7 @@
  * @param trustedScoringSignalsUri URI endpoint of sell-side trusted signal from which creative
  *     specific realtime information can be fetched from.
  */
+@SuppressLint("ClassVerificationFailure")
 class AdSelectionConfig public constructor(
     val seller: AdTechIdentifier,
     val decisionLogicUri: Uri,
@@ -84,4 +90,60 @@
             "sellerSignals=$sellerSignals, perBuyerSignals=$perBuyerSignals, " +
             "trustedScoringSignalsUri=$trustedScoringSignalsUri"
     }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.adselection.AdSelectionConfig {
+        return android.adservices.adselection.AdSelectionConfig.Builder()
+            .setAdSelectionSignals(adSelectionSignals.convertToAdServices())
+            .setCustomAudienceBuyers(customAudienceBuyers.convertToAdServices())
+            .setDecisionLogicUri(decisionLogicUri)
+            .setSeller(seller.convertToAdServices())
+            .setPerBuyerSignals(perBuyerSignals.convertToAdServices())
+            .setSellerSignals(sellerSignals.convertToAdServices())
+            .setTrustedScoringSignalsUri(trustedScoringSignalsUri)
+            .build()
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private fun List<AdTechIdentifier>.convertToAdServices():
+        MutableList<android.adservices.common.AdTechIdentifier> {
+            val ids = mutableListOf<android.adservices.common.AdTechIdentifier>()
+            for (buyer in this) {
+                ids.add(buyer.convertToAdServices())
+            }
+            return ids
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private fun Map<AdTechIdentifier, AdSelectionSignals>.convertToAdServices():
+        MutableMap<android.adservices.common.AdTechIdentifier,
+                android.adservices.common.AdSelectionSignals?> {
+            val map = HashMap<android.adservices.common.AdTechIdentifier,
+                android.adservices.common.AdSelectionSignals?>()
+            for (key in this.keys) {
+                val id = key.convertToAdServices()
+                val value = this[key]?.convertToAdServices()
+                map[id] = value
+            }
+            return map
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    internal companion object {
+        val EMPTY = AdSelectionConfig(
+            AdTechIdentifier(""),
+            Uri.EMPTY,
+            emptyList(),
+            AdSelectionSignals(""),
+            AdSelectionSignals(""),
+            emptyMap(),
+            Uri.EMPTY
+        )
+    }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionFromOutcomesConfig.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionFromOutcomesConfig.kt
new file mode 100644
index 0000000..af9cda0
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionFromOutcomesConfig.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.net.Uri
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+/**
+ * Contains the configuration of the ad selection process that select a winner from a given list of
+ * ad selection ids.
+ *
+ * Instances of this class are created by SDKs to be provided as arguments to the
+ * [AdSelectionManager#selectAds] methods in [AdSelectionManager].
+ *
+ * @param seller AdTechIdentifier of the seller, for example "www.example-ssp.com".
+ * @param adSelectionIds a list of ad selection ids passed by the SSP to participate in the ad
+ *     selection from outcomes process.
+ * @param adSelectionSignals signals given to the participating buyers in the ad selection and
+ *     reporting processes.
+ * @param selectionLogicUri the URI used to retrieve the JS code containing the seller/SSP
+ *      function used during the ad selection.
+ */
+@ExperimentalFeatures.Ext10OptIn
+class AdSelectionFromOutcomesConfig public constructor(
+    val seller: AdTechIdentifier,
+    val adSelectionIds: List<Long>,
+    val adSelectionSignals: AdSelectionSignals,
+    var selectionLogicUri: Uri,
+) {
+
+    /** Checks whether two [AdSelectionFromOutcomesConfig] objects contain the same information.  */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is AdSelectionFromOutcomesConfig) return false
+        return this.seller == other.seller &&
+            this.adSelectionIds == other.adSelectionIds &&
+            this.adSelectionSignals == other.adSelectionSignals &&
+            this.selectionLogicUri == other.selectionLogicUri
+    }
+
+    /** Returns the hash of the [AdSelectionFromOutcomesConfig] object's data.  */
+    override fun hashCode(): Int {
+        var hash = seller.hashCode()
+        hash = 31 * hash + adSelectionIds.hashCode()
+        hash = 31 * hash + adSelectionSignals.hashCode()
+        hash = 31 * hash + selectionLogicUri.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method.  */
+    override fun toString(): String {
+        return "AdSelectionFromOutcomesConfig: seller=$seller, adSelectionIds='$adSelectionIds', " +
+            "adSelectionSignals=$adSelectionSignals, selectionLogicUri=$selectionLogicUri"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    internal fun convertToAdServices():
+        android.adservices.adselection.AdSelectionFromOutcomesConfig {
+        return android.adservices.adselection.AdSelectionFromOutcomesConfig.Builder()
+            .setSelectionSignals(adSelectionSignals.convertToAdServices())
+            .setAdSelectionIds(adSelectionIds)
+            .setSelectionLogicUri(selectionLogicUri)
+            .setSeller(seller.convertToAdServices())
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt
index 311bc63..d33a503 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt
@@ -22,6 +22,7 @@
 import android.os.LimitExceededException
 import android.os.TransactionTooLargeException
 import androidx.annotation.RequiresPermission
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import java.util.concurrent.TimeoutException
 
@@ -59,15 +60,222 @@
     abstract suspend fun selectAds(adSelectionConfig: AdSelectionConfig): AdSelectionOutcome
 
     /**
+     * Selects an ad from the results of previously ran ad selections.
+     *
+     * @param adSelectionFromOutcomesConfig is provided by the Ads SDK and the
+     * [AdSelectionFromOutcomesConfig] object is transferred via a Binder call. For this reason, the
+     * total size of these objects is bound to the Android IPC limitations. Failures to transfer the
+     * [AdSelectionFromOutcomesConfig] will throw an [TransactionTooLargeException].
+     *
+     * The output is passed by the receiver, which either returns an [AdSelectionOutcome]
+     * for a successful run, or an [Exception] includes the type of the exception thrown and
+     * the corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to run the ad selection.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [TimeoutException] is thrown, it is caused when a timeout is encountered
+     * during bidding, scoring, or overall selection process to find winning Ad.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract suspend fun selectAds(
+        adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig
+    ): AdSelectionOutcome
+
+    /**
      * Report the given impression. The [ReportImpressionRequest] is provided by the Ads SDK.
      * The receiver either returns a {@code void} for a successful run, or an [Exception]
-     * indicates the error.
+     * indicating the error.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to report the impression.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support [ReportImpressionRequest] with null
+     * {@code AdSelectionConfig}
      *
      * @param reportImpressionRequest the request for reporting impression.
      */
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
     abstract suspend fun reportImpression(reportImpressionRequest: ReportImpressionRequest)
 
+    /**
+     * Notifies the service that there is a new ad event to report for the ad selected by the
+     * ad-selection run identified by {@code adSelectionId}. An ad event is any occurrence that
+     * happens to an ad associated with the given {@code adSelectionId}. There is no guarantee about
+     * when the ad event will be reported. The event reporting could be delayed and reports could be
+     * batched.
+     *
+     * Using [ReportEventRequest#getKey()], the service will fetch the {@code reportingUri}
+     * that was registered in {@code registerAdBeacon}. See documentation of [reportImpression] for
+     * more details regarding {@code registerAdBeacon}. Then, the service will attach
+     * [ReportEventRequest#getData()] to the request body of a POST request and send the request.
+     * The body of the POST request will have the {@code content-type} of {@code text/plain}, and
+     * the data will be transmitted in {@code charset=UTF-8}.
+     *
+     * The output is passed by the receiver, which either returns an empty [Object] for a
+     * successful run, or an [Exception] includes the type of the exception thrown and the
+     * corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to report the ad event.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * Events will be reported at most once as a best-effort attempt.
+     *
+     * @param reportEventRequest the request for reporting event.
+     */
+    @ExperimentalFeatures.Ext8OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract suspend fun reportEvent(reportEventRequest: ReportEventRequest)
+
+    /**
+     * Updates the counter histograms for an ad which was previously selected by a call to
+     * [selectAds].
+     *
+     * The counter histograms are used in ad selection to inform frequency cap filtering on
+     * candidate ads, where ads whose frequency caps are met or exceeded are removed from the
+     * bidding process during ad selection.
+     *
+     * Counter histograms can only be updated for ads specified by the given {@code
+     * adSelectionId} returned by a recent call to Protected Audience API ad selection from the same
+     * caller app.
+     *
+     * A [SecurityException] is returned if:
+     *
+     * <ol>
+     *   <li>the app has not declared the correct permissions in its manifest, or
+     *   <li>the app or entity identified by the {@code callerAdTechIdentifier} are not authorized
+     *       to use the API.
+     * </ol>
+     *
+     * An [IllegalStateException] is returned if the call does not come from an app with a
+     * foreground activity.
+     *
+     * A [LimitExceededException] is returned if the call exceeds the calling app's API throttle.
+     *
+     * An [UnsupportedOperationException] is returned if the Android API level and AdServices module
+     * versions don't support this API.
+     *
+     * In all other failure cases, it will return an empty [Object]. Note that to protect user
+     * privacy, internal errors will not be sent back via an exception.
+     *
+     * @param updateAdCounterHistogramRequest the request for updating the ad counter histogram.
+     */
+    @ExperimentalFeatures.Ext8OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract suspend fun updateAdCounterHistogram(
+        updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest
+    )
+
+    /**
+     * Collects custom audience data from device. Returns a compressed and encrypted blob to send to
+     * auction servers for ad selection.
+     *
+     * Custom audience ads must have a {@code ad_render_id} to be eligible for to be collected.
+     *
+     * See [AdSelectionManager#persistAdSelectionResult] for how to process the results of
+     * the ad selection run on server-side with the blob generated by this API.
+     *
+     * The output is passed by the receiver, which either returns an [GetAdSelectionDataOutcome]
+     * for a successful run, or an [Exception] includes the type of
+     * the exception thrown and the corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to run the ad selection.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [TimeoutException] is thrown, it is caused when a timeout is encountered
+     * during bidding, scoring, or overall selection process to find winning Ad.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * @param getAdSelectionDataRequest the request for get ad selection data.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract suspend fun getAdSelectionData(
+        getAdSelectionDataRequest: GetAdSelectionDataRequest
+    ): GetAdSelectionDataOutcome
+
+    /**
+     * Persists the ad selection results from the server-side.
+     *
+     * See [AdSelectionManager#getAdSelectionData] for how to generate an encrypted blob to
+     * run an ad selection on the server side.
+     *
+     * The output is passed by the receiver, which either returns an [AdSelectionOutcome]
+     * for a successful run, or an [Exception] includes the type of the exception thrown and
+     * the corresponding error message.
+     *
+     * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument
+     * the API received to run the ad selection.
+     *
+     * If the [IllegalStateException] is thrown with error message "Failure of AdSelection
+     * services.", it is caused by an internal failure of the ad selection service.
+     *
+     * If the [TimeoutException] is thrown, it is caused when a timeout is encountered
+     * during bidding, scoring, or overall selection process to find winning Ad.
+     *
+     * If the [LimitExceededException] is thrown, it is caused when the calling package
+     * exceeds the allowed rate limits and is throttled.
+     *
+     * If the [SecurityException] is thrown, it is caused when the caller is not authorized
+     * or permission is not requested.
+     *
+     * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * @param persistAdSelectionResultRequest the request for persist ad selection result.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract suspend fun persistAdSelectionResult(
+        persistAdSelectionResultRequest: PersistAdSelectionResultRequest
+    ): AdSelectionOutcome
+
     companion object {
         /**
          *  Creates [AdSelectionManager].
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.kt
index e780975..7b9ad2d 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.kt
@@ -25,10 +25,11 @@
 import androidx.annotation.RequiresPermission
 import androidx.annotation.RestrictTo
 import androidx.core.os.asOutcomeReceiver
-import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
-import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import kotlinx.coroutines.suspendCancellableCoroutine
 
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @SuppressLint("NewApi", "ClassVerificationFailure")
 @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@@ -40,7 +41,11 @@
     @DoNotInline
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
     override suspend fun selectAds(adSelectionConfig: AdSelectionConfig): AdSelectionOutcome {
-        return convertResponse(selectAdsInternal(convertAdSelectionConfig(adSelectionConfig)))
+        return AdSelectionOutcome(
+            selectAdsInternal(
+                adSelectionConfig.convertToAdServices()
+            )
+        )
     }
 
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
@@ -55,56 +60,17 @@
         )
     }
 
-    private fun convertAdSelectionConfig(
-        request: AdSelectionConfig
-    ): android.adservices.adselection.AdSelectionConfig {
-        return android.adservices.adselection.AdSelectionConfig.Builder()
-            .setAdSelectionSignals(convertAdSelectionSignals(request.adSelectionSignals))
-            .setCustomAudienceBuyers(convertBuyers(request.customAudienceBuyers))
-            .setDecisionLogicUri(request.decisionLogicUri)
-            .setSeller(android.adservices.common.AdTechIdentifier.fromString(
-                request.seller.identifier))
-            .setPerBuyerSignals(convertPerBuyerSignals(request.perBuyerSignals))
-            .setSellerSignals(convertAdSelectionSignals(request.sellerSignals))
-            .setTrustedScoringSignalsUri(request.trustedScoringSignalsUri)
-            .build()
-    }
-
-    private fun convertAdSelectionSignals(
-        request: AdSelectionSignals
-    ): android.adservices.common.AdSelectionSignals {
-        return android.adservices.common.AdSelectionSignals.fromString(request.signals)
-    }
-
-    private fun convertBuyers(
-        buyers: List<AdTechIdentifier>
-    ): MutableList<android.adservices.common.AdTechIdentifier> {
-        val ids = mutableListOf<android.adservices.common.AdTechIdentifier>()
-        for (buyer in buyers) {
-            ids.add(android.adservices.common.AdTechIdentifier.fromString(buyer.identifier))
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun selectAds(adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig):
+        AdSelectionOutcome {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.selectAds(
+                mAdSelectionManager,
+                adSelectionFromOutcomesConfig
+            )
         }
-        return ids
-    }
-
-    private fun convertPerBuyerSignals(
-        request: Map<AdTechIdentifier, AdSelectionSignals>
-    ): Map<android.adservices.common.AdTechIdentifier,
-        android.adservices.common.AdSelectionSignals?> {
-        val map = HashMap<android.adservices.common.AdTechIdentifier,
-            android.adservices.common.AdSelectionSignals?>()
-        for (key in request.keys) {
-            val id = android.adservices.common.AdTechIdentifier.fromString(key.identifier)
-            val value = if (request[key] != null) convertAdSelectionSignals(request[key]!!)
-            else null
-            map[id] = value
-        }
-        return map
-    }
-
-    private fun convertResponse(
-        response: android.adservices.adselection.AdSelectionOutcome
-    ): AdSelectionOutcome {
-        return AdSelectionOutcome(response.adSelectionId, response.renderUri)
+        throw UnsupportedOperationException("API is not available. Min version is API 31 ext 10")
     }
 
     @DoNotInline
@@ -112,19 +78,154 @@
     override suspend fun reportImpression(reportImpressionRequest: ReportImpressionRequest) {
         suspendCancellableCoroutine<Any> { continuation ->
             mAdSelectionManager.reportImpression(
-                convertReportImpressionRequest(reportImpressionRequest),
+                reportImpressionRequest.convertToAdServices(),
                 Runnable::run,
                 continuation.asOutcomeReceiver()
             )
         }
     }
 
-    private fun convertReportImpressionRequest(
-        request: ReportImpressionRequest
-    ): android.adservices.adselection.ReportImpressionRequest {
-        return android.adservices.adselection.ReportImpressionRequest(
-            request.adSelectionId,
-            convertAdSelectionConfig(request.adSelectionConfig)
-        )
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun reportEvent(reportEventRequest: ReportEventRequest) {
+        if (AdServicesInfo.adServicesVersion() >= 8 || AdServicesInfo.extServicesVersion() >= 9) {
+            return Ext8Impl.reportEvent(
+                mAdSelectionManager,
+                reportEventRequest
+            )
+        }
+        throw UnsupportedOperationException("API is unsupported. Min version is API 33 ext 8 or " +
+            "API 31/32 ext 9")
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun updateAdCounterHistogram(
+        updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest
+    ) {
+        if (AdServicesInfo.adServicesVersion() >= 8 || AdServicesInfo.extServicesVersion() >= 9) {
+            return Ext8Impl.updateAdCounterHistogram(
+                mAdSelectionManager,
+                updateAdCounterHistogramRequest
+            )
+        }
+        throw UnsupportedOperationException("API is unsupported. Min version is API 33 ext 8 or " +
+            "API 31/32 ext 9")
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun getAdSelectionData(
+        getAdSelectionDataRequest: GetAdSelectionDataRequest
+    ): GetAdSelectionDataOutcome {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.getAdSelectionData(
+                mAdSelectionManager,
+                getAdSelectionDataRequest
+            )
+        }
+        throw UnsupportedOperationException("API is not available. Min version is API 31 ext 10")
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun persistAdSelectionResult(
+        persistAdSelectionResultRequest: PersistAdSelectionResultRequest
+    ): AdSelectionOutcome {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.persistAdSelectionResult(
+                mAdSelectionManager,
+                persistAdSelectionResultRequest
+            )
+        }
+        throw UnsupportedOperationException("API is not available. Min version is API 31 ext 10")
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    private class Ext10Impl private constructor() {
+        companion object {
+            @DoNotInline
+            @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+            suspend fun getAdSelectionData(
+                adSelectionManager: android.adservices.adselection.AdSelectionManager,
+                getAdSelectionDataRequest: GetAdSelectionDataRequest
+            ): GetAdSelectionDataOutcome {
+                return GetAdSelectionDataOutcome(suspendCancellableCoroutine<
+                        android.adservices.adselection.GetAdSelectionDataOutcome> { continuation ->
+                    adSelectionManager.getAdSelectionData(
+                        getAdSelectionDataRequest.convertToAdServices(),
+                        Runnable::run,
+                        continuation.asOutcomeReceiver()
+                    )
+                })
+            }
+
+            @DoNotInline
+            @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+            suspend fun persistAdSelectionResult(
+                adSelectionManager: android.adservices.adselection.AdSelectionManager,
+                persistAdSelectionResultRequest: PersistAdSelectionResultRequest
+            ): AdSelectionOutcome {
+                return AdSelectionOutcome(suspendCancellableCoroutine { continuation ->
+                    adSelectionManager.persistAdSelectionResult(
+                        persistAdSelectionResultRequest.convertToAdServices(),
+                        Runnable::run,
+                        continuation.asOutcomeReceiver()
+                    )
+                })
+            }
+
+            @DoNotInline
+            @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+            suspend fun selectAds(
+                adSelectionManager: android.adservices.adselection.AdSelectionManager,
+                adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig
+            ): AdSelectionOutcome {
+                return AdSelectionOutcome(suspendCancellableCoroutine { continuation ->
+                    adSelectionManager.selectAds(
+                        adSelectionFromOutcomesConfig.convertToAdServices(),
+                        Runnable::run,
+                        continuation.asOutcomeReceiver()
+                    )
+                })
+            }
+        }
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private class Ext8Impl private constructor() {
+        companion object {
+            @DoNotInline
+            @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+            suspend fun updateAdCounterHistogram(
+                adSelectionManager: android.adservices.adselection.AdSelectionManager,
+                updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest
+            ) {
+                suspendCancellableCoroutine<Any> { cont ->
+                    adSelectionManager.updateAdCounterHistogram(
+                        updateAdCounterHistogramRequest.convertToAdServices(),
+                        Runnable::run,
+                        cont.asOutcomeReceiver()
+                    )
+                }
+            }
+
+            @DoNotInline
+            @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+            suspend fun reportEvent(
+                adSelectionManager: android.adservices.adselection.AdSelectionManager,
+                reportEventRequest: ReportEventRequest
+            ) {
+                suspendCancellableCoroutine<Any> { continuation ->
+                    adSelectionManager.reportEvent(
+                        reportEventRequest.convertToAdServices(),
+                        Runnable::run,
+                        continuation.asOutcomeReceiver()
+                    )
+                }
+            }
+        }
     }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcome.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcome.kt
index 9286e12..f166e7b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcome.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionOutcome.kt
@@ -16,17 +16,24 @@
 
 package androidx.privacysandbox.ads.adservices.adselection
 
+import android.annotation.SuppressLint
 import android.net.Uri
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 
 /**
- * This class represents  input to the [AdSelectionManager#selectAds] in the
- * [AdSelectionManager]. This field is populated in the case of a successful
+ * This class represents the output of the [AdSelectionManager#selectAds] in the
+ * [AdSelectionManager]. The fields are populated in the case of a successful
  * [AdSelectionManager#selectAds] call.
  *
  * @param adSelectionId An ID unique only to a device user that identifies a successful ad
  *     selection.
  * @param renderUri A render URL for the winning ad.
  */
+@SuppressLint("ClassVerificationFailure")
 class AdSelectionOutcome public constructor(
     val adSelectionId: Long,
     val renderUri: Uri
@@ -51,4 +58,23 @@
     override fun toString(): String {
         return "AdSelectionOutcome: adSelectionId=$adSelectionId, renderUri=$renderUri"
     }
+
+    @ExperimentalFeatures.Ext10OptIn
+    fun hasOutcome(): Boolean {
+        return this != NO_OUTCOME
+    }
+
+    @ExperimentalFeatures.Ext10OptIn
+    companion object {
+        /** Represents an AdSelectionOutcome with empty results. */
+        @ExperimentalFeatures.Ext10OptIn
+        @JvmField public val NO_OUTCOME = AdSelectionOutcome(0, Uri.EMPTY)
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal constructor(
+        response: android.adservices.adselection.AdSelectionOutcome
+    ) : this(response.adSelectionId, response.renderUri)
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataOutcome.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataOutcome.kt
new file mode 100644
index 0000000..9d96dab
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataOutcome.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+/**
+ * This class represents the output of the [AdSelectionManager#getAdSelectionData] in the
+ * [AdSelectionManager]. The fields are populated in the case of a successful
+ * [AdSelectionManager#getAdSelectionData] call.
+ *
+ * @param adSelectionId An ID unique only to a device user that identifies a successful ad
+ *     selection.
+ * @param adSelectionData The adSelectionData that is collected from device.
+ */
+@ExperimentalFeatures.Ext10OptIn
+class GetAdSelectionDataOutcome public constructor(
+    val adSelectionId: Long,
+    val adSelectionData: ByteArray? = null
+) {
+
+    /** Checks whether two [GetAdSelectionDataOutcome] objects contain the same information.  */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is GetAdSelectionDataOutcome) return false
+        return this.adSelectionId == other.adSelectionId &&
+            this.adSelectionData.contentEquals(other.adSelectionData)
+    }
+
+    /** Returns the hash of the [GetAdSelectionDataOutcome] object's data.  */
+    override fun hashCode(): Int {
+        var hash = adSelectionId.hashCode()
+        hash = 31 * hash + adSelectionData.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method.  */
+    override fun toString(): String {
+        return "GetAdSelectionDataOutcome: adSelectionId=$adSelectionId, " +
+            "adSelectionData=$adSelectionData"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    internal constructor(
+        response: android.adservices.adselection.GetAdSelectionDataOutcome
+    ) : this(response.adSelectionId, response.adSelectionData)
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt
new file mode 100644
index 0000000..2bfba69
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+/**
+ * Represent input parameters to the [AdSelectionManager#getAdSelectionData] API.
+ *
+ * @param seller AdTechIdentifier of the seller, for example "www.example-ssp.com".
+ */
+@ExperimentalFeatures.Ext10OptIn
+class GetAdSelectionDataRequest public constructor(
+    val seller: AdTechIdentifier? = null,
+) {
+    /** Checks whether two [GetAdSelectionDataRequest] objects contain the same information.  */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is GetAdSelectionDataRequest) return false
+        return this.seller == other.seller
+    }
+
+    /** Returns the hash of the [GetAdSelectionDataRequest] object's data.  */
+    override fun hashCode(): Int {
+        return seller.hashCode()
+    }
+
+    /** Overrides the toString method.  */
+    override fun toString(): String {
+        return "GetAdSelectionDataRequest: seller=$seller"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    internal fun convertToAdServices(): android.adservices.adselection.GetAdSelectionDataRequest {
+        return android.adservices.adselection.GetAdSelectionDataRequest.Builder()
+            .setSeller(seller?.convertToAdServices())
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/PersistAdSelectionResultRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/PersistAdSelectionResultRequest.kt
new file mode 100644
index 0000000..b868550
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/PersistAdSelectionResultRequest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+
+/**
+ * Represent input parameters to the [AdSelectionManager#persistAdSelectionResult] API.
+ *
+ * @param adSelectionId An ID unique only to a device user that identifies a successful ad
+ * selection.
+ * @param seller AdTechIdentifier of the seller, for example "www.example-ssp.com".
+ * @param adSelectionResult The adSelectionResult that is collected from device.
+ */
+@ExperimentalFeatures.Ext10OptIn
+class PersistAdSelectionResultRequest public constructor(
+    val adSelectionId: Long,
+    val seller: AdTechIdentifier? = null,
+    val adSelectionResult: ByteArray? = null,
+) {
+    /** Checks whether two [PersistAdSelectionResultRequest] contain the same information. */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is PersistAdSelectionResultRequest) return false
+        return this.adSelectionId == other.adSelectionId &&
+            this.seller == other.seller &&
+            this.adSelectionResult.contentEquals(other.adSelectionResult)
+    }
+
+    /** Returns the hash of the [PersistAdSelectionResultRequest] object's data. */
+    override fun hashCode(): Int {
+        var hash = adSelectionId.hashCode()
+        hash = 31 * hash + seller.hashCode()
+        hash = 31 * hash + adSelectionResult.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method. */
+    override fun toString(): String {
+        return "PersistAdSelectionResultRequest: adSelectionId=$adSelectionId, " +
+            "seller=$seller, adSelectionResult=$adSelectionResult"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    internal fun convertToAdServices():
+        android.adservices.adselection.PersistAdSelectionResultRequest {
+        return android.adservices.adselection.PersistAdSelectionResultRequest.Builder()
+            .setAdSelectionId(adSelectionId)
+            .setSeller(seller?.convertToAdServices())
+            .setAdSelectionResult(adSelectionResult)
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt
new file mode 100644
index 0000000..344be83
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportEventRequest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import android.util.Log
+import android.view.InputEvent
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
+
+/**
+ * Represent input parameters to the reportImpression API.
+ *
+ * @param adSelectionId An ID unique only to a device user that identifies a successful ad
+ * selection.
+ * @param eventKey An event key, the type of ad event to be reported.
+ * @param eventData The ad event data
+ * @param reportingDestinations The bitfield of reporting destinations to report to (buyer, seller,
+ * or both).
+ * @param inputEvent The input event associated with the user interaction.
+ */
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
+@ExperimentalFeatures.Ext8OptIn
+class ReportEventRequest public constructor(
+    val adSelectionId: Long,
+    val eventKey: String,
+    val eventData: String,
+    val reportingDestinations: Int,
+    @property:ExperimentalFeatures.Ext10OptIn val inputEvent: InputEvent? = null
+) {
+    init {
+        require(0 < reportingDestinations &&
+            reportingDestinations
+                <= (FLAG_REPORTING_DESTINATION_SELLER or FLAG_REPORTING_DESTINATION_BUYER)) {
+            "Invalid reporting destinations bitfield."
+        }
+    }
+
+    /** Checks whether two [ReportImpressionRequest] objects contain the same information.  */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ReportEventRequest) return false
+        return this.adSelectionId == other.adSelectionId &&
+            this.eventKey == other.eventKey &&
+            this.eventData == other.eventData &&
+            this.reportingDestinations == other.reportingDestinations &&
+            this.inputEvent == other.inputEvent
+    }
+
+    /** Returns the hash of the [ReportImpressionRequest] object's data.  */
+    override fun hashCode(): Int {
+        var hash = adSelectionId.hashCode()
+        hash = 31 * hash + eventKey.hashCode()
+        hash = 31 * hash + eventData.hashCode()
+        hash = 31 * hash + reportingDestinations.hashCode()
+        hash = 31 * hash + inputEvent.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method.  */
+    override fun toString(): String {
+        return "ReportEventRequest: adSelectionId=$adSelectionId, eventKey=$eventKey, " +
+            "eventData=$eventData, reportingDestinations=$reportingDestinations" +
+            "inputEvent=$inputEvent"
+    }
+
+    companion object {
+        const val FLAG_REPORTING_DESTINATION_SELLER: Int =
+            android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_SELLER
+        const val FLAG_REPORTING_DESTINATION_BUYER: Int =
+            android.adservices.adselection.ReportEventRequest.FLAG_REPORTING_DESTINATION_BUYER
+    }
+
+    @SuppressLint("NewApi")
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.adselection.ReportEventRequest {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.convertReportEventRequest(this)
+        }
+        return Ext8Impl.convertReportEventRequest(this)
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    private class Ext10Impl private constructor() {
+        companion object {
+            fun convertReportEventRequest(
+                request: ReportEventRequest
+            ): android.adservices.adselection.ReportEventRequest {
+                return android.adservices.adselection.ReportEventRequest.Builder(
+                    request.adSelectionId,
+                    request.eventKey,
+                    request.eventData,
+                    request.reportingDestinations)
+                    .setInputEvent(request.inputEvent)
+                    .build()
+            }
+        }
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private class Ext8Impl private constructor() {
+        companion object {
+            fun convertReportEventRequest(
+                request: ReportEventRequest
+            ): android.adservices.adselection.ReportEventRequest {
+                request.inputEvent?.let { Log.w("ReportEventRequest",
+                    "inputEvent is ignored. Min version to use inputEvent is API 31 ext 10") }
+                return android.adservices.adselection.ReportEventRequest.Builder(
+                    request.adSelectionId,
+                    request.eventKey,
+                    request.eventData,
+                    request.reportingDestinations)
+                    .build()
+            }
+        }
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportImpressionRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportImpressionRequest.kt
index 836899d..f8c9e92 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportImpressionRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/ReportImpressionRequest.kt
@@ -16,18 +16,33 @@
 
 package androidx.privacysandbox.ads.adservices.adselection
 
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
+
 /**
  * Represent input parameters to the reportImpression API.
  *
  * @param adSelectionId An ID unique only to a device user that identifies a successful ad
  *     selection.
- * @param adSelectionConfig The same configuration used in the selectAds() call identified by the
- *      provided ad selection ID.
+ * @param adSelectionConfig optional config used in the selectAds() call identified by the provided
+ *     ad selection ID. If the {@code adSelectionId} is for a on-device auction run using
+ *     [AdSelectionManager#selectAds], then the config must be included. If the
+ *     {@code adSelectionId} is for a server auction run where device info collected by
+ *     [AdSelectionManager#getAdSelectionData} then the impression reporting request should
+ *     only include the ad selection id.
  */
+@SuppressLint("ClassVerificationFailure")
 class ReportImpressionRequest public constructor(
     val adSelectionId: Long,
     val adSelectionConfig: AdSelectionConfig
 ) {
+    @ExperimentalFeatures.Ext8OptIn
+    constructor(adSelectionId: Long) : this(adSelectionId, AdSelectionConfig.EMPTY)
 
     /** Checks whether two [ReportImpressionRequest] objects contain the same information.  */
     override fun equals(other: Any?): Boolean {
@@ -49,4 +64,51 @@
         return "ReportImpressionRequest: adSelectionId=$adSelectionId, " +
             "adSelectionConfig=$adSelectionConfig"
     }
+
+    @SuppressLint("NewApi")
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    internal fun convertToAdServices(): android.adservices.adselection.ReportImpressionRequest {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.convertReportImpressionRequest(this)
+        }
+        return Ext4Impl.convertReportImpressionRequest(this)
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    private class Ext10Impl private constructor() {
+        companion object {
+            fun convertReportImpressionRequest(
+                request: ReportImpressionRequest
+            ): android.adservices.adselection.ReportImpressionRequest {
+                return if (request.adSelectionConfig == AdSelectionConfig.EMPTY)
+                    android.adservices.adselection.ReportImpressionRequest(
+                        request.adSelectionId
+                    )
+                else android.adservices.adselection.ReportImpressionRequest(
+                    request.adSelectionId,
+                    request.adSelectionConfig.convertToAdServices()
+                )
+            }
+        }
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private class Ext4Impl private constructor() {
+        companion object {
+            fun convertReportImpressionRequest(
+                request: ReportImpressionRequest
+            ): android.adservices.adselection.ReportImpressionRequest {
+                if (request.adSelectionConfig == AdSelectionConfig.EMPTY) {
+                    throw UnsupportedOperationException("adSelectionConfig is mandatory for" +
+                        "API versions lower than ext 10")
+                }
+                return android.adservices.adselection.ReportImpressionRequest(
+                    request.adSelectionId,
+                    request.adSelectionConfig.convertToAdServices()
+                )
+            }
+        }
+    }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt
new file mode 100644
index 0000000..5528a9f
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/UpdateAdCounterHistogramRequest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.common.FrequencyCapFilters
+
+/**
+ * This class represents input to the [AdSelectionManager#updateAdCounterHistogram] in the
+ * [AdSelectionManager].
+ *
+ * Note that the [FrequencyCapFilters.AD_EVENT_TYPE_WIN] event type cannot be updated manually using
+ * the [AdSelectionManager#updateAdCounterHistogram] API.
+ *
+ * @param adSelectionId An ID unique only to a device user that identifies a successful ad
+ * selection.
+ * @param adEventType A render URL for the winning ad.
+ * @param callerAdTech The caller adtech entity's [AdTechIdentifier].
+ */
+@ExperimentalFeatures.Ext8OptIn
+class UpdateAdCounterHistogramRequest public constructor(
+    val adSelectionId: Long,
+    val adEventType: Int,
+    val callerAdTech: AdTechIdentifier
+) {
+    init {
+        require(adEventType != FrequencyCapFilters.AD_EVENT_TYPE_WIN) {
+            "Win event types cannot be manually updated."
+        }
+        require(adEventType == FrequencyCapFilters.AD_EVENT_TYPE_IMPRESSION ||
+            adEventType == FrequencyCapFilters.AD_EVENT_TYPE_VIEW ||
+            adEventType == FrequencyCapFilters.AD_EVENT_TYPE_CLICK) {
+            "Ad event type must be one of AD_EVENT_TYPE_IMPRESSION, AD_EVENT_TYPE_VIEW, or" +
+                " AD_EVENT_TYPE_CLICK"
+        }
+    }
+
+    /**
+     * Checks whether two [UpdateAdCounterHistogramRequest] objects contain the same information.
+     */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is UpdateAdCounterHistogramRequest) return false
+        return this.adSelectionId == other.adSelectionId &&
+            this.adEventType == other.adEventType &&
+            this.callerAdTech == other.callerAdTech
+    }
+
+    /** Returns the hash of the [UpdateAdCounterHistogramRequest] object's data. */
+    override fun hashCode(): Int {
+        var hash = adSelectionId.hashCode()
+        hash = 31 * hash + adEventType.hashCode()
+        hash = 31 * hash + callerAdTech.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method. */
+    override fun toString(): String {
+        val adEventTypeStr = when (adEventType) {
+            FrequencyCapFilters.AD_EVENT_TYPE_IMPRESSION -> "AD_EVENT_TYPE_IMPRESSION"
+            FrequencyCapFilters.AD_EVENT_TYPE_VIEW -> "AD_EVENT_TYPE_VIEW"
+            else -> "AD_EVENT_TYPE_CLICK"
+        }
+        return "UpdateAdCounterHistogramRequest: adSelectionId=$adSelectionId, " +
+            "adEventType=$adEventTypeStr, callerAdTech=$callerAdTech"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices():
+        android.adservices.adselection.UpdateAdCounterHistogramRequest {
+        return android.adservices.adselection.UpdateAdCounterHistogramRequest.Builder(
+            adSelectionId,
+            adEventType,
+            callerAdTech.convertToAdServices())
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt
index c12afe8..b1db375 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdData.kt
@@ -16,35 +16,154 @@
 
 package androidx.privacysandbox.ads.adservices.common
 
+import android.annotation.SuppressLint
 import android.net.Uri
+import android.os.Build
+import android.os.ext.SdkExtensions
+import android.util.Log
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 
 /**
  * Represents data specific to an ad that is necessary for ad selection and rendering.
+ *
  * @param renderUri a URI pointing to the ad's rendering assets
  * @param metadata buyer ad metadata represented as a JSON string
+ * @param adCounterKeys the set of keys used in counting events
+ * @param adFilters all [AdFilters] associated with the ad
+ * @param adRenderId ad render id for server auctions
  */
-class AdData public constructor(
+@OptIn(ExperimentalFeatures.Ext8OptIn::class, ExperimentalFeatures.Ext10OptIn::class)
+@SuppressLint("ClassVerificationFailure")
+class AdData
+@ExperimentalFeatures.Ext10OptIn
+public constructor(
     val renderUri: Uri,
-    val metadata: String
-    ) {
+    val metadata: String,
+    val adCounterKeys: Set<Int> = setOf(),
+    val adFilters: AdFilters? = null,
+    val adRenderId: String? = null
+) {
+    /**
+     * Represents data specific to an ad that is necessary for ad selection and rendering.
+     *
+     * @param renderUri a URI pointing to the ad's rendering assets
+     * @param metadata buyer ad metadata represented as a JSON string
+     * @param adCounterKeys the set of keys used in counting events
+     * @param adFilters all [AdFilters] associated with the ad
+     */
+    @ExperimentalFeatures.Ext8OptIn
+    constructor(
+        renderUri: Uri,
+        metadata: String,
+        adCounterKeys: Set<Int> = setOf(),
+        adFilters: AdFilters? = null
+    ) : this(renderUri, metadata, adCounterKeys, adFilters, null)
 
-    /** Checks whether two [AdData] objects contain the same information.  */
+    /**
+     * Represents data specific to an ad that is necessary for ad selection and rendering.
+     *
+     * @param renderUri a URI pointing to the ad's rendering assets
+     * @param metadata buyer ad metadata represented as a JSON string
+     */
+    constructor(
+        renderUri: Uri,
+        metadata: String,
+    ) : this(renderUri, metadata, setOf(), null)
+
+    /** Checks whether two [AdData] objects contain the same information. */
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is AdData) return false
-        return this.renderUri == other.renderUri &&
-            this.metadata == other.metadata
+        return renderUri == other.renderUri &&
+            metadata == other.metadata &&
+            adCounterKeys == other.adCounterKeys &&
+            adFilters == other.adFilters &&
+            adRenderId == other.adRenderId
     }
 
-    /** Returns the hash of the [AdData] object's data.  */
+    /** Returns the hash of the [AdData] object's data. */
     override fun hashCode(): Int {
         var hash = renderUri.hashCode()
         hash = 31 * hash + metadata.hashCode()
+        hash = 31 * hash + adCounterKeys.hashCode()
+        hash = 31 * hash + adFilters.hashCode()
+        hash = 31 * hash + adRenderId.hashCode()
         return hash
     }
 
-    /** Overrides the toString method.  */
+    /** Overrides the toString method. */
     override fun toString(): String {
-        return "AdData: renderUri=$renderUri, metadata='$metadata'"
+        return "AdData: renderUri=$renderUri, metadata='$metadata', " +
+            "adCounterKeys=$adCounterKeys, adFilters=$adFilters, adRenderId=$adRenderId"
+    }
+
+    @SuppressLint("NewApi")
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    internal fun convertToAdServices(): android.adservices.common.AdData {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.convertAdData(this)
+        } else if (AdServicesInfo.adServicesVersion() >= 8 ||
+                AdServicesInfo.extServicesVersion() >= 9) {
+            return Ext8Impl.convertAdData(this)
+        }
+        return Ext4Impl.convertAdData(this)
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    private class Ext10Impl private constructor() {
+        companion object {
+            fun convertAdData(adData: AdData): android.adservices.common.AdData {
+                return android.adservices.common.AdData.Builder()
+                    .setMetadata(adData.metadata)
+                    .setRenderUri(adData.renderUri)
+                    .setAdCounterKeys(adData.adCounterKeys)
+                    .setAdFilters(adData.adFilters?.convertToAdServices())
+                    .setAdRenderId(adData.adRenderId)
+                    .build()
+            }
+        }
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private class Ext8Impl private constructor() {
+        companion object {
+            fun convertAdData(adData: AdData): android.adservices.common.AdData {
+                adData.adRenderId?.let { Log.w("AdData",
+                    "adRenderId is ignored. Min version to use adRenderId is " +
+                        "API 31 ext 10") }
+                return android.adservices.common.AdData.Builder()
+                    .setMetadata(adData.metadata)
+                    .setRenderUri(adData.renderUri)
+                    .setAdCounterKeys(adData.adCounterKeys)
+                    .setAdFilters(adData.adFilters?.convertToAdServices())
+                    .build()
+            }
+        }
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private class Ext4Impl private constructor() {
+        companion object {
+            fun convertAdData(adData: AdData): android.adservices.common.AdData {
+                if (adData.adCounterKeys.isNotEmpty()) { Log.w("AdData",
+                    "adCounterKeys is ignored. Min version to use adCounterKeys is " +
+                        "API 33 ext 8 or API 31/32 ext 9") }
+                adData.adFilters?.let { Log.w("AdData",
+                    "adFilters is ignored. Min version to use adFilters is " +
+                        "API 33 ext 8 or API 31/32 ext 9") }
+                adData.adRenderId?.let { Log.w("AdData",
+                    "adRenderId is ignored. Min version to use adRenderId is " +
+                        "API 31 ext 10") }
+                return android.adservices.common.AdData.Builder()
+                    .setMetadata(adData.metadata)
+                    .setRenderUri(adData.renderUri)
+                    .build()
+            }
+        }
     }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdFilters.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdFilters.kt
new file mode 100644
index 0000000..948fe40
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdFilters.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+/**
+ * A container class for filters which are associated with an ad.
+ *
+ * If any of the filters in an [AdFilters] instance are not satisfied, the associated ad
+ * will not be eligible for ad selection. Filters are optional ad parameters and are not required as
+ * part of [AdData].
+ *
+ * @param frequencyCapFilters Gets the [FrequencyCapFilters] instance that represents all frequency
+ * cap filters for the ad.
+ */
+@ExperimentalFeatures.Ext8OptIn
+class AdFilters public constructor(
+    val frequencyCapFilters: FrequencyCapFilters?
+) {
+    /** Checks whether two [AdFilters] objects contain the same information. */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is AdFilters) return false
+        return this.frequencyCapFilters == other.frequencyCapFilters
+    }
+
+    /** Returns the hash of the [AdFilters] object's data. */
+    override fun hashCode(): Int {
+        return frequencyCapFilters.hashCode()
+    }
+
+    /** Overrides the toString method. */
+    override fun toString(): String {
+        return "AdFilters: frequencyCapFilters=$frequencyCapFilters"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.common.AdFilters {
+        return android.adservices.common.AdFilters.Builder()
+            .setFrequencyCapFilters(frequencyCapFilters?.convertToAdServices())
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdSelectionSignals.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdSelectionSignals.kt
index 5495ae5..6cb0b4a 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdSelectionSignals.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdSelectionSignals.kt
@@ -16,6 +16,12 @@
 
 package androidx.privacysandbox.ads.adservices.common
 
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
 /**
  * This class holds JSON that will be passed into a JavaScript function during ad selection. Its
  * contents are not used by <a
@@ -24,6 +30,7 @@
  * function.
  * @param signals Any valid JSON string to create the AdSelectionSignals with.
  */
+@SuppressLint("ClassVerificationFailure")
 class AdSelectionSignals public constructor(val signals: String) {
     /**
      * Compares this AdSelectionSignals to the specified object. The result is true if and only if
@@ -59,4 +66,11 @@
     override fun toString(): String {
         return "AdSelectionSignals: $signals"
     }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.common.AdSelectionSignals {
+        return android.adservices.common.AdSelectionSignals.fromString(signals)
+    }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt
index 4180e13..084d9f6 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/AdTechIdentifier.kt
@@ -16,11 +16,18 @@
 
 package androidx.privacysandbox.ads.adservices.common
 
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
 /**
  * An Identifier representing an ad buyer or seller.
  *
  * @param identifier The identifier.
  */
+@SuppressLint("ClassVerificationFailure")
 class AdTechIdentifier public constructor(val identifier: String) {
 
     /**
@@ -57,4 +64,11 @@
     override fun toString(): String {
         return "$identifier"
     }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.common.AdTechIdentifier {
+        return android.adservices.common.AdTechIdentifier.fromString(identifier)
+    }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
index e1935b3..c7ac0e5 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/ExperimentalFeatures.kt
@@ -17,7 +17,7 @@
 package androidx.privacysandbox.ads.adservices.common
 
 /**
- * Contains AdServices experimental feature opt-in anntations.
+ * Contains AdServices experimental feature opt-in annotations.
  */
 sealed interface ExperimentalFeatures {
     /**
@@ -25,4 +25,10 @@
      */
     @RequiresOptIn("This API is experimental.", RequiresOptIn.Level.WARNING)
     annotation class RegisterSourceOptIn
+
+    @RequiresOptIn("The Ext8 API is experimental.", RequiresOptIn.Level.WARNING)
+    annotation class Ext8OptIn
+
+    @RequiresOptIn("The Ext10 API is experimental.", RequiresOptIn.Level.WARNING)
+    annotation class Ext10OptIn
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt
new file mode 100644
index 0000000..502262d
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/FrequencyCapFilters.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+/**
+ * A container for the ad filters that are based on frequency caps.
+ *
+ * Frequency caps filters combine an event type with a list of [KeyedFrequencyCap] objects
+ * to define a collection of ad filters. If any of these frequency caps are exceeded for a given ad,
+ * the ad will be removed from the group of ads submitted to a buyer adtech's bidding function.
+ *
+ * @param keyedFrequencyCapsForWinEvents The list of frequency caps applied to events which
+ * correlate to a win as interpreted by an adtech.
+ * @param keyedFrequencyCapsForImpressionEvents The list of frequency caps applied to events which
+ * correlate to an impression as interpreted by an adtech.
+ * @param keyedFrequencyCapsForViewEvents The list of frequency caps applied to events which
+ * correlate to a view as interpreted by an adtech.
+ * @param keyedFrequencyCapsForClickEvents The list of frequency caps applied to events which
+ * correlate to a click as interpreted by an adtech.
+ */
+@ExperimentalFeatures.Ext8OptIn
+class FrequencyCapFilters @JvmOverloads public constructor(
+    val keyedFrequencyCapsForWinEvents: List<KeyedFrequencyCap> = listOf(),
+    val keyedFrequencyCapsForImpressionEvents: List<KeyedFrequencyCap> = listOf(),
+    val keyedFrequencyCapsForViewEvents: List<KeyedFrequencyCap> = listOf(),
+    val keyedFrequencyCapsForClickEvents: List<KeyedFrequencyCap> = listOf()
+) {
+    /** Checks whether two [FrequencyCapFilters] objects contain the same information. */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is FrequencyCapFilters) return false
+        return this.keyedFrequencyCapsForWinEvents == other.keyedFrequencyCapsForWinEvents &&
+            this.keyedFrequencyCapsForImpressionEvents ==
+                other.keyedFrequencyCapsForImpressionEvents &&
+            this.keyedFrequencyCapsForViewEvents == other.keyedFrequencyCapsForViewEvents &&
+            this.keyedFrequencyCapsForClickEvents == other.keyedFrequencyCapsForClickEvents
+    }
+
+    /** Returns the hash of the [FrequencyCapFilters] object's data. */
+    override fun hashCode(): Int {
+        var hash = keyedFrequencyCapsForWinEvents.hashCode()
+        hash = 31 * hash + keyedFrequencyCapsForImpressionEvents.hashCode()
+        hash = 31 * hash + keyedFrequencyCapsForViewEvents.hashCode()
+        hash = 31 * hash + keyedFrequencyCapsForClickEvents.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method. */
+    override fun toString(): String {
+        return "FrequencyCapFilters: " +
+            "keyedFrequencyCapsForWinEvents=$keyedFrequencyCapsForWinEvents, " +
+            "keyedFrequencyCapsForImpressionEvents=$keyedFrequencyCapsForImpressionEvents, " +
+            "keyedFrequencyCapsForViewEvents=$keyedFrequencyCapsForViewEvents, " +
+            "keyedFrequencyCapsForClickEvents=$keyedFrequencyCapsForClickEvents"
+    }
+
+    companion object {
+        /**
+         * The WIN ad event type is automatically populated within the Protected Audience service
+         * for any winning ad which is returned from Protected Audience ad selection.
+         *
+         * It should not be used to manually update an ad counter histogram.
+         */
+        public const val AD_EVENT_TYPE_WIN: Int =
+            android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_WIN
+        public const val AD_EVENT_TYPE_IMPRESSION: Int =
+            android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_IMPRESSION
+        public const val AD_EVENT_TYPE_VIEW: Int =
+            android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_VIEW
+        public const val AD_EVENT_TYPE_CLICK: Int =
+            android.adservices.common.FrequencyCapFilters.AD_EVENT_TYPE_CLICK
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.common.FrequencyCapFilters {
+        return android.adservices.common.FrequencyCapFilters.Builder()
+            .setKeyedFrequencyCapsForWinEvents(
+                keyedFrequencyCapsForWinEvents.convertToAdServices())
+            .setKeyedFrequencyCapsForImpressionEvents(
+                keyedFrequencyCapsForImpressionEvents.convertToAdServices())
+            .setKeyedFrequencyCapsForViewEvents(
+                keyedFrequencyCapsForViewEvents.convertToAdServices())
+            .setKeyedFrequencyCapsForClickEvents(
+                keyedFrequencyCapsForClickEvents.convertToAdServices())
+            .build()
+    }
+
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    private fun List<KeyedFrequencyCap>.convertToAdServices():
+            MutableList<android.adservices.common.KeyedFrequencyCap> {
+        val result = mutableListOf<android.adservices.common.KeyedFrequencyCap>()
+        for (keyedFrequencyCap in this) {
+            result.add(keyedFrequencyCap.convertToAdServices())
+        }
+        return result
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt
new file mode 100644
index 0000000..7e95473
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/common/KeyedFrequencyCap.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.common
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import java.time.Duration
+
+/**
+ * A frequency cap for a specific ad counter key.
+ *
+ * Frequency caps define the maximum rate an event can occur within a given time interval. If the
+ * frequency cap is exceeded, the associated ad will be filtered out of ad selection.
+ *
+ * @param adCounterKey The ad counter key that the frequency cap is applied to.
+ * @param maxCount A render URL for the winning ad.
+ * @param interval The caller adtech entity's [AdTechIdentifier].
+ */
+@ExperimentalFeatures.Ext8OptIn
+class KeyedFrequencyCap public constructor(
+    val adCounterKey: Int,
+    val maxCount: Int,
+    val interval: Duration
+) {
+    /** Checks whether two [KeyedFrequencyCap] objects contain the same information. */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is KeyedFrequencyCap) return false
+        return this.adCounterKey == other.adCounterKey &&
+            this.maxCount == other.maxCount &&
+            this.interval == other.interval
+    }
+
+    /** Returns the hash of the [KeyedFrequencyCap] object's data. */
+    override fun hashCode(): Int {
+        var hash = adCounterKey.hashCode()
+        hash = 31 * hash + maxCount.hashCode()
+        hash = 31 * hash + interval.hashCode()
+        return hash
+    }
+
+    /** Overrides the toString method. */
+    override fun toString(): String {
+        return "KeyedFrequencyCap: adCounterKey=$adCounterKey, maxCount=$maxCount, " +
+            "interval=$interval"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 8)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+    internal fun convertToAdServices(): android.adservices.common.KeyedFrequencyCap {
+        return android.adservices.common.KeyedFrequencyCap.Builder(
+            adCounterKey,
+            maxCount,
+            interval)
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt
index a9b91d7..d8aaeee 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.os.LimitExceededException
 import androidx.annotation.RequiresPermission
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 
 /**
@@ -63,6 +64,44 @@
     abstract suspend fun joinCustomAudience(request: JoinCustomAudienceRequest)
 
     /**
+     * Adds the user to the [CustomAudience] fetched from a {@code fetchUri}.
+     *
+     * An attempt to register the user for a custom audience with the same combination of {@code
+     * ownerPackageName}, {@code buyer}, and {@code name} will cause the existing custom audience's
+     * information to be overwritten, including the list of ads data.
+     *
+     * Note that the ads list can be completely overwritten by the daily background fetch job.
+     *
+     * This call fails with an [SecurityException] if
+     *
+     * <ol>
+     *   <li>the {@code ownerPackageName} is not calling app's package name and/or
+     *   <li>the buyer is not authorized to use the API.
+     * </ol>
+     *
+     * This call fails with an [IllegalArgumentException] if
+     *
+     * <ol>
+     *   <li>the storage limit has been exceeded by the calling application and/or
+     *   <li>any URI parameters in the [CustomAudience] given are not authenticated with the
+     *       [CustomAudience] buyer.
+     * </ol>
+     *
+     * This call fails with [LimitExceededException] if the calling package exceeds the
+     * allowed rate limits and is throttled.
+     *
+     * This call fails with an [IllegalStateException] if an internal service error is encountered.
+     *
+     * This call fails with an [UnsupportedOperationException] if the Android API level and
+     * AdServices module versions don't support this API.
+     *
+     * @param request The request to fetch and join custom audience.
+     */
+    @ExperimentalFeatures.Ext10OptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    abstract suspend fun fetchAndJoinCustomAudience(request: FetchAndJoinCustomAudienceRequest)
+
+    /**
      * Attempts to remove a user from a custom audience by deleting any existing [CustomAudience]
      * data, identified by {@code ownerPackageName}, {@code buyer}, and {@code
      * name}.
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt
index 44f09a8..031635b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt
@@ -26,10 +26,11 @@
 import androidx.annotation.RestrictTo
 import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.ads.adservices.common.AdData
-import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
-import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import kotlinx.coroutines.suspendCancellableCoroutine
 
+@OptIn(ExperimentalFeatures.Ext10OptIn::class)
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @SuppressLint("NewApi", "ClassVerificationFailure")
 @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
@@ -51,6 +52,15 @@
 
     @DoNotInline
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun fetchAndJoinCustomAudience(request: FetchAndJoinCustomAudienceRequest) {
+        if (AdServicesInfo.adServicesVersion() >= 10 || AdServicesInfo.extServicesVersion() >= 10) {
+            return Ext10Impl.fetchAndJoinCustomAudience(customAudienceManager, request)
+        }
+        throw UnsupportedOperationException("API is not available. Min version is API 31 ext 10")
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
     override suspend fun leaveCustomAudience(request: LeaveCustomAudienceRequest) {
         suspendCancellableCoroutine { continuation ->
             customAudienceManager.leaveCustomAudience(
@@ -73,7 +83,7 @@
         request: LeaveCustomAudienceRequest
     ): android.adservices.customaudience.LeaveCustomAudienceRequest {
         return android.adservices.customaudience.LeaveCustomAudienceRequest.Builder()
-            .setBuyer(convertAdTechIdentifier(request.buyer))
+            .setBuyer(request.buyer.convertToAdServices())
             .setName(request.name)
             .build()
     }
@@ -83,36 +93,27 @@
     ): android.adservices.customaudience.CustomAudience {
         return android.adservices.customaudience.CustomAudience.Builder()
             .setActivationTime(request.activationTime)
-            .setAds(convertAdData(request.ads))
+            .setAds(convertAds(request.ads))
             .setBiddingLogicUri(request.biddingLogicUri)
-            .setBuyer(convertAdTechIdentifier(request.buyer))
+            .setBuyer(request.buyer.convertToAdServices())
             .setDailyUpdateUri(request.dailyUpdateUri)
             .setExpirationTime(request.expirationTime)
             .setName(request.name)
             .setTrustedBiddingData(convertTrustedSignals(request.trustedBiddingSignals))
-            .setUserBiddingSignals(convertBiddingSignals(request.userBiddingSignals))
+            .setUserBiddingSignals(request.userBiddingSignals?.convertToAdServices())
             .build()
     }
 
-    private fun convertAdData(
+    private fun convertAds(
         input: List<AdData>
     ): List<android.adservices.common.AdData> {
         val result = mutableListOf<android.adservices.common.AdData>()
         for (ad in input) {
-            result.add(android.adservices.common.AdData.Builder()
-                .setMetadata(ad.metadata)
-                .setRenderUri(ad.renderUri)
-                .build())
+            result.add(ad.convertToAdServices())
         }
         return result
     }
 
-    private fun convertAdTechIdentifier(
-        input: AdTechIdentifier
-    ): android.adservices.common.AdTechIdentifier {
-        return android.adservices.common.AdTechIdentifier.fromString(input.identifier)
-    }
-
     private fun convertTrustedSignals(
         input: TrustedBiddingData?
     ): android.adservices.customaudience.TrustedBiddingData? {
@@ -123,10 +124,24 @@
             .build()
     }
 
-    private fun convertBiddingSignals(
-        input: AdSelectionSignals?
-    ): android.adservices.common.AdSelectionSignals? {
-        if (input == null) return null
-        return android.adservices.common.AdSelectionSignals.fromString(input.signals)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    private class Ext10Impl private constructor() {
+        companion object {
+            @DoNotInline
+            @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+            suspend fun fetchAndJoinCustomAudience(
+                customAudienceManager: android.adservices.customaudience.CustomAudienceManager,
+                fetchAndJoinCustomAudienceRequest: FetchAndJoinCustomAudienceRequest
+            ) {
+                suspendCancellableCoroutine { continuation ->
+                    customAudienceManager.fetchAndJoinCustomAudience(
+                        fetchAndJoinCustomAudienceRequest.convertToAdServices(),
+                        Runnable::run,
+                        continuation.asOutcomeReceiver()
+                    )
+                }
+            }
+        }
     }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/FetchAndJoinCustomAudienceRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/FetchAndJoinCustomAudienceRequest.kt
new file mode 100644
index 0000000..6a73258
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/FetchAndJoinCustomAudienceRequest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.customaudience
+
+import android.net.Uri
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import java.time.Instant
+
+/**
+ * The request object to fetch and join a custom audience.
+ *
+ * @param fetchUri The [Uri] from which the custom audience is to be fetched.
+ * @param name The name of the custom audience to join.
+ * @param activationTime The [Instant] by which joining the custom audience will be delayed.
+ * @param expirationTime The [Instant] by when the membership to the custom audience will expire.
+ * @param userBiddingSignals The [AdSelectionSignals] object representing the user bidding signals
+ * for the custom audience.
+ */
+@ExperimentalFeatures.Ext10OptIn
+class FetchAndJoinCustomAudienceRequest public constructor(
+    val fetchUri: Uri,
+    val name: String? = null,
+    val activationTime: Instant? = null,
+    val expirationTime: Instant? = null,
+    val userBiddingSignals: AdSelectionSignals? = null
+) {
+    /**
+     * Checks whether two [FetchAndJoinCustomAudienceRequest] objects contain the same information.
+     */
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is FetchAndJoinCustomAudienceRequest) return false
+        return this.fetchUri == other.fetchUri &&
+            this.name == other.name &&
+            this.activationTime == other.activationTime &&
+            this.expirationTime == other.expirationTime &&
+            this.userBiddingSignals == other.userBiddingSignals
+    }
+
+    /**
+     * Returns the hash of the [FetchAndJoinCustomAudienceRequest] object's data.
+     */
+    override fun hashCode(): Int {
+        var hash = fetchUri.hashCode()
+        hash = 31 * hash + name.hashCode()
+        hash = 31 * hash + activationTime.hashCode()
+        hash = 31 * hash + expirationTime.hashCode()
+        hash = 31 * hash + userBiddingSignals.hashCode()
+        return hash
+    }
+
+    override fun toString(): String {
+        return "FetchAndJoinCustomAudienceRequest: fetchUri=$fetchUri, " +
+            "name=$name, activationTime=$activationTime, " +
+            "expirationTime=$expirationTime, userBiddingSignals=$userBiddingSignals"
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 10)
+    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 10)
+    internal fun convertToAdServices():
+        android.adservices.customaudience.FetchAndJoinCustomAudienceRequest {
+        return android.adservices.customaudience.FetchAndJoinCustomAudienceRequest
+            .Builder(fetchUri)
+            .setName(name)
+            .setActivationTime(activationTime)
+            .setExpirationTime(expirationTime)
+            .setUserBiddingSignals(userBiddingSignals?.convertToAdServices())
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
index 6f8b056..535e485 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.RequiresApi
 
 internal object AdServicesInfo {
-
     fun adServicesVersion(): Int {
         return if (Build.VERSION.SDK_INT >= 33) {
             Extensions30Impl.getAdServicesVersion()
diff --git a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
index 8bcd445..412b983 100644
--- a/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
+++ b/room/room-runtime/src/androidMain/kotlin/androidx/room/driver/SupportSQLiteStatement.android.kt
@@ -182,13 +182,17 @@
 
         override fun reset() {
             throwIfClosed()
+            cursor?.close()
+            cursor = null
+        }
+
+        override fun clearBindings() {
+            throwIfClosed()
             bindingTypes = IntArray(0)
             longBindings = LongArray(0)
             doubleBindings = DoubleArray(0)
             stringBindings = emptyArray()
             blobBindings = emptyArray()
-            cursor?.close()
-            cursor = null
         }
 
         override fun close() {
@@ -320,6 +324,10 @@
         }
 
         override fun reset() {
+            // Android executes and releases non-query statements, so there is nothing to 'reset'.
+        }
+
+        override fun clearBindings() {
             throwIfClosed()
             delegate.clearBindings()
         }
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 4c84e2d..74321c8 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
@@ -495,6 +495,10 @@
             delegate.reset()
         }
 
+        override fun clearBindings() = withStateCheck {
+            delegate.clearBindings()
+        }
+
         override fun close() = withStateCheck {
             delegate.close()
         }
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 786d43e..ef5bed9 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
@@ -257,6 +257,19 @@
         }
     }
 
+    @Test
+    fun clearBindings() = testWithConnection {
+        it.execSQL("CREATE TABLE Foo (id)")
+        it.execSQL("INSERT INTO Foo (id) VALUES (1)")
+        it.prepare("SELECT * FROM Foo WHERE id = ?").use {
+            it.bindLong(1, 1)
+            assertThat(it.step()).isTrue()
+            it.reset()
+            it.clearBindings()
+            assertThat(it.step()).isFalse()
+        }
+    }
+
     private inline fun testWithConnection(block: (SQLiteConnection) -> Unit) {
         val driver = getDriver()
         val connection = driver.open()
diff --git a/sqlite/sqlite-bundled/src/androidJvmCommonMain/jni/sqlite_bindings.cpp b/sqlite/sqlite-bundled/src/androidJvmCommonMain/jni/sqlite_bindings.cpp
index 3e5fb88..da884f2 100644
--- a/sqlite/sqlite-bundled/src/androidJvmCommonMain/jni/sqlite_bindings.cpp
+++ b/sqlite/sqlite-bundled/src/androidJvmCommonMain/jni/sqlite_bindings.cpp
@@ -290,6 +290,18 @@
 }
 
 extern "C" JNIEXPORT void JNICALL
+Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClearBindings(
+        JNIEnv* env,
+        jclass clazz,
+        jlong stmtPointer) {
+    sqlite3_stmt* stmt = reinterpret_cast<sqlite3_stmt*>(stmtPointer);
+    int rc = sqlite3_clear_bindings(stmt);
+    if (rc != SQLITE_OK) {
+        throwSQLiteException(env, rc, sqlite3_errmsg(sqlite3_db_handle(stmt)));
+    }
+}
+
+extern "C" JNIEXPORT void JNICALL
 Java_androidx_sqlite_driver_bundled_BundledSQLiteStatementKt_nativeClose(
         JNIEnv* env,
         jclass clazz,
diff --git a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.androidJvmCommon.kt b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.androidJvmCommon.kt
index bb60768..99a78f0 100644
--- a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.androidJvmCommon.kt
+++ b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteStatement.androidJvmCommon.kt
@@ -100,6 +100,11 @@
         nativeReset(statementPointer)
     }
 
+    override fun clearBindings() {
+        throwIfClosed()
+        nativeClearBindings(statementPointer)
+    }
+
     override fun close() {
         nativeClose(statementPointer)
         isClosed = true
@@ -134,4 +139,5 @@
 private external fun nativeGetColumnCount(pointer: Long): Int
 private external fun nativeGetColumnName(pointer: Long, index: Int): String
 private external fun nativeReset(pointer: Long)
+private external fun nativeClearBindings(pointer: Long)
 private external fun nativeClose(pointer: Long)
diff --git a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt
index 068e716..61a2221 100644
--- a/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt
+++ b/sqlite/sqlite-framework/src/androidMain/kotlin/androidx/sqlite/driver/AndroidSQLiteStatement.android.kt
@@ -169,13 +169,17 @@
 
         override fun reset() {
             throwIfClosed()
+            cursor?.close()
+            cursor = null
+        }
+
+        override fun clearBindings() {
+            throwIfClosed()
             bindingTypes = IntArray(0)
             longBindings = LongArray(0)
             doubleBindings = DoubleArray(0)
             stringBindings = emptyArray()
             blobBindings = emptyArray()
-            cursor?.close()
-            cursor = null
         }
 
         override fun close() {
@@ -319,6 +323,10 @@
         }
 
         override fun reset() {
+            // Android executes and releases non-query statements, so there is nothing to 'reset'.
+        }
+
+        override fun clearBindings() {
             throwIfClosed()
             delegate.clearBindings()
         }
diff --git a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
index 80ea382..4136b2f 100644
--- a/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
+++ b/sqlite/sqlite-framework/src/nativeMain/kotlin/androidx/sqlite/driver/NativeSQLiteStatement.kt
@@ -41,6 +41,7 @@
 import sqlite3.sqlite3_bind_int64
 import sqlite3.sqlite3_bind_null
 import sqlite3.sqlite3_bind_text16
+import sqlite3.sqlite3_clear_bindings
 import sqlite3.sqlite3_column_blob
 import sqlite3.sqlite3_column_bytes
 import sqlite3.sqlite3_column_count
@@ -196,6 +197,14 @@
         }
     }
 
+    override fun clearBindings() {
+        throwIfClosed()
+        val resultCode = sqlite3_clear_bindings(stmtPointer)
+        if (resultCode != SQLITE_OK) {
+            throwSQLiteException(resultCode, dbPointer.getErrorMsg())
+        }
+    }
+
     override fun close() {
         sqlite3_finalize(stmtPointer)
         isClosed = true
diff --git a/sqlite/sqlite/api/current.txt b/sqlite/sqlite/api/current.txt
index 2004b4f..7c8bfc7 100644
--- a/sqlite/sqlite/api/current.txt
+++ b/sqlite/sqlite/api/current.txt
@@ -22,6 +22,7 @@
     method public void bindLong(int index, long value);
     method public void bindNull(int index);
     method public void bindText(int index, String value);
+    method public void clearBindings();
     method public void close();
     method public byte[] getBlob(int index);
     method public int getColumnCount();
diff --git a/sqlite/sqlite/api/restricted_current.txt b/sqlite/sqlite/api/restricted_current.txt
index 2004b4f..7c8bfc7 100644
--- a/sqlite/sqlite/api/restricted_current.txt
+++ b/sqlite/sqlite/api/restricted_current.txt
@@ -22,6 +22,7 @@
     method public void bindLong(int index, long value);
     method public void bindNull(int index);
     method public void bindText(int index, String value);
+    method public void clearBindings();
     method public void close();
     method public byte[] getBlob(int index);
     method public int getColumnCount();
diff --git a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
index bccd831..e0f62d6 100644
--- a/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
+++ b/sqlite/sqlite/src/commonMain/kotlin/androidx/sqlite/SQLiteStatement.kt
@@ -138,6 +138,11 @@
     fun reset()
 
     /**
+     * Clears all parameter bindings. Unset bindings are treated as NULL.
+     */
+    fun clearBindings()
+
+    /**
      * Closes the statement.
      *
      * Once a statement is closed it should no longer be used. Calling this function on an already
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ConfiguredShapeScreen.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ConfiguredShapeScreen.kt
deleted file mode 100644
index 7df6527..0000000
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/ConfiguredShapeScreen.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.compose.material
-
-import android.content.res.Configuration
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.platform.LocalConfiguration
-
-/**
- * Helper function which changes [Configuration.isScreenRound] result by changing
- * [Configuration.screenLayout] field and allows to mock square and round behaviour
- * @param isRound mocks shape either to round or not round (square) form
- */
-@Composable
-fun ConfiguredShapeScreen(
-    isRound: Boolean,
-    content: @Composable () -> Unit
-) {
-    val newConfiguration = Configuration(LocalConfiguration.current)
-    newConfiguration.screenLayout = newConfiguration.screenLayout and
-        Configuration.SCREENLAYOUT_ROUND_MASK.inv() or
-        if (isRound) {
-            Configuration.SCREENLAYOUT_ROUND_YES
-        } else {
-            Configuration.SCREENLAYOUT_ROUND_NO
-        }
-
-    CompositionLocalProvider(
-        LocalConfiguration provides newConfiguration,
-        content = content
-    )
-}
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt
index 2c400ae..ed7b8a0 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/TimeTextTest.kt
@@ -23,6 +23,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.RoundScreen
 import androidx.compose.ui.test.assertCountEquals
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -62,7 +64,7 @@
         val timeState = mutableStateOf("Unchanged")
 
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     modifier = Modifier.testTag(TEST_TAG),
                     timeSource = object : TimeSource {
@@ -83,7 +85,7 @@
         val timeState = mutableStateOf("Unchanged")
 
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     modifier = Modifier.testTag(TEST_TAG),
                     timeSource = object : TimeSource {
@@ -101,7 +103,7 @@
     @Test
     fun supports_start_linear_text_only_on_square_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     startLinearContent = {
                         Text(
@@ -129,7 +131,7 @@
     @Test
     fun supports_start_curved_text_only_on_round_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     startLinearContent = {
                         Text(
@@ -156,7 +158,7 @@
     @Test
     fun supports_end_linear_text_only_on_square_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     endLinearContent = {
                         Text(
@@ -183,7 +185,7 @@
     @Test
     fun supports_end_curved_text_only_on_round_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     endLinearContent = {
                         Text(
@@ -210,7 +212,7 @@
     @Test
     fun omits_separator_with_only_time_on_square_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     textLinearSeparator = {
                         TimeTextDefaults.TextSeparator(
@@ -236,7 +238,7 @@
     @Test
     fun omits_separator_with_only_time_on_round_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     textLinearSeparator = {
                         TimeTextDefaults.TextSeparator(
@@ -262,7 +264,7 @@
     @Test
     fun shows_only_start_linear_separator_on_square_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     startLinearContent = {
                         Text(
@@ -295,7 +297,7 @@
     @Test
     fun shows_only_start_curved_separator_on_round_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     startCurvedContent = {
                         curvedComposable {
@@ -329,7 +331,7 @@
     @Test
     fun shows_only_end_linear_separator_on_square_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     endLinearContent = {
                         Text(
@@ -362,7 +364,7 @@
     @Test
     fun shows_only_end_curved_separator_on_round_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     endCurvedContent = {
                         curvedText(
@@ -394,7 +396,7 @@
     @Test
     fun shows_start_and_end_linear_separators_on_square_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     startLinearContent = {
                         Text(
@@ -430,7 +432,7 @@
     @Test
     fun shows_start_and_end_curved_separators_on_round_device() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(true) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(true)) {
                 TimeText(
                     startCurvedContent = {
                         curvedText(
@@ -475,7 +477,7 @@
             fontSize = 20.sp
         )
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(false) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                 TimeText(
                     timeSource = object : TimeSource {
                         override val currentTime: String
@@ -509,7 +511,7 @@
                     caption1 = testTextStyle
                 )
             ) {
-                ConfiguredShapeScreen(false) {
+                DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(false)) {
                     TimeText(
                         timeSource = object : TimeSource {
                             override val currentTime: String
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/VignetteScreenshotTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/VignetteScreenshotTest.kt
index 0f1a3a9..cee74ee 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/VignetteScreenshotTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/VignetteScreenshotTest.kt
@@ -25,6 +25,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.RoundScreen
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -56,7 +58,7 @@
 
     @Test
     fun vignette_circular_top() = verifyScreenshot {
-        ConfiguredShapeScreen(isRound = true) {
+        DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isScreenRound = true)) {
             sampleVignette(
                 VignettePosition.Top,
                 modifier = Modifier.size(screenSize).clip(CircleShape)
@@ -66,7 +68,7 @@
 
     @Test
     fun vignette_circular_bottom() = verifyScreenshot {
-        ConfiguredShapeScreen(isRound = true) {
+        DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isScreenRound = true)) {
             sampleVignette(
                 VignettePosition.Bottom,
                 modifier = Modifier.size(screenSize).clip(CircleShape)
@@ -76,7 +78,7 @@
 
     @Test
     fun vignette_circular_top_and_bottom() = verifyScreenshot {
-        ConfiguredShapeScreen(isRound = true) {
+        DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isScreenRound = true)) {
             sampleVignette(
                 VignettePosition.TopAndBottom,
                 modifier = Modifier.size(screenSize).clip(CircleShape)
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfiguredShapeScreen.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfiguredShapeScreen.kt
deleted file mode 100644
index 84d8046..0000000
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ConfiguredShapeScreen.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.wear.compose.material3
-
-import android.content.res.Configuration
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.platform.LocalConfiguration
-
-/**
- * Helper function which changes [Configuration.isScreenRound] result by changing
- * [Configuration.screenLayout] field and allows to mock square and round behaviour
- * @param isRound mocks shape either to round or not round (square) form
- */
-@Composable
-fun ConfiguredShapeScreen(
-    isRound: Boolean,
-    content: @Composable () -> Unit
-) {
-    val newConfiguration = Configuration(LocalConfiguration.current)
-    newConfiguration.screenLayout = newConfiguration.screenLayout and
-        Configuration.SCREENLAYOUT_ROUND_MASK.inv() or
-        if (isRound) {
-            Configuration.SCREENLAYOUT_ROUND_YES
-        } else {
-            Configuration.SCREENLAYOUT_ROUND_NO
-        }
-
-    CompositionLocalProvider(
-        LocalConfiguration provides newConfiguration,
-        content = content
-    )
-}
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt
index 6b492bb..8e16616 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorScreenshotTest.kt
@@ -19,12 +19,13 @@
 import android.os.Build
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.LayoutDirection
+import androidx.compose.ui.test.RoundScreen
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -89,7 +90,9 @@
         layoutDirection: LayoutDirection
     ) {
         rule.setContentWithTheme {
-            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.LayoutDirection(layoutDirection)
+            ) {
                 defaultHorizontalPageIndicator(isRound)
             }
         }
@@ -102,7 +105,7 @@
 
     private fun between_pages(isRound: Boolean) {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(isRound) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
                 HorizontalPageIndicator(
                     modifier = Modifier
                         .testTag(TEST_TAG)
@@ -123,7 +126,7 @@
 
     @Composable
     private fun defaultHorizontalPageIndicator(isRound: Boolean) {
-        ConfiguredShapeScreen(isRound) {
+        DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
             HorizontalPageIndicator(
                 modifier = Modifier
                     .testTag(TEST_TAG)
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt
index 5418f73..24509d0 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/HorizontalPageIndicatorTest.kt
@@ -23,6 +23,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.DeviceConfigurationOverride
+import androidx.compose.ui.test.RoundScreen
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -38,7 +40,9 @@
     @Test
     public fun supports_testtag_circular() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(isRound = true) {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(isScreenRound = true)
+            ) {
                 HorizontalPageIndicator(
                     modifier = Modifier.testTag(TEST_TAG),
                     pageIndicatorState = pageIndicatorState()
@@ -51,7 +55,9 @@
     @Test
     public fun supports_testtag_linear() {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(isRound = false) {
+            DeviceConfigurationOverride(
+                DeviceConfigurationOverride.RoundScreen(isScreenRound = false)
+            ) {
                 HorizontalPageIndicator(
                     modifier = Modifier.testTag(TEST_TAG),
                     pageIndicatorState = pageIndicatorState()
@@ -83,7 +89,7 @@
 
     private fun position_is_selected(isRound: Boolean) {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(isRound) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
                 HorizontalPageIndicator(
                     modifier = Modifier
                         .testTag(TEST_TAG)
@@ -109,7 +115,7 @@
 
     private fun in_between_positions(isRound: Boolean) {
         rule.setContentWithTheme {
-            ConfiguredShapeScreen(isRound) {
+            DeviceConfigurationOverride(DeviceConfigurationOverride.RoundScreen(isRound)) {
                 HorizontalPageIndicator(
                     modifier = Modifier
                         .testTag(TEST_TAG)
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/SizedArcContainer.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/SizedArcContainer.java
index ff56775..ccdb6db 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/SizedArcContainer.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/SizedArcContainer.java
@@ -244,7 +244,7 @@
         float childSweep = ((ArcLayout.Widget) child).getSweepAngleDegrees();
         float offsetDegrees = (mSweepAngleDegrees - childSweep) / 2;
 
-        int sign = getSignForClockwise(mArcDirection, /* devaultValue= */ 1);
+        int sign = getSignForClockwise(mArcDirection, /* defaultValue= */ 1);
 
         switch (alignment) {
             case LayoutParams.ANGULAR_ALIGNMENT_START:
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
index 3c8571d..fb88bb2 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.StrictMode;
 import android.util.Log;
 
 import androidx.annotation.MainThread;
@@ -684,10 +685,15 @@
     private static void cleanupActiveTilesSharedPref(
             @NonNull SharedPreferences activeTilesSharedPref,
             @NonNull TimeSourceClock timeSourceClock) {
-        for (String key : activeTilesSharedPref.getAll().keySet()) {
-            if (isTileInactive(activeTilesSharedPref.getLong(key, -1L), timeSourceClock)) {
-                activeTilesSharedPref.edit().remove(key).apply();
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        try {
+            for (String key : activeTilesSharedPref.getAll().keySet()) {
+                if (isTileInactive(activeTilesSharedPref.getLong(key, -1L), timeSourceClock)) {
+                    activeTilesSharedPref.edit().remove(key).apply();
+                }
             }
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
         }
     }
 
@@ -732,7 +738,12 @@
     }
 
     private static SharedPreferences getActiveTilesSharedPreferences(@NonNull Context context) {
-        return context.getSharedPreferences(ACTIVE_TILES_SHARED_PREF_NAME, MODE_PRIVATE);
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        try {
+            return context.getSharedPreferences(ACTIVE_TILES_SHARED_PREF_NAME, MODE_PRIVATE);
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
     }
 
     /**