android / privacy-sandbox-samples

Apache License 2.0
130 stars 52 forks source link

FetchAndJoinCustomAudienceRequest - Unable to set ActivationTime and ExpirationTime via JSON payload #81

Closed davidae closed 11 months ago

davidae commented 11 months ago

As it's started in the design doc,

The CustomAudienceFetchRequest object allows the API caller to define some information for the Custom Audience by using the optional properties shown in the example above. If specified these values cannot be overwritten by the buyer response received by the buyer.

By look at and experimenting with the API, I assume these are the optional fields,

When trying this out locally, I send a full CA JSON as a reply even if some of the Setters in the builder are called, such as

{
    "name": "ca_397",
    "activation_time": 1680603133,
    "expiration_time": 1680803133,
    "ads": [
        {
            "metadata": {"valid": 1},
            "render_uri": "https://example.com/fetch_and_join_ad1"
        },
        {
            "metadata": {"valid": 2},
            "render_uri": "https://example.com/fetch_and_join_ad2"
        }
    ],
    "bidding_logic_uri": "https://example.com/b/ca_397",
    "daily_update_uri": "https://example.com/du",
    "trusted_bidding_data": {
        "trusted_bidding_keys": ["key1","key2"],
        "trusted_bidding_uri": "https://example.com/t"
    },
    "user_bidding_signals": {
        "arbitrary": "yes",
        "valid": true
    }
}

the JSON payload successfully sets and creates the CA if I specify this setup with the request builder

val request = FetchAndJoinCustomAudienceRequest.Builder(fetchURI)
    //.setName(name)
    .setActivationTime(activationTime)
    .setExpirationTime(expirationTime)
    //.setUserBiddingSignals(AdSelectionSignals.EMPTY)
    .setFetchUri(fetchURI)
    .build()

Here I assume the activation_time and expiration_time in the JSON payload is simply ignored and it uses whatever is defined in the local variables activationTime and expirationTime. If there isn't a setter defined for a field (Name & UserBiddingSignals), they are successfully set to whatever is in the JSON.

But if I choose to also set the ActivationTime and ExpirationTime with what is defined in the JSON payload (int64 UNIX timestamp), it fails, so

val request = FetchAndJoinCustomAudienceRequest.Builder(fetchURI)
    //.setName(name)
    //.setActivationTime(activationTime)
    //.setExpirationTime(expirationTime)
    //.setUserBiddingSignals(AdSelectionSignals.EMPTY)
    .setFetchUri(fetchURI)
    .build()

gives the error

  E  java.lang.IllegalArgumentException
    at android.adservices.common.AdServicesStatusUtils.asException(AdServicesStatusUtils.java:179)
    at android.adservices.common.AdServicesStatusUtils.asException(AdServicesStatusUtils.java:210)
    at android.adservices.customaudience.CustomAudienceManager$2.lambda$onFailure$1(CustomAudienceManager.java:255)
    at android.adservices.customaudience.CustomAudienceManager$2$$ExternalSyntheticLambda1.run(Unknown Source:4)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
    at java.lang.Thread.run(Thread.java:1012)
cshmerling commented 11 months ago

Some additional context about the API:

If you set the activationTime and expirationTime for a custom audience when creating the request object, those fields cannot be overwritten. However, if they are not set you should be able to set them via the response from the server.

Based on the order of operations in your post, the issue may be that you have created the custom audience, and then trying to fetch an audience of the same name. Is that the case?

In other words, if you try to join a custom audience with a different name and only set the activation and expiration times via the JSON, do you get the same exception?

maciejkowalczyk commented 11 months ago

@cshmerling It seems the unhelpful error occurs if either activationTime or expirationTime is unset in FetchAndJoinCustomAudienceRequest no matter if name is set or not.

davidae commented 11 months ago

In other words, if you try to join a custom audience with a different name and only set the activation and expiration times via the JSON, do you get the same exception?

@cshmerling, no, it fails each time on a fresh emulator device with the JSON defined above and with the following build

val request = FetchAndJoinCustomAudienceRequest.Builder(fetchURI)
    .setFetchUri(fetchURI)
    .build()

Just to note, the JSON payload uses an another UNIX timestamp to avoid being in the past (not 1680803133), e.g.

   "activation_time": "1695634961",
   "expiration_time": "1695638561"

And I can confirm the use-case mentioned by @maciejkowalczyk too, it's activationTime and/or expirationTime.

cshmerling commented 11 months ago

Ah, one thing I overlooked- the units for activation and expiration should be milliseconds, and it looks like you are providing seconds

davidae commented 11 months ago

Understood, I can confirm that it works with milis epoch. I would recommend to update the JSON payload in the example under Join a custom audience, the JSON uses epoch seconds for both activation_time and expiration_time, or the error response from the API.

guybashan commented 11 months ago

Hi @davidae, I am having the same issue and I keep getting this exception:

java.lang.IllegalStateException
                                                                                                        at android.adservices.common.AdServicesStatusUtils.asException(AdServicesStatusUtils.java:203)
                                                                                                        at android.adservices.common.AdServicesStatusUtils.asException(AdServicesStatusUtils.java:210)
                                                                                                        at android.adservices.customaudience.CustomAudienceManager$2.lambda$onFailure$1(CustomAudienceManager.java:255)
                                                                                                        at android.adservices.customaudience.CustomAudienceManager$2$$ExternalSyntheticLambda1.run(Unknown Source:4)
                                                                                                        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
                                                                                                        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
                                                                                                        at java.lang.Thread.run(Thread.java:1012)

Not sure how exactly you got over it. I am using this code:

final Instant activationTime = Instant.now();
final Instant expirationTime = activationTime.plus(Duration.ofDays(30));
final String name = "shoes";

FetchAndJoinCustomAudienceRequest request =
                            new FetchAndJoinCustomAudienceRequest.Builder(
                                    Uri.parse(mockServer + "/audience")
                            )
                                    .setName(name)
                                    .setFetchUri(Uri.parse(mockServer + "/audience"))
                                    .setActivationTime(activationTime)
                                    .setExpirationTime(expirationTime)
                                   // .setUserBiddingSignals(adSelectionSignals)
                                    .build();

Please note: I do not see any request to my mock server, so it seems nothing is actually sent outside from the emulator.