blob: cb7088615bd263a174a78bbb5066b184f973197b [file] [log] [blame]
/*
* 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 android.adservices.common;
import android.adservices.adselection.ReportImpressionRequest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.adservices.AdServicesParcelableUtil;
import com.android.internal.annotations.VisibleForTesting;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
/**
* A container for the ad filters that are based on frequency caps.
*
* <p>Frequency caps filters combine an event type with a set of {@link KeyedFrequencyCap} objects
* to define a set 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.
*
* @hide
*/
// TODO(b/221876775): Unhide for frequency cap API review
public final class FrequencyCapFilters implements Parcelable {
/**
* Event types which are used to update ad counter histograms, which inform frequency cap
* filtering in FLEDGE.
*
* @hide
*/
@IntDef(
prefix = {"AD_EVENT_TYPE_"},
value = {
AD_EVENT_TYPE_INVALID,
AD_EVENT_TYPE_WIN,
AD_EVENT_TYPE_IMPRESSION,
AD_EVENT_TYPE_VIEW,
AD_EVENT_TYPE_CLICK
})
@Retention(RetentionPolicy.SOURCE)
public @interface AdEventType {}
/** @hide */
public static final int AD_EVENT_TYPE_INVALID = -1;
/**
* The WIN ad event type is automatically populated within the FLEDGE service for any winning ad
* which is returned from FLEDGE ad selection.
*
* <p>It should not be used to manually update an ad counter histogram.
*/
public static final int AD_EVENT_TYPE_WIN = 0;
public static final int AD_EVENT_TYPE_IMPRESSION = 1;
public static final int AD_EVENT_TYPE_VIEW = 2;
public static final int AD_EVENT_TYPE_CLICK = 3;
/** @hide */
@VisibleForTesting public static final String WIN_EVENTS_FIELD_NAME = "win";
/** @hide */
@VisibleForTesting public static final String IMPRESSION_EVENTS_FIELD_NAME = "impression";
/** @hide */
@VisibleForTesting public static final String VIEW_EVENTS_FIELD_NAME = "view";
/** @hide */
@VisibleForTesting public static final String CLICK_EVENTS_FIELD_NAME = "click";
@NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents;
@NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents;
@NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents;
@NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents;
@NonNull
public static final Creator<FrequencyCapFilters> CREATOR =
new Creator<FrequencyCapFilters>() {
@Override
public FrequencyCapFilters createFromParcel(@NonNull Parcel in) {
Objects.requireNonNull(in);
return new FrequencyCapFilters(in);
}
@Override
public FrequencyCapFilters[] newArray(int size) {
return new FrequencyCapFilters[size];
}
};
private FrequencyCapFilters(@NonNull Builder builder) {
Objects.requireNonNull(builder);
mKeyedFrequencyCapsForWinEvents = builder.mKeyedFrequencyCapsForWinEvents;
mKeyedFrequencyCapsForImpressionEvents = builder.mKeyedFrequencyCapsForImpressionEvents;
mKeyedFrequencyCapsForViewEvents = builder.mKeyedFrequencyCapsForViewEvents;
mKeyedFrequencyCapsForClickEvents = builder.mKeyedFrequencyCapsForClickEvents;
}
private FrequencyCapFilters(@NonNull Parcel in) {
Objects.requireNonNull(in);
mKeyedFrequencyCapsForWinEvents =
AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR);
mKeyedFrequencyCapsForImpressionEvents =
AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR);
mKeyedFrequencyCapsForViewEvents =
AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR);
mKeyedFrequencyCapsForClickEvents =
AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR);
}
/**
* Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_WIN} event type.
*
* <p>These frequency caps apply to events for ads that were selected as winners in ad
* selection. Winning ads are used to automatically increment the associated counter keys on the
* win event type.
*/
@NonNull
public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents() {
return mKeyedFrequencyCapsForWinEvents;
}
/**
* Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_IMPRESSION} event type.
*
* <p>These frequency caps apply to events which correlate to an impression as interpreted by an
* adtech. Note that events are not automatically counted when calling {@link
* android.adservices.adselection.AdSelectionManager#reportImpression(ReportImpressionRequest,
* Executor, OutcomeReceiver)}.
*/
@NonNull
public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents() {
return mKeyedFrequencyCapsForImpressionEvents;
}
/**
* Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_VIEW} event type.
*
* <p>These frequency caps apply to events which correlate to a view as interpreted by an
* adtech.
*/
@NonNull
public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents() {
return mKeyedFrequencyCapsForViewEvents;
}
/**
* Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_CLICK} event type.
*
* <p>These frequency caps apply to events which correlate to a click as interpreted by an
* adtech.
*/
@NonNull
public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents() {
return mKeyedFrequencyCapsForClickEvents;
}
/**
* @return The estimated size of this object, in bytes.
* @hide
*/
public int getSizeInBytes() {
return getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForWinEvents)
+ getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForImpressionEvents)
+ getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForViewEvents)
+ getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForClickEvents);
}
private int getSizeInBytesOfFcapSet(Set<KeyedFrequencyCap> fcaps) {
int toReturn = 0;
for (final KeyedFrequencyCap fcap : fcaps) {
toReturn += fcap.getSizeInBytes();
}
return toReturn;
}
/**
* A JSON serializer.
*
* @return A JSON serialization of this object.
* @hide
*/
public JSONObject toJson() throws JSONException {
JSONObject toReturn = new JSONObject();
toReturn.put(WIN_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForWinEvents));
toReturn.put(
IMPRESSION_EVENTS_FIELD_NAME,
fcapSetToJsonArray(mKeyedFrequencyCapsForImpressionEvents));
toReturn.put(VIEW_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForViewEvents));
toReturn.put(
CLICK_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForClickEvents));
return toReturn;
}
private static JSONArray fcapSetToJsonArray(Set<KeyedFrequencyCap> fcapSet)
throws JSONException {
JSONArray toReturn = new JSONArray();
for (KeyedFrequencyCap fcap : fcapSet) {
toReturn.put(fcap.toJson());
}
return toReturn;
}
/**
* A JSON de-serializer.
*
* @param json A JSON representation of an {@link FrequencyCapFilters} object as would be
* generated by {@link #toJson()}.
* @return An {@link FrequencyCapFilters} object generated from the given JSON.
* @hide
*/
public static FrequencyCapFilters fromJson(JSONObject json) throws JSONException {
Builder builder = new Builder();
if (json.has(WIN_EVENTS_FIELD_NAME)) {
builder.setKeyedFrequencyCapsForWinEvents(
jsonArrayToFcapSet(json.getJSONArray(WIN_EVENTS_FIELD_NAME)));
}
if (json.has(IMPRESSION_EVENTS_FIELD_NAME)) {
builder.setKeyedFrequencyCapsForImpressionEvents(
jsonArrayToFcapSet(json.getJSONArray(IMPRESSION_EVENTS_FIELD_NAME)));
}
if (json.has(VIEW_EVENTS_FIELD_NAME)) {
builder.setKeyedFrequencyCapsForViewEvents(
jsonArrayToFcapSet(json.getJSONArray(VIEW_EVENTS_FIELD_NAME)));
}
if (json.has(CLICK_EVENTS_FIELD_NAME)) {
builder.setKeyedFrequencyCapsForClickEvents(
jsonArrayToFcapSet(json.getJSONArray(CLICK_EVENTS_FIELD_NAME)));
}
return builder.build();
}
private static Set<KeyedFrequencyCap> jsonArrayToFcapSet(JSONArray json) throws JSONException {
Set<KeyedFrequencyCap> toReturn = new HashSet<>();
for (int i = 0; i < json.length(); i++) {
toReturn.add(KeyedFrequencyCap.fromJson(json.getJSONObject(i)));
}
return toReturn;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
Objects.requireNonNull(dest);
AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForWinEvents);
AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForImpressionEvents);
AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForViewEvents);
AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForClickEvents);
}
/** @hide */
@Override
public int describeContents() {
return 0;
}
/** Checks whether the {@link FrequencyCapFilters} objects contain the same information. */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FrequencyCapFilters)) return false;
FrequencyCapFilters that = (FrequencyCapFilters) o;
return mKeyedFrequencyCapsForWinEvents.equals(that.mKeyedFrequencyCapsForWinEvents)
&& mKeyedFrequencyCapsForImpressionEvents.equals(
that.mKeyedFrequencyCapsForImpressionEvents)
&& mKeyedFrequencyCapsForViewEvents.equals(that.mKeyedFrequencyCapsForViewEvents)
&& mKeyedFrequencyCapsForClickEvents.equals(that.mKeyedFrequencyCapsForClickEvents);
}
/** Returns the hash of the {@link FrequencyCapFilters} object's data. */
@Override
public int hashCode() {
return Objects.hash(
mKeyedFrequencyCapsForWinEvents,
mKeyedFrequencyCapsForImpressionEvents,
mKeyedFrequencyCapsForViewEvents,
mKeyedFrequencyCapsForClickEvents);
}
@Override
public String toString() {
return "FrequencyCapFilters{"
+ "mKeyedFrequencyCapsForWinEvents="
+ mKeyedFrequencyCapsForWinEvents
+ ", mKeyedFrequencyCapsForImpressionEvents="
+ mKeyedFrequencyCapsForImpressionEvents
+ ", mKeyedFrequencyCapsForViewEvents="
+ mKeyedFrequencyCapsForViewEvents
+ ", mKeyedFrequencyCapsForClickEvents="
+ mKeyedFrequencyCapsForClickEvents
+ '}';
}
/** Builder for creating {@link FrequencyCapFilters} objects. */
public static final class Builder {
@NonNull private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents = new HashSet<>();
@NonNull
private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents = new HashSet<>();
@NonNull private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents = new HashSet<>();
@NonNull private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents = new HashSet<>();
public Builder() {}
/**
* Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_WIN} event type.
*
* <p>See {@link #getKeyedFrequencyCapsForWinEvents()} for more information.
*/
@NonNull
public Builder setKeyedFrequencyCapsForWinEvents(
@NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForWinEvents) {
Objects.requireNonNull(keyedFrequencyCapsForWinEvents);
mKeyedFrequencyCapsForWinEvents = keyedFrequencyCapsForWinEvents;
return this;
}
/**
* Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_IMPRESSION} event type.
*
* <p>See {@link #getKeyedFrequencyCapsForImpressionEvents()} for more information.
*/
@NonNull
public Builder setKeyedFrequencyCapsForImpressionEvents(
@NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents) {
Objects.requireNonNull(keyedFrequencyCapsForImpressionEvents);
mKeyedFrequencyCapsForImpressionEvents = keyedFrequencyCapsForImpressionEvents;
return this;
}
/**
* Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_VIEW} event type.
*
* <p>See {@link #getKeyedFrequencyCapsForViewEvents()} for more information.
*/
@NonNull
public Builder setKeyedFrequencyCapsForViewEvents(
@NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForViewEvents) {
Objects.requireNonNull(keyedFrequencyCapsForViewEvents);
mKeyedFrequencyCapsForViewEvents = keyedFrequencyCapsForViewEvents;
return this;
}
/**
* Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link
* #AD_EVENT_TYPE_CLICK} event type.
*
* <p>See {@link #getKeyedFrequencyCapsForClickEvents()} for more information.
*/
@NonNull
public Builder setKeyedFrequencyCapsForClickEvents(
@NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForClickEvents) {
Objects.requireNonNull(keyedFrequencyCapsForClickEvents);
mKeyedFrequencyCapsForClickEvents = keyedFrequencyCapsForClickEvents;
return this;
}
/** Builds and returns a {@link FrequencyCapFilters} instance. */
@NonNull
public FrequencyCapFilters build() {
return new FrequencyCapFilters(this);
}
}
}