1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.adservices.common; 18 19 import android.adservices.adselection.ReportImpressionRequest; 20 import android.adservices.adselection.UpdateAdCounterHistogramRequest; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.os.OutcomeReceiver; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.internal.util.Preconditions; 29 30 import org.json.JSONArray; 31 import org.json.JSONException; 32 import org.json.JSONObject; 33 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Objects; 39 import java.util.concurrent.Executor; 40 41 /** 42 * A container for the ad filters that are based on frequency caps. 43 * 44 * <p>No more than 20 frequency cap filters may be associated with a single ad. 45 * 46 * <p>Frequency caps filters combine an event type with a list of {@link KeyedFrequencyCap} objects 47 * to define a collection of ad filters. If any of these frequency caps are exceeded for a given ad, 48 * the ad will be removed from the group of ads submitted to a buyer adtech's bidding function. 49 */ 50 public final class FrequencyCapFilters implements Parcelable { 51 /** @hide */ 52 public static final String NUM_FREQUENCY_CAP_FILTERS_EXCEEDED_FORMAT = 53 "FrequencyCapFilters should have no more than %d filters"; 54 /** @hide */ 55 public static final int MAX_NUM_FREQUENCY_CAP_FILTERS = 20; 56 /** @hide */ 57 public static final String FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE = 58 "FrequencyCapFilters should not set null list of KeyedFrequencyCaps"; 59 /** @hide */ 60 public static final String FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE = 61 "FrequencyCapFilters should not contain null KeyedFrequencyCaps"; 62 63 /** 64 * Event types which are used to update ad counter histograms, which inform frequency cap 65 * filtering in Protected Audience. 66 * 67 * @hide 68 */ 69 @IntDef( 70 prefix = {"AD_EVENT_TYPE_"}, 71 value = { 72 AD_EVENT_TYPE_INVALID, 73 AD_EVENT_TYPE_WIN, 74 AD_EVENT_TYPE_IMPRESSION, 75 AD_EVENT_TYPE_VIEW, 76 AD_EVENT_TYPE_CLICK, 77 AD_EVENT_TYPE_MIN, 78 AD_EVENT_TYPE_MAX 79 }) 80 @Retention(RetentionPolicy.SOURCE) 81 public @interface AdEventType {} 82 83 /** @hide */ 84 public static final int AD_EVENT_TYPE_INVALID = -1; 85 86 /** 87 * The WIN ad event type is automatically populated within the Protected Audience service for 88 * any winning ad which is returned from Protected Audience ad selection. 89 * 90 * <p>It should not be used to manually update an ad counter histogram. 91 */ 92 public static final int AD_EVENT_TYPE_WIN = 0; 93 94 public static final int AD_EVENT_TYPE_IMPRESSION = 1; 95 public static final int AD_EVENT_TYPE_VIEW = 2; 96 public static final int AD_EVENT_TYPE_CLICK = 3; 97 98 /** @hide */ 99 public static final int AD_EVENT_TYPE_MIN = AD_EVENT_TYPE_WIN; 100 /** @hide */ 101 public static final int AD_EVENT_TYPE_MAX = AD_EVENT_TYPE_CLICK; 102 103 /** @hide */ 104 @VisibleForTesting public static final String WIN_EVENTS_FIELD_NAME = "win"; 105 /** @hide */ 106 @VisibleForTesting public static final String IMPRESSION_EVENTS_FIELD_NAME = "impression"; 107 /** @hide */ 108 @VisibleForTesting public static final String VIEW_EVENTS_FIELD_NAME = "view"; 109 /** @hide */ 110 @VisibleForTesting public static final String CLICK_EVENTS_FIELD_NAME = "click"; 111 112 @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents; 113 @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents; 114 @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents; 115 @NonNull private final List<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents; 116 117 @NonNull 118 public static final Creator<FrequencyCapFilters> CREATOR = 119 new Creator<FrequencyCapFilters>() { 120 @Override 121 public FrequencyCapFilters createFromParcel(@NonNull Parcel in) { 122 Objects.requireNonNull(in); 123 return new FrequencyCapFilters(in); 124 } 125 126 @Override 127 public FrequencyCapFilters[] newArray(int size) { 128 return new FrequencyCapFilters[size]; 129 } 130 }; 131 FrequencyCapFilters(@onNull Builder builder)132 private FrequencyCapFilters(@NonNull Builder builder) { 133 Objects.requireNonNull(builder); 134 135 mKeyedFrequencyCapsForWinEvents = builder.mKeyedFrequencyCapsForWinEvents; 136 mKeyedFrequencyCapsForImpressionEvents = builder.mKeyedFrequencyCapsForImpressionEvents; 137 mKeyedFrequencyCapsForViewEvents = builder.mKeyedFrequencyCapsForViewEvents; 138 mKeyedFrequencyCapsForClickEvents = builder.mKeyedFrequencyCapsForClickEvents; 139 } 140 FrequencyCapFilters(@onNull Parcel in)141 private FrequencyCapFilters(@NonNull Parcel in) { 142 Objects.requireNonNull(in); 143 144 mKeyedFrequencyCapsForWinEvents = new ArrayList<>(); 145 mKeyedFrequencyCapsForImpressionEvents = new ArrayList<>(); 146 mKeyedFrequencyCapsForViewEvents = new ArrayList<>(); 147 mKeyedFrequencyCapsForClickEvents = new ArrayList<>(); 148 149 in.readTypedList(mKeyedFrequencyCapsForWinEvents, KeyedFrequencyCap.CREATOR); 150 in.readTypedList(mKeyedFrequencyCapsForImpressionEvents, KeyedFrequencyCap.CREATOR); 151 in.readTypedList(mKeyedFrequencyCapsForViewEvents, KeyedFrequencyCap.CREATOR); 152 in.readTypedList(mKeyedFrequencyCapsForClickEvents, KeyedFrequencyCap.CREATOR); 153 } 154 155 /** 156 * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 157 * #AD_EVENT_TYPE_WIN} event type. 158 * 159 * <p>These frequency caps apply to events for ads that were selected as winners in ad 160 * selection. Winning ads are used to automatically increment the associated counter keys on the 161 * win event type. 162 * 163 * <p>Note that the {@link #AD_EVENT_TYPE_WIN} event type cannot be updated manually using the 164 * {@link android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram( 165 * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API. 166 */ 167 @NonNull getKeyedFrequencyCapsForWinEvents()168 public List<KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents() { 169 return mKeyedFrequencyCapsForWinEvents; 170 } 171 172 /** 173 * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 174 * #AD_EVENT_TYPE_IMPRESSION} event type. 175 * 176 * <p>These frequency caps apply to events which correlate to an impression as interpreted by an 177 * adtech. 178 * 179 * <p>Note that events are not automatically counted when calling {@link 180 * android.adservices.adselection.AdSelectionManager#reportImpression(ReportImpressionRequest, 181 * Executor, OutcomeReceiver)}. Instead, the {@link #AD_EVENT_TYPE_IMPRESSION} event type must 182 * be updated using the {@link 183 * android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram( 184 * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API. 185 */ 186 @NonNull getKeyedFrequencyCapsForImpressionEvents()187 public List<KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents() { 188 return mKeyedFrequencyCapsForImpressionEvents; 189 } 190 191 /** 192 * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 193 * #AD_EVENT_TYPE_VIEW} event type. 194 * 195 * <p>These frequency caps apply to events which correlate to a view as interpreted by an 196 * adtech. View events are counted when the {@link 197 * android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram( 198 * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API is invoked with the {@link 199 * #AD_EVENT_TYPE_VIEW} event type. 200 */ 201 @NonNull getKeyedFrequencyCapsForViewEvents()202 public List<KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents() { 203 return mKeyedFrequencyCapsForViewEvents; 204 } 205 206 /** 207 * Gets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 208 * #AD_EVENT_TYPE_CLICK} event type. 209 * 210 * <p>These frequency caps apply to events which correlate to a click as interpreted by an 211 * adtech. Click events are counted when the {@link 212 * android.adservices.adselection.AdSelectionManager#updateAdCounterHistogram( 213 * UpdateAdCounterHistogramRequest, Executor, OutcomeReceiver)} API is invoked with the {@link 214 * #AD_EVENT_TYPE_CLICK} event type. 215 */ 216 @NonNull getKeyedFrequencyCapsForClickEvents()217 public List<KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents() { 218 return mKeyedFrequencyCapsForClickEvents; 219 } 220 221 /** 222 * @return The estimated size of this object, in bytes. 223 * @hide 224 */ getSizeInBytes()225 public int getSizeInBytes() { 226 return getSizeInBytesOfFcapList(mKeyedFrequencyCapsForWinEvents) 227 + getSizeInBytesOfFcapList(mKeyedFrequencyCapsForImpressionEvents) 228 + getSizeInBytesOfFcapList(mKeyedFrequencyCapsForViewEvents) 229 + getSizeInBytesOfFcapList(mKeyedFrequencyCapsForClickEvents); 230 } 231 getSizeInBytesOfFcapList(List<KeyedFrequencyCap> fcaps)232 private int getSizeInBytesOfFcapList(List<KeyedFrequencyCap> fcaps) { 233 int toReturn = 0; 234 for (final KeyedFrequencyCap fcap : fcaps) { 235 toReturn += fcap.getSizeInBytes(); 236 } 237 return toReturn; 238 } 239 240 /** 241 * A JSON serializer. 242 * 243 * @return A JSON serialization of this object. 244 * @hide 245 */ toJson()246 public JSONObject toJson() throws JSONException { 247 JSONObject toReturn = new JSONObject(); 248 toReturn.put(WIN_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForWinEvents)); 249 toReturn.put( 250 IMPRESSION_EVENTS_FIELD_NAME, 251 fcapSetToJsonArray(mKeyedFrequencyCapsForImpressionEvents)); 252 toReturn.put(VIEW_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForViewEvents)); 253 toReturn.put( 254 CLICK_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForClickEvents)); 255 return toReturn; 256 } 257 fcapSetToJsonArray(List<KeyedFrequencyCap> fcapSet)258 private static JSONArray fcapSetToJsonArray(List<KeyedFrequencyCap> fcapSet) 259 throws JSONException { 260 JSONArray toReturn = new JSONArray(); 261 for (KeyedFrequencyCap fcap : fcapSet) { 262 toReturn.put(fcap.toJson()); 263 } 264 return toReturn; 265 } 266 267 /** 268 * A JSON de-serializer. 269 * 270 * @param json A JSON representation of an {@link FrequencyCapFilters} object as would be 271 * generated by {@link #toJson()}. 272 * @return An {@link FrequencyCapFilters} object generated from the given JSON. 273 * @hide 274 */ fromJson(JSONObject json)275 public static FrequencyCapFilters fromJson(JSONObject json) throws JSONException { 276 Builder builder = new Builder(); 277 if (json.has(WIN_EVENTS_FIELD_NAME)) { 278 builder.setKeyedFrequencyCapsForWinEvents( 279 jsonArrayToFcapList(json.getJSONArray(WIN_EVENTS_FIELD_NAME))); 280 } 281 if (json.has(IMPRESSION_EVENTS_FIELD_NAME)) { 282 builder.setKeyedFrequencyCapsForImpressionEvents( 283 jsonArrayToFcapList(json.getJSONArray(IMPRESSION_EVENTS_FIELD_NAME))); 284 } 285 if (json.has(VIEW_EVENTS_FIELD_NAME)) { 286 builder.setKeyedFrequencyCapsForViewEvents( 287 jsonArrayToFcapList(json.getJSONArray(VIEW_EVENTS_FIELD_NAME))); 288 } 289 if (json.has(CLICK_EVENTS_FIELD_NAME)) { 290 builder.setKeyedFrequencyCapsForClickEvents( 291 jsonArrayToFcapList(json.getJSONArray(CLICK_EVENTS_FIELD_NAME))); 292 } 293 return builder.build(); 294 } 295 jsonArrayToFcapList(JSONArray json)296 private static List<KeyedFrequencyCap> jsonArrayToFcapList(JSONArray json) 297 throws JSONException { 298 List<KeyedFrequencyCap> toReturn = new ArrayList<>(); 299 for (int i = 0; i < json.length(); i++) { 300 toReturn.add(KeyedFrequencyCap.fromJson(json.getJSONObject(i))); 301 } 302 return toReturn; 303 } 304 305 @Override writeToParcel(@onNull Parcel dest, int flags)306 public void writeToParcel(@NonNull Parcel dest, int flags) { 307 Objects.requireNonNull(dest); 308 dest.writeTypedList(mKeyedFrequencyCapsForWinEvents); 309 dest.writeTypedList(mKeyedFrequencyCapsForImpressionEvents); 310 dest.writeTypedList(mKeyedFrequencyCapsForViewEvents); 311 dest.writeTypedList(mKeyedFrequencyCapsForClickEvents); 312 } 313 314 /** @hide */ 315 @Override describeContents()316 public int describeContents() { 317 return 0; 318 } 319 320 /** Checks whether the {@link FrequencyCapFilters} objects contain the same information. */ 321 @Override equals(Object o)322 public boolean equals(Object o) { 323 if (this == o) return true; 324 if (!(o instanceof FrequencyCapFilters)) return false; 325 FrequencyCapFilters that = (FrequencyCapFilters) o; 326 return mKeyedFrequencyCapsForWinEvents.equals(that.mKeyedFrequencyCapsForWinEvents) 327 && mKeyedFrequencyCapsForImpressionEvents.equals( 328 that.mKeyedFrequencyCapsForImpressionEvents) 329 && mKeyedFrequencyCapsForViewEvents.equals(that.mKeyedFrequencyCapsForViewEvents) 330 && mKeyedFrequencyCapsForClickEvents.equals(that.mKeyedFrequencyCapsForClickEvents); 331 } 332 333 /** Returns the hash of the {@link FrequencyCapFilters} object's data. */ 334 @Override hashCode()335 public int hashCode() { 336 return Objects.hash( 337 mKeyedFrequencyCapsForWinEvents, 338 mKeyedFrequencyCapsForImpressionEvents, 339 mKeyedFrequencyCapsForViewEvents, 340 mKeyedFrequencyCapsForClickEvents); 341 } 342 343 @Override toString()344 public String toString() { 345 return "FrequencyCapFilters{" 346 + "mKeyedFrequencyCapsForWinEvents=" 347 + mKeyedFrequencyCapsForWinEvents 348 + ", mKeyedFrequencyCapsForImpressionEvents=" 349 + mKeyedFrequencyCapsForImpressionEvents 350 + ", mKeyedFrequencyCapsForViewEvents=" 351 + mKeyedFrequencyCapsForViewEvents 352 + ", mKeyedFrequencyCapsForClickEvents=" 353 + mKeyedFrequencyCapsForClickEvents 354 + '}'; 355 } 356 357 /** Builder for creating {@link FrequencyCapFilters} objects. */ 358 public static final class Builder { 359 @NonNull 360 private List<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents = new ArrayList<>(); 361 362 @NonNull 363 private List<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents = new ArrayList<>(); 364 365 @NonNull 366 private List<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents = new ArrayList<>(); 367 368 @NonNull 369 private List<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents = new ArrayList<>(); 370 Builder()371 public Builder() {} 372 373 /** 374 * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 375 * #AD_EVENT_TYPE_WIN} event type. 376 * 377 * <p>See {@link #getKeyedFrequencyCapsForWinEvents()} for more information. 378 */ 379 @NonNull setKeyedFrequencyCapsForWinEvents( @onNull List<KeyedFrequencyCap> keyedFrequencyCapsForWinEvents)380 public Builder setKeyedFrequencyCapsForWinEvents( 381 @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForWinEvents) { 382 Objects.requireNonNull( 383 keyedFrequencyCapsForWinEvents, FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE); 384 Preconditions.checkArgument( 385 !keyedFrequencyCapsForWinEvents.contains(null), 386 FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE); 387 mKeyedFrequencyCapsForWinEvents = keyedFrequencyCapsForWinEvents; 388 return this; 389 } 390 391 /** 392 * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 393 * #AD_EVENT_TYPE_IMPRESSION} event type. 394 * 395 * <p>See {@link #getKeyedFrequencyCapsForImpressionEvents()} for more information. 396 */ 397 @NonNull setKeyedFrequencyCapsForImpressionEvents( @onNull List<KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents)398 public Builder setKeyedFrequencyCapsForImpressionEvents( 399 @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents) { 400 Objects.requireNonNull( 401 keyedFrequencyCapsForImpressionEvents, 402 FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE); 403 Preconditions.checkArgument( 404 !keyedFrequencyCapsForImpressionEvents.contains(null), 405 FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE); 406 mKeyedFrequencyCapsForImpressionEvents = keyedFrequencyCapsForImpressionEvents; 407 return this; 408 } 409 410 /** 411 * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 412 * #AD_EVENT_TYPE_VIEW} event type. 413 * 414 * <p>See {@link #getKeyedFrequencyCapsForViewEvents()} for more information. 415 */ 416 @NonNull setKeyedFrequencyCapsForViewEvents( @onNull List<KeyedFrequencyCap> keyedFrequencyCapsForViewEvents)417 public Builder setKeyedFrequencyCapsForViewEvents( 418 @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForViewEvents) { 419 Objects.requireNonNull( 420 keyedFrequencyCapsForViewEvents, FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE); 421 Preconditions.checkArgument( 422 !keyedFrequencyCapsForViewEvents.contains(null), 423 FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE); 424 mKeyedFrequencyCapsForViewEvents = keyedFrequencyCapsForViewEvents; 425 return this; 426 } 427 428 /** 429 * Sets the list of {@link KeyedFrequencyCap} objects that will filter on the {@link 430 * #AD_EVENT_TYPE_CLICK} event type. 431 * 432 * <p>See {@link #getKeyedFrequencyCapsForClickEvents()} for more information. 433 */ 434 @NonNull setKeyedFrequencyCapsForClickEvents( @onNull List<KeyedFrequencyCap> keyedFrequencyCapsForClickEvents)435 public Builder setKeyedFrequencyCapsForClickEvents( 436 @NonNull List<KeyedFrequencyCap> keyedFrequencyCapsForClickEvents) { 437 Objects.requireNonNull( 438 keyedFrequencyCapsForClickEvents, 439 FREQUENCY_CAP_FILTERS_NULL_LIST_ERROR_MESSAGE); 440 Preconditions.checkArgument( 441 !keyedFrequencyCapsForClickEvents.contains(null), 442 FREQUENCY_CAP_FILTERS_NULL_ELEMENT_ERROR_MESSAGE); 443 mKeyedFrequencyCapsForClickEvents = keyedFrequencyCapsForClickEvents; 444 return this; 445 } 446 447 /** 448 * Builds and returns a {@link FrequencyCapFilters} instance. 449 * 450 * <p>No more than 20 frequency cap filters may be associated with a single ad. If more 451 * total filters than the limit have been set, an {@link IllegalArgumentException} will be 452 * thrown. 453 */ 454 @NonNull build()455 public FrequencyCapFilters build() { 456 int numFrequencyCapFilters = 0; 457 numFrequencyCapFilters += mKeyedFrequencyCapsForWinEvents.size(); 458 numFrequencyCapFilters += mKeyedFrequencyCapsForImpressionEvents.size(); 459 numFrequencyCapFilters += mKeyedFrequencyCapsForViewEvents.size(); 460 numFrequencyCapFilters += mKeyedFrequencyCapsForClickEvents.size(); 461 462 Preconditions.checkArgument( 463 numFrequencyCapFilters <= MAX_NUM_FREQUENCY_CAP_FILTERS, 464 NUM_FREQUENCY_CAP_FILTERS_EXCEEDED_FORMAT, 465 MAX_NUM_FREQUENCY_CAP_FILTERS); 466 467 return new FrequencyCapFilters(this); 468 } 469 } 470 } 471