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);
+ }
}
/**