blob: ebc0592d34573d3f48a2b18b7d1ef9b83776fa8d [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.adservices.customaudience;
import android.adservices.common.AdData;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
/**
* Represents the information necessary for a custom audience to participate in ad selection.
*
* <p>A custom audience is an abstract grouping of users with similar demonstrated interests. This
* class is a collection of some data stored on a device that is necessary to serve advertisements
* targeting a single custom audience.
*/
public final class CustomAudience implements Parcelable {
@Nullable
private final String mOwner;
@NonNull
private final String mBuyer;
@NonNull
private final String mName;
@Nullable
private final Instant mActivationTime;
@Nullable
private final Instant mExpirationTime;
@NonNull
private final Uri mDailyUpdateUrl;
@Nullable
private final String mUserBiddingSignals;
@Nullable
private final TrustedBiddingData mTrustedBiddingData;
@NonNull
private final Uri mBiddingLogicUrl;
@NonNull
private final List<AdData> mAds;
@NonNull
public static final Creator<CustomAudience> CREATOR = new Creator<CustomAudience>() {
@Override
public CustomAudience createFromParcel(@NonNull Parcel in) {
Objects.requireNonNull(in);
return new CustomAudience(in);
}
@Override
public CustomAudience[] newArray(int size) {
return new CustomAudience[size];
}
};
private CustomAudience(@NonNull CustomAudience.Builder builder) {
mOwner = builder.mOwner;
mBuyer = builder.mBuyer;
mName = builder.mName;
mActivationTime = builder.mActivationTime;
mExpirationTime = builder.mExpirationTime;
mDailyUpdateUrl = builder.mDailyUpdateUrl;
mUserBiddingSignals = builder.mUserBiddingSignals;
mTrustedBiddingData = builder.mTrustedBiddingData;
mBiddingLogicUrl = builder.mBiddingLogicUrl;
mAds = builder.mAds;
}
private CustomAudience(@NonNull Parcel in) {
Objects.requireNonNull(in);
mOwner = in.readBoolean() ? in.readString() : null;
mBuyer = in.readString();
mName = in.readString();
mActivationTime = in.readBoolean() ? Instant.ofEpochMilli(in.readLong()) : null;
mExpirationTime = in.readBoolean() ? Instant.ofEpochMilli(in.readLong()) : null;
mDailyUpdateUrl = Uri.CREATOR.createFromParcel(in);
mUserBiddingSignals = in.readBoolean() ? in.readString() : null;
mTrustedBiddingData = in.readBoolean()
? TrustedBiddingData.CREATOR.createFromParcel(in) : null;
mBiddingLogicUrl = Uri.CREATOR.createFromParcel(in);
mAds = in.createTypedArrayList(AdData.CREATOR);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
Objects.requireNonNull(dest);
writeNullable(dest, mOwner, () -> dest.writeString(mOwner));
dest.writeString(mBuyer);
dest.writeString(mName);
writeNullable(dest, mActivationTime,
() -> dest.writeLong(mActivationTime.toEpochMilli()));
writeNullable(dest, mExpirationTime,
() -> dest.writeLong(mExpirationTime.toEpochMilli()));
mDailyUpdateUrl.writeToParcel(dest, flags);
writeNullable(dest, mUserBiddingSignals, () -> dest.writeString(mUserBiddingSignals));
writeNullable(dest, mTrustedBiddingData,
() -> mTrustedBiddingData.writeToParcel(dest, flags));
mBiddingLogicUrl.writeToParcel(dest, flags);
dest.writeTypedList(mAds);
}
private static void writeNullable(Parcel parcel, Object o, Runnable howToWrite) {
boolean isFieldPresents = o != null;
parcel.writeBoolean(isFieldPresents);
if (isFieldPresents) {
howToWrite.run();
}
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/**
* Returns a String representing the custom audience's owner application or null to be the
* calling application.
* <p>
* The value format must be &lt;App UID&gt;-&lt;package name&gt;.
*/
@Nullable
public String getOwner() {
return mOwner;
}
/**
* A buyer is identified by a domain in the form "buyerexample.com".
*
* @return a String containing the custom audience's buyer's domain
*/
@NonNull
public String getBuyer() {
return mBuyer;
}
/**
* This name of a custom audience is an opaque string provided by the owner and buyer on
* creation of the {@link CustomAudience} object.
*
* @return the String name of the custom audience
*/
@NonNull
public String getName() {
return mName;
}
/**
* On creation of the {@link CustomAudience} object, an optional activation time may be set in
* the future, in order to serve a delayed activation. If the field is not set, the {@link
* CustomAudience} will be activated at the time of joining.
*
* <p>For example, a custom audience for lapsed users may not activate until a threshold of
* inactivity is reached, at which point the custom audience's ads will participate in the ad
* selection process, potentially redirecting lapsed users to the original owner application.
*
* <p>The maximum delay in activation is 60 days from initial creation.
*
* <p>If specified, the activation time must be an earlier instant than the expiration time.
*
* @return the timestamp, truncated to milliseconds, after which the custom audience is active;
*/
@Nullable
public Instant getActivationTime() {
return mActivationTime;
}
/**
* Once the expiration time has passed, a custom audience is no longer eligible for daily
* ad/bidding data updates or participation in the ad selection process. The custom audience
* will then be deleted from memory by the next daily update.
*
* <p>If no expiration time is provided on creation of the {@link CustomAudience}, expiry will
* default to 60 days from activation.
*
* <p>The maximum expiry is 60 days from initial activation.
*
* @return the timestamp, truncated to milliseconds, after which the custom audience should be
* removed;
*/
@Nullable
public Instant getExpirationTime() {
return mExpirationTime;
}
/**
* This URL points to a buyer-operated server that hosts updated bidding data and ads metadata
* to be used in the on-device ad selection process. The URL must use HTTPS.
*
* @return the custom audience's daily update URL
*/
@NonNull
public Uri getDailyUpdateUrl() {
return mDailyUpdateUrl;
}
/**
* User bidding signals are optionally provided by buyers to be consumed by buyer-provided
* JavaScript during ad selection in an isolated execution environment. These signals should be
* represented as a valid JSON object serialized into a string.
*
* <p>If the user bidding signals are not a valid JSON object that can be consumed by the
* buyer's JS, the custom audience will not be eligible for ad selection.
*
* <p>If not specified, the {@link CustomAudience} will not participate in ad selection until
* user bidding signals are provided via the daily update for the custom audience.
*
* @return a JSON String representing the user bidding signals for the custom audience
*/
@Nullable
public String getUserBiddingSignals() {
return mUserBiddingSignals;
}
/**
* Trusted bidding data consists of a URL pointing to a trusted server for buyers' bidding data
* and a list of keys to query the server with. Note that the keys are arbitrary identifiers
* that will only be used to query the trusted server for a buyer's bidding logic during ad
* selection.
*
* <p>If not specified, the {@link CustomAudience} will not participate in ad selection until
* trusted bidding data are provided via the daily update for the custom audience.
*
* @return a {@link TrustedBiddingData} object containing the custom audience's trusted bidding
* data
*/
@Nullable
public TrustedBiddingData getTrustedBiddingData() {
return mTrustedBiddingData;
}
/**
* Returns the target URL used to fetch bidding logic when a custom audience participates in the
* ad selection process. The URL must use HTTPS.
*/
@NonNull
public Uri getBiddingLogicUrl() {
return mBiddingLogicUrl;
}
/**
* This list of {@link AdData} objects is a full and complete list of the ads served by this
* {@link CustomAudience} during the ad selection process.
*
* <p>If not specified, or if an empty list is provided, the {@link CustomAudience} will not
* participate in ad selection until a valid list of ads are provided via the daily update for
* the custom audience.
*
* @return a {@link List} of {@link AdData} objects representing ads currently served by the
* custom audience
*/
@NonNull
public List<AdData> getAds() {
return mAds;
}
/**
* Checks whether two {@link CustomAudience} objects contain the same information.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CustomAudience)) return false;
CustomAudience that = (CustomAudience) o;
return Objects.equals(mOwner, that.mOwner) && mBuyer.equals(that.mBuyer)
&& mName.equals(that.mName)
&& Objects.equals(mActivationTime, that.mActivationTime)
&& Objects.equals(mExpirationTime, that.mExpirationTime)
&& mDailyUpdateUrl.equals(that.mDailyUpdateUrl)
&& Objects.equals(mUserBiddingSignals, that.mUserBiddingSignals)
&& Objects.equals(mTrustedBiddingData, that.mTrustedBiddingData)
&& mBiddingLogicUrl.equals(that.mBiddingLogicUrl)
&& mAds.equals(that.mAds);
}
/**
* Returns the hash of the {@link CustomAudience} object's data.
*/
@Override
public int hashCode() {
return Objects.hash(mOwner, mBuyer, mName, mActivationTime, mExpirationTime,
mDailyUpdateUrl, mUserBiddingSignals, mTrustedBiddingData, mBiddingLogicUrl, mAds);
}
/** Builder for {@link CustomAudience} objects. */
public static final class Builder {
@Nullable
private String mOwner;
@NonNull
private String mBuyer;
@NonNull
private String mName;
@Nullable
private Instant mActivationTime;
@Nullable
private Instant mExpirationTime;
@NonNull
private Uri mDailyUpdateUrl;
@Nullable
private String mUserBiddingSignals;
@Nullable
private TrustedBiddingData mTrustedBiddingData;
@NonNull
private Uri mBiddingLogicUrl;
@NonNull
private List<AdData> mAds;
// TODO(b/232883403): We may need to add @NonNUll members as args.
public Builder() {
}
/**
* Sets the owner application.
* <p>
* See {@link #getOwner()} for more information.
*
* @param owner &lt;App UID&gt;-&lt;package name&gt; or leave null to default to the calling
* app.
*/
@NonNull
public CustomAudience.Builder setOwner(@Nullable String owner) {
mOwner = owner;
return this;
}
/**
* Sets the buyer domain URL.
* <p>
* See {@link #getBuyer()} for more information.
*/
@NonNull
public CustomAudience.Builder setBuyer(@NonNull String buyer) {
Objects.requireNonNull(buyer);
mBuyer = buyer;
return this;
}
/**
* Sets the {@link CustomAudience} object's name.
* <p>
* See {@link #getName()} for more information.
*/
@NonNull
public CustomAudience.Builder setName(@NonNull String name) {
Objects.requireNonNull(name);
mName = name;
return this;
}
/**
* Sets the time, truncated to milliseconds, after which the {@link CustomAudience} will
* serve ads.
*
* <p>Set to {@code null} in order for this {@link CustomAudience} to be immediately active
* and participate in ad selection.
*
* <p>See {@link #getActivationTime()} for more information.
*/
@NonNull
public CustomAudience.Builder setActivationTime(@Nullable Instant activationTime) {
mActivationTime = activationTime;
return this;
}
/**
* Sets the time, truncated to milliseconds, after which the {@link CustomAudience} should
* be removed.
* <p>
* See {@link #getExpirationTime()} for more information.
*/
@NonNull
public CustomAudience.Builder setExpirationTime(@Nullable Instant expirationTime) {
mExpirationTime = expirationTime;
return this;
}
/**
* Sets the daily update URL. The URL must use HTTPS.
* <p>
* See {@link #getDailyUpdateUrl()} for more information.
*/
@NonNull
public CustomAudience.Builder setDailyUpdateUrl(@NonNull Uri dailyUpdateUrl) {
Objects.requireNonNull(dailyUpdateUrl);
mDailyUpdateUrl = dailyUpdateUrl;
return this;
}
/**
* Sets the user bidding signals used in the ad selection process.
* <p>
* See {@link #getUserBiddingSignals()} for more information.
*/
@NonNull
public CustomAudience.Builder setUserBiddingSignals(@Nullable String userBiddingSignals) {
mUserBiddingSignals = userBiddingSignals;
return this;
}
/**
* Sets the trusted bidding data to be queried and used in the ad selection process.
* <p>
* See {@link #getTrustedBiddingData()} for more information.
*/
@NonNull
public CustomAudience.Builder setTrustedBiddingData(
@Nullable TrustedBiddingData trustedBiddingData) {
mTrustedBiddingData = trustedBiddingData;
return this;
}
/**
* Sets the URL to fetch bidding logic from for use in the ad selection process. The URL
* must use HTTPS.
*
* <p>See {@link #getBiddingLogicUrl()} ()} for more information.
*/
@NonNull
public CustomAudience.Builder setBiddingLogicUrl(@NonNull Uri biddingLogicUrl) {
Objects.requireNonNull(biddingLogicUrl);
mBiddingLogicUrl = biddingLogicUrl;
return this;
}
/**
* Sets the initial remarketing ads served by the custom audience. Will be assigned with an
* empty list if not provided.
*
* <p>See {@link #getAds()} for more information.
*/
@NonNull
public CustomAudience.Builder setAds(@Nullable List<AdData> ads) {
mAds = ads;
return this;
}
/**
* Builds an instance of a {@link CustomAudience}.
*
* @throws NullPointerException if any non-null parameter is null
* @throws IllegalArgumentException if the expiration time occurs before activation time
* @throws IllegalArgumentException if the expiration time is set before the current time
*/
@NonNull
public CustomAudience build() {
Objects.requireNonNull(mBuyer);
Objects.requireNonNull(mName);
Objects.requireNonNull(mDailyUpdateUrl);
Objects.requireNonNull(mBiddingLogicUrl);
// To pass the API lint, we should not allow null Collection.
if (mAds == null) {
mAds = List.of();
}
return new CustomAudience(this);
}
}
}