CustomAudience.kt

/*
 * 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.customaudience

import android.net.Uri
import androidx.privacysandbox.ads.adservices.common.AdData
import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
import java.time.Instant

/**
 * Represents the information necessary for a custom audience to participate in ad selection.
 *
 * 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.
 *
 * @param buyer A buyer is identified by a domain in the form "buyerexample.com".
 * @param name The custom audience's name is an arbitrary string provided by the owner and buyer on
 * creation of the [CustomAudience] object.
 * @param dailyUpdateUri a URI that 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 URI must use HTTPS.
 * @param biddingLogicUri the target URI used to fetch bidding logic when a custom audience
 * participates in the ad selection process. The URI must use HTTPS.
 * @param ads the list of [AdData] objects is a full and complete list of the ads that will be
 * served by this [CustomAudience] during the ad selection process.
 * @param activationTime optional activation time may be set in the future, in order to serve a
 * delayed activation. If the field is not set, the object will be activated at the time of joining.
 * @param expirationTime optional expiration time. Once it 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.
 * @param userBiddingSignals optional User bidding signals, provided by buyers to be consumed by
 * buyer-provided JavaScript during ad selection in an isolated execution environment.
 * @param trustedBiddingSignals optional trusted bidding data, consists of a URI pointing to a
 * trusted server for buyers' bidding data and a list of keys to query the server with.
 */
class CustomAudience public constructor(
    val buyer: AdTechIdentifier,
    val name: String,
    val dailyUpdateUri: Uri,
    val biddingLogicUri: Uri,
    val ads: List<AdData>,
    val activationTime: Instant? = null,
    val expirationTime: Instant? = null,
    val userBiddingSignals: AdSelectionSignals? = null,
    val trustedBiddingSignals: TrustedBiddingData? = null
) {

    /**
     * Checks whether two [CustomAudience] objects contain the same information.
     */
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is CustomAudience) return false
        return this.buyer == other.buyer &&
            this.name == other.name &&
            this.activationTime == other.activationTime &&
            this.expirationTime == other.expirationTime &&
            this.dailyUpdateUri == other.dailyUpdateUri &&
            this.userBiddingSignals == other.userBiddingSignals &&
            this.trustedBiddingSignals == other.trustedBiddingSignals &&
            this.ads == other.ads
    }

    /**
     * Returns the hash of the [CustomAudience] object's data.
     */
    override fun hashCode(): Int {
        var hash = buyer.hashCode()
        hash = 31 * hash + name.hashCode()
        hash = 31 * hash + activationTime.hashCode()
        hash = 31 * hash + expirationTime.hashCode()
        hash = 31 * hash + dailyUpdateUri.hashCode()
        hash = 31 * hash + userBiddingSignals.hashCode()
        hash = 31 * hash + trustedBiddingSignals.hashCode()
        hash = 31 * hash + biddingLogicUri.hashCode()
        hash = 31 * hash + ads.hashCode()
        return hash
    }

    override fun toString(): String {
        return "CustomAudience: " +
            "buyer=$biddingLogicUri, activationTime=$activationTime, " +
            "expirationTime=$expirationTime, dailyUpdateUri=$dailyUpdateUri, " +
            "userBiddingSignals=$userBiddingSignals, " +
            "trustedBiddingSignals=$trustedBiddingSignals, " +
            "biddingLogicUri=$biddingLogicUri, ads=$ads"
    }

    /** Builder for [CustomAudience] objects. */
    @SuppressWarnings("OptionalBuilderConstructorArgument")
    public class Builder(
        private var buyer: AdTechIdentifier,
        private var name: String,
        private var dailyUpdateUri: Uri,
        private var biddingLogicUri: Uri,
        private var ads: List<AdData>
    ) {
        private var activationTime: Instant? = null
        private var expirationTime: Instant? = null
        private var userBiddingSignals: AdSelectionSignals? = null
        private var trustedBiddingData: TrustedBiddingData? = null

        /**
         * Sets the buyer [AdTechIdentifier].
         *
         * @param buyer A buyer is identified by a domain in the form "buyerexample.com".
         */
        fun setBuyer(buyer: AdTechIdentifier): Builder = apply {
            this.buyer = buyer
        }

        /**
         * Sets the [CustomAudience] object's name.
         *
         * @param name  The custom audience's name is an arbitrary string provided by the owner and
         * buyer on creation of the [CustomAudience] object.
         */
        fun setName(name: String): Builder = apply {
            this.name = name
        }

        /**
         * On creation of the [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
         * [CustomAudience] will be activated at the time of joining.
         *
         * 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.
         *
         * The maximum delay in activation is 60 days from initial creation.
         *
         * If specified, the activation time must be an earlier instant than the expiration time.
         *
         * @param activationTime activation time, truncated to milliseconds, after which the
         * [CustomAudience] will serve ads.
         */
        fun setActivationTime(activationTime: Instant): Builder = apply {
            this.activationTime = activationTime
        }

        /**
         * 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.
         *
         * If no expiration time is provided on creation of the [CustomAudience], expiry will
         * default to 60 days from activation.
         *
         * The maximum expiry is 60 days from initial activation.
         *
         * @param expirationTime the timestamp [Instant], truncated to milliseconds, after
         * which the custom audience should be removed.
         */
        fun setExpirationTime(expirationTime: Instant): Builder = apply {
            this.expirationTime = expirationTime
        }

        /**
         * This URI 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 URI must use HTTPS.
         *
         * @param dailyUpdateUri the custom audience's daily update URI
         */
        fun setDailyUpdateUri(dailyUpdateUri: Uri): Builder = apply {
            this.dailyUpdateUri = dailyUpdateUri
        }

        /**
         * User bidding signals are optionally provided by buyers to be consumed by buyer-provided
         * JavaScript during ad selection in an isolated execution environment.
         *
         * 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.
         *
         * If not specified, the [CustomAudience] will not participate in ad selection
         * until user bidding signals are provided via the daily update for the custom audience.
         *
         * @param userBiddingSignals an [AdSelectionSignals] object representing the user
         * bidding signals for the custom audience
         */
        fun setUserBiddingSignals(userBiddingSignals: AdSelectionSignals): Builder = apply {
            this.userBiddingSignals = userBiddingSignals
        }

        /**
         * Trusted bidding data consists of a URI 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.
         *
         * If not specified, the [CustomAudience] will not participate in ad selection
         * until trusted bidding data are provided via the daily update for the custom audience.
         *
         * @param trustedBiddingSignals a [TrustedBiddingData] object containing the custom
         * audience's trusted bidding data.
         */
        @SuppressWarnings("MissingGetterMatchingBuilder")
        fun setTrustedBiddingData(trustedBiddingSignals: TrustedBiddingData): Builder = apply {
            this.trustedBiddingData = trustedBiddingSignals
        }

        /**
         * Returns the target URI used to fetch bidding logic when a custom audience participates in the
         * ad selection process. The URI must use HTTPS.
         *
         * @param biddingLogicUri the URI for fetching buyer bidding logic
         */
        fun setBiddingLogicUri(biddingLogicUri: Uri): Builder = apply {
            this.biddingLogicUri = biddingLogicUri
        }

        /**
         * This list of [AdData] objects is a full and complete list of the ads that will be
         * served by this [CustomAudience] during the ad selection process.
         *
         * If not specified, or if an empty list is provided, the [CustomAudience] will not
         * participate in ad selection until a valid list of ads are provided via the daily update
         * for the custom audience.
         *
         * @param ads a [List] of [AdData] objects representing ads currently served by
         * the custom audience.
         */
        fun setAds(ads: List<AdData>): Builder = apply {
            this.ads = ads
        }

        /**
         * Builds an instance of a [CustomAudience].
         */
        fun build(): CustomAudience {
            return CustomAudience(
                buyer,
                name,
                dailyUpdateUri,
                biddingLogicUri,
                ads,
                activationTime,
                expirationTime,
                userBiddingSignals,
                trustedBiddingData
            )
        }
    }
}