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.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.os.OutcomeReceiver; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import com.android.adservices.AdServicesParcelableUtil; 27 import com.android.internal.annotations.VisibleForTesting; 28 29 import org.json.JSONArray; 30 import org.json.JSONException; 31 import org.json.JSONObject; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.HashSet; 36 import java.util.Objects; 37 import java.util.Set; 38 import java.util.concurrent.Executor; 39 40 /** 41 * A container for the ad filters that are based on frequency caps. 42 * 43 * <p>Frequency caps filters combine an event type with a set of {@link KeyedFrequencyCap} objects 44 * to define a set of ad filters. If any of these frequency caps are exceeded for a given ad, the ad 45 * will be removed from the group of ads submitted to a buyer adtech's bidding function. 46 * 47 * @hide 48 */ 49 // TODO(b/221876775): Unhide for frequency cap API review 50 public final class FrequencyCapFilters implements Parcelable { 51 /** 52 * Event types which are used to update ad counter histograms, which inform frequency cap 53 * filtering in FLEDGE. 54 * 55 * @hide 56 */ 57 @IntDef( 58 prefix = {"AD_EVENT_TYPE_"}, 59 value = { 60 AD_EVENT_TYPE_INVALID, 61 AD_EVENT_TYPE_WIN, 62 AD_EVENT_TYPE_IMPRESSION, 63 AD_EVENT_TYPE_VIEW, 64 AD_EVENT_TYPE_CLICK 65 }) 66 @Retention(RetentionPolicy.SOURCE) 67 public @interface AdEventType {} 68 69 /** @hide */ 70 public static final int AD_EVENT_TYPE_INVALID = -1; 71 72 /** 73 * The WIN ad event type is automatically populated within the FLEDGE service for any winning ad 74 * which is returned from FLEDGE ad selection. 75 * 76 * <p>It should not be used to manually update an ad counter histogram. 77 */ 78 public static final int AD_EVENT_TYPE_WIN = 0; 79 80 public static final int AD_EVENT_TYPE_IMPRESSION = 1; 81 public static final int AD_EVENT_TYPE_VIEW = 2; 82 public static final int AD_EVENT_TYPE_CLICK = 3; 83 /** @hide */ 84 @VisibleForTesting public static final String WIN_EVENTS_FIELD_NAME = "win"; 85 /** @hide */ 86 @VisibleForTesting public static final String IMPRESSION_EVENTS_FIELD_NAME = "impression"; 87 /** @hide */ 88 @VisibleForTesting public static final String VIEW_EVENTS_FIELD_NAME = "view"; 89 /** @hide */ 90 @VisibleForTesting public static final String CLICK_EVENTS_FIELD_NAME = "click"; 91 92 @NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents; 93 @NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents; 94 @NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents; 95 @NonNull private final Set<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents; 96 97 @NonNull 98 public static final Creator<FrequencyCapFilters> CREATOR = 99 new Creator<FrequencyCapFilters>() { 100 @Override 101 public FrequencyCapFilters createFromParcel(@NonNull Parcel in) { 102 Objects.requireNonNull(in); 103 return new FrequencyCapFilters(in); 104 } 105 106 @Override 107 public FrequencyCapFilters[] newArray(int size) { 108 return new FrequencyCapFilters[size]; 109 } 110 }; 111 FrequencyCapFilters(@onNull Builder builder)112 private FrequencyCapFilters(@NonNull Builder builder) { 113 Objects.requireNonNull(builder); 114 115 mKeyedFrequencyCapsForWinEvents = builder.mKeyedFrequencyCapsForWinEvents; 116 mKeyedFrequencyCapsForImpressionEvents = builder.mKeyedFrequencyCapsForImpressionEvents; 117 mKeyedFrequencyCapsForViewEvents = builder.mKeyedFrequencyCapsForViewEvents; 118 mKeyedFrequencyCapsForClickEvents = builder.mKeyedFrequencyCapsForClickEvents; 119 } 120 FrequencyCapFilters(@onNull Parcel in)121 private FrequencyCapFilters(@NonNull Parcel in) { 122 Objects.requireNonNull(in); 123 124 mKeyedFrequencyCapsForWinEvents = 125 AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR); 126 mKeyedFrequencyCapsForImpressionEvents = 127 AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR); 128 mKeyedFrequencyCapsForViewEvents = 129 AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR); 130 mKeyedFrequencyCapsForClickEvents = 131 AdServicesParcelableUtil.readSetFromParcel(in, KeyedFrequencyCap.CREATOR); 132 } 133 134 /** 135 * Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 136 * #AD_EVENT_TYPE_WIN} event type. 137 * 138 * <p>These frequency caps apply to events for ads that were selected as winners in ad 139 * selection. Winning ads are used to automatically increment the associated counter keys on the 140 * win event type. 141 */ 142 @NonNull getKeyedFrequencyCapsForWinEvents()143 public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForWinEvents() { 144 return mKeyedFrequencyCapsForWinEvents; 145 } 146 147 /** 148 * Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 149 * #AD_EVENT_TYPE_IMPRESSION} event type. 150 * 151 * <p>These frequency caps apply to events which correlate to an impression as interpreted by an 152 * adtech. Note that events are not automatically counted when calling {@link 153 * android.adservices.adselection.AdSelectionManager#reportImpression(ReportImpressionRequest, 154 * Executor, OutcomeReceiver)}. 155 */ 156 @NonNull getKeyedFrequencyCapsForImpressionEvents()157 public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForImpressionEvents() { 158 return mKeyedFrequencyCapsForImpressionEvents; 159 } 160 161 /** 162 * Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 163 * #AD_EVENT_TYPE_VIEW} event type. 164 * 165 * <p>These frequency caps apply to events which correlate to a view as interpreted by an 166 * adtech. 167 */ 168 @NonNull getKeyedFrequencyCapsForViewEvents()169 public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForViewEvents() { 170 return mKeyedFrequencyCapsForViewEvents; 171 } 172 173 /** 174 * Gets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 175 * #AD_EVENT_TYPE_CLICK} event type. 176 * 177 * <p>These frequency caps apply to events which correlate to a click as interpreted by an 178 * adtech. 179 */ 180 @NonNull getKeyedFrequencyCapsForClickEvents()181 public Set<KeyedFrequencyCap> getKeyedFrequencyCapsForClickEvents() { 182 return mKeyedFrequencyCapsForClickEvents; 183 } 184 185 /** 186 * @return The estimated size of this object, in bytes. 187 * @hide 188 */ getSizeInBytes()189 public int getSizeInBytes() { 190 return getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForWinEvents) 191 + getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForImpressionEvents) 192 + getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForViewEvents) 193 + getSizeInBytesOfFcapSet(mKeyedFrequencyCapsForClickEvents); 194 } 195 getSizeInBytesOfFcapSet(Set<KeyedFrequencyCap> fcaps)196 private int getSizeInBytesOfFcapSet(Set<KeyedFrequencyCap> fcaps) { 197 int toReturn = 0; 198 for (final KeyedFrequencyCap fcap : fcaps) { 199 toReturn += fcap.getSizeInBytes(); 200 } 201 return toReturn; 202 } 203 204 /** 205 * A JSON serializer. 206 * 207 * @return A JSON serialization of this object. 208 * @hide 209 */ toJson()210 public JSONObject toJson() throws JSONException { 211 JSONObject toReturn = new JSONObject(); 212 toReturn.put(WIN_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForWinEvents)); 213 toReturn.put( 214 IMPRESSION_EVENTS_FIELD_NAME, 215 fcapSetToJsonArray(mKeyedFrequencyCapsForImpressionEvents)); 216 toReturn.put(VIEW_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForViewEvents)); 217 toReturn.put( 218 CLICK_EVENTS_FIELD_NAME, fcapSetToJsonArray(mKeyedFrequencyCapsForClickEvents)); 219 return toReturn; 220 } 221 fcapSetToJsonArray(Set<KeyedFrequencyCap> fcapSet)222 private static JSONArray fcapSetToJsonArray(Set<KeyedFrequencyCap> fcapSet) 223 throws JSONException { 224 JSONArray toReturn = new JSONArray(); 225 for (KeyedFrequencyCap fcap : fcapSet) { 226 toReturn.put(fcap.toJson()); 227 } 228 return toReturn; 229 } 230 231 /** 232 * A JSON de-serializer. 233 * 234 * @param json A JSON representation of an {@link FrequencyCapFilters} object as would be 235 * generated by {@link #toJson()}. 236 * @return An {@link FrequencyCapFilters} object generated from the given JSON. 237 * @hide 238 */ fromJson(JSONObject json)239 public static FrequencyCapFilters fromJson(JSONObject json) throws JSONException { 240 Builder builder = new Builder(); 241 if (json.has(WIN_EVENTS_FIELD_NAME)) { 242 builder.setKeyedFrequencyCapsForWinEvents( 243 jsonArrayToFcapSet(json.getJSONArray(WIN_EVENTS_FIELD_NAME))); 244 } 245 if (json.has(IMPRESSION_EVENTS_FIELD_NAME)) { 246 builder.setKeyedFrequencyCapsForImpressionEvents( 247 jsonArrayToFcapSet(json.getJSONArray(IMPRESSION_EVENTS_FIELD_NAME))); 248 } 249 if (json.has(VIEW_EVENTS_FIELD_NAME)) { 250 builder.setKeyedFrequencyCapsForViewEvents( 251 jsonArrayToFcapSet(json.getJSONArray(VIEW_EVENTS_FIELD_NAME))); 252 } 253 if (json.has(CLICK_EVENTS_FIELD_NAME)) { 254 builder.setKeyedFrequencyCapsForClickEvents( 255 jsonArrayToFcapSet(json.getJSONArray(CLICK_EVENTS_FIELD_NAME))); 256 } 257 return builder.build(); 258 } 259 jsonArrayToFcapSet(JSONArray json)260 private static Set<KeyedFrequencyCap> jsonArrayToFcapSet(JSONArray json) throws JSONException { 261 Set<KeyedFrequencyCap> toReturn = new HashSet<>(); 262 for (int i = 0; i < json.length(); i++) { 263 toReturn.add(KeyedFrequencyCap.fromJson(json.getJSONObject(i))); 264 } 265 return toReturn; 266 } 267 268 @Override writeToParcel(@onNull Parcel dest, int flags)269 public void writeToParcel(@NonNull Parcel dest, int flags) { 270 Objects.requireNonNull(dest); 271 AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForWinEvents); 272 AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForImpressionEvents); 273 AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForViewEvents); 274 AdServicesParcelableUtil.writeSetToParcel(dest, mKeyedFrequencyCapsForClickEvents); 275 } 276 277 /** @hide */ 278 @Override describeContents()279 public int describeContents() { 280 return 0; 281 } 282 283 /** Checks whether the {@link FrequencyCapFilters} objects contain the same information. */ 284 @Override equals(Object o)285 public boolean equals(Object o) { 286 if (this == o) return true; 287 if (!(o instanceof FrequencyCapFilters)) return false; 288 FrequencyCapFilters that = (FrequencyCapFilters) o; 289 return mKeyedFrequencyCapsForWinEvents.equals(that.mKeyedFrequencyCapsForWinEvents) 290 && mKeyedFrequencyCapsForImpressionEvents.equals( 291 that.mKeyedFrequencyCapsForImpressionEvents) 292 && mKeyedFrequencyCapsForViewEvents.equals(that.mKeyedFrequencyCapsForViewEvents) 293 && mKeyedFrequencyCapsForClickEvents.equals(that.mKeyedFrequencyCapsForClickEvents); 294 } 295 296 /** Returns the hash of the {@link FrequencyCapFilters} object's data. */ 297 @Override hashCode()298 public int hashCode() { 299 return Objects.hash( 300 mKeyedFrequencyCapsForWinEvents, 301 mKeyedFrequencyCapsForImpressionEvents, 302 mKeyedFrequencyCapsForViewEvents, 303 mKeyedFrequencyCapsForClickEvents); 304 } 305 306 @Override toString()307 public String toString() { 308 return "FrequencyCapFilters{" 309 + "mKeyedFrequencyCapsForWinEvents=" 310 + mKeyedFrequencyCapsForWinEvents 311 + ", mKeyedFrequencyCapsForImpressionEvents=" 312 + mKeyedFrequencyCapsForImpressionEvents 313 + ", mKeyedFrequencyCapsForViewEvents=" 314 + mKeyedFrequencyCapsForViewEvents 315 + ", mKeyedFrequencyCapsForClickEvents=" 316 + mKeyedFrequencyCapsForClickEvents 317 + '}'; 318 } 319 320 /** Builder for creating {@link FrequencyCapFilters} objects. */ 321 public static final class Builder { 322 @NonNull private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForWinEvents = new HashSet<>(); 323 324 @NonNull 325 private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForImpressionEvents = new HashSet<>(); 326 327 @NonNull private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForViewEvents = new HashSet<>(); 328 @NonNull private Set<KeyedFrequencyCap> mKeyedFrequencyCapsForClickEvents = new HashSet<>(); 329 Builder()330 public Builder() {} 331 332 /** 333 * Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 334 * #AD_EVENT_TYPE_WIN} event type. 335 * 336 * <p>See {@link #getKeyedFrequencyCapsForWinEvents()} for more information. 337 */ 338 @NonNull setKeyedFrequencyCapsForWinEvents( @onNull Set<KeyedFrequencyCap> keyedFrequencyCapsForWinEvents)339 public Builder setKeyedFrequencyCapsForWinEvents( 340 @NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForWinEvents) { 341 Objects.requireNonNull(keyedFrequencyCapsForWinEvents); 342 mKeyedFrequencyCapsForWinEvents = keyedFrequencyCapsForWinEvents; 343 return this; 344 } 345 346 /** 347 * Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 348 * #AD_EVENT_TYPE_IMPRESSION} event type. 349 * 350 * <p>See {@link #getKeyedFrequencyCapsForImpressionEvents()} for more information. 351 */ 352 @NonNull setKeyedFrequencyCapsForImpressionEvents( @onNull Set<KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents)353 public Builder setKeyedFrequencyCapsForImpressionEvents( 354 @NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForImpressionEvents) { 355 Objects.requireNonNull(keyedFrequencyCapsForImpressionEvents); 356 mKeyedFrequencyCapsForImpressionEvents = keyedFrequencyCapsForImpressionEvents; 357 return this; 358 } 359 360 /** 361 * Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 362 * #AD_EVENT_TYPE_VIEW} event type. 363 * 364 * <p>See {@link #getKeyedFrequencyCapsForViewEvents()} for more information. 365 */ 366 @NonNull setKeyedFrequencyCapsForViewEvents( @onNull Set<KeyedFrequencyCap> keyedFrequencyCapsForViewEvents)367 public Builder setKeyedFrequencyCapsForViewEvents( 368 @NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForViewEvents) { 369 Objects.requireNonNull(keyedFrequencyCapsForViewEvents); 370 mKeyedFrequencyCapsForViewEvents = keyedFrequencyCapsForViewEvents; 371 return this; 372 } 373 374 /** 375 * Sets the set of {@link KeyedFrequencyCap} objects that will filter on the {@link 376 * #AD_EVENT_TYPE_CLICK} event type. 377 * 378 * <p>See {@link #getKeyedFrequencyCapsForClickEvents()} for more information. 379 */ 380 @NonNull setKeyedFrequencyCapsForClickEvents( @onNull Set<KeyedFrequencyCap> keyedFrequencyCapsForClickEvents)381 public Builder setKeyedFrequencyCapsForClickEvents( 382 @NonNull Set<KeyedFrequencyCap> keyedFrequencyCapsForClickEvents) { 383 Objects.requireNonNull(keyedFrequencyCapsForClickEvents); 384 mKeyedFrequencyCapsForClickEvents = keyedFrequencyCapsForClickEvents; 385 return this; 386 } 387 388 /** Builds and returns a {@link FrequencyCapFilters} instance. */ 389 @NonNull build()390 public FrequencyCapFilters build() { 391 return new FrequencyCapFilters(this); 392 } 393 } 394 } 395