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