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 com.android.adservices.data.customaudience;
18 
19 import static android.adservices.common.AdFilters.APP_INSTALL_FIELD_NAME;
20 import static android.adservices.common.AdFilters.FREQUENCY_CAP_FIELD_NAME;
21 
22 import android.adservices.common.AdData;
23 import android.adservices.common.AdFilters;
24 import android.adservices.common.AppInstallFilters;
25 import android.adservices.common.FrequencyCapFilters;
26 import android.net.Uri;
27 
28 import androidx.annotation.NonNull;
29 
30 import com.android.adservices.LoggerFactory;
31 import com.android.adservices.data.common.DBAdData;
32 import com.android.adservices.data.common.FledgeRoomConverters;
33 
34 import org.json.JSONArray;
35 import org.json.JSONException;
36 import org.json.JSONObject;
37 
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43 
44 /** Factory for AdDataConversionStrategys */
45 public class AdDataConversionStrategyFactory {
46     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
47     private static final String RENDER_URI_FIELD_NAME = "renderUri";
48     private static final String METADATA_FIELD_NAME = "metadata";
49     private static final String AD_COUNTER_KEYS_FIELD_NAME = "adCounterKeys";
50     private static final String AD_FILTERS_FIELD_NAME = "adFilters";
51     private static final String AD_RENDER_ID_FIELD_NAME = "adRenderId";
52 
53     private static class FrequencyCapConversionImpl
54             implements FrequencyCapFiltersConversionStrategy {
55         @Override
toJsonAdCounterKeys(DBAdData adData, JSONObject toReturn)56         public void toJsonAdCounterKeys(DBAdData adData, JSONObject toReturn) throws JSONException {
57             if (!adData.getAdCounterKeys().isEmpty()) {
58                 JSONArray jsonCounterKeys = new JSONArray(adData.getAdCounterKeys());
59                 toReturn.put(AD_COUNTER_KEYS_FIELD_NAME, jsonCounterKeys);
60             }
61         }
62 
63         @Override
toJsonFilters(FrequencyCapFilters frequencyCapFilters, JSONObject toReturn)64         public void toJsonFilters(FrequencyCapFilters frequencyCapFilters, JSONObject toReturn)
65                 throws JSONException {
66             if (frequencyCapFilters != null) {
67                 toReturn.put(FREQUENCY_CAP_FIELD_NAME, frequencyCapFilters.toJson());
68             }
69         }
70 
71         @Override
fromJsonAdCounterKeys(JSONObject json, DBAdData.Builder adDataBuilder)72         public void fromJsonAdCounterKeys(JSONObject json, DBAdData.Builder adDataBuilder)
73                 throws JSONException {
74             Set<Integer> adCounterKeys = new HashSet<>();
75             if (json.has(AD_COUNTER_KEYS_FIELD_NAME)) {
76                 JSONArray counterKeys = json.getJSONArray(AD_COUNTER_KEYS_FIELD_NAME);
77                 for (int i = 0; i < counterKeys.length(); i++) {
78                     adCounterKeys.add(counterKeys.getInt(i));
79                 }
80             }
81             adDataBuilder.setAdCounterKeys(adCounterKeys);
82         }
83 
84         @Override
fromJsonFilters(JSONObject json, AdFilters.Builder builder)85         public void fromJsonFilters(JSONObject json, AdFilters.Builder builder)
86                 throws JSONException {
87             if (json.has(FREQUENCY_CAP_FIELD_NAME)) {
88                 builder.setFrequencyCapFilters(
89                         FrequencyCapFilters.fromJson(json.getJSONObject(FREQUENCY_CAP_FIELD_NAME)));
90             }
91         }
92 
93         @Override
fromServiceObjectAdCounterKeys( AdData parcelable, DBAdData.Builder adDataBuilder)94         public void fromServiceObjectAdCounterKeys(
95                 AdData parcelable, DBAdData.Builder adDataBuilder) {
96             adDataBuilder.setAdCounterKeys(parcelable.getAdCounterKeys());
97         }
98 
99         @Override
fromServiceObjectFilters(AdData parcelable, AdFilters.Builder builder)100         public void fromServiceObjectFilters(AdData parcelable, AdFilters.Builder builder) {
101             builder.setFrequencyCapFilters(parcelable.getAdFilters().getFrequencyCapFilters());
102         }
103     }
104 
105     private static class FrequencyCapConversionNoOp
106             implements FrequencyCapFiltersConversionStrategy {
107 
108         @Override
toJsonAdCounterKeys(DBAdData adData, JSONObject toReturn)109         public void toJsonAdCounterKeys(DBAdData adData, JSONObject toReturn) throws JSONException {
110             sLogger.v("Frequency cap filtering is disabled, so toJSON is a no op");
111         }
112 
113         @Override
toJsonFilters(FrequencyCapFilters frequencyCapFilters, JSONObject toReturn)114         public void toJsonFilters(FrequencyCapFilters frequencyCapFilters, JSONObject toReturn)
115                 throws JSONException {
116             sLogger.v("Frequency cap filtering is disabled, so toJSON is a no op");
117         }
118 
119         @Override
fromJsonAdCounterKeys(JSONObject json, DBAdData.Builder adDataBuilder)120         public void fromJsonAdCounterKeys(JSONObject json, DBAdData.Builder adDataBuilder) {
121             sLogger.v("Frequency cap filtering is disabled, so fromJSON is a no op");
122         }
123 
124         @Override
fromJsonFilters(JSONObject json, AdFilters.Builder builder)125         public void fromJsonFilters(JSONObject json, AdFilters.Builder builder)
126                 throws JSONException {
127             sLogger.v("Frequency cap filtering is disabled, so fromJSON is a no op");
128         }
129 
130         @Override
fromServiceObjectAdCounterKeys( AdData parcelable, DBAdData.Builder adDataBuilder)131         public void fromServiceObjectAdCounterKeys(
132                 AdData parcelable, DBAdData.Builder adDataBuilder) {
133             sLogger.v("Frequency cap filtering is disabled, so fromServiceObject is a no op");
134         }
135 
136         @Override
fromServiceObjectFilters(AdData parcelable, AdFilters.Builder builder)137         public void fromServiceObjectFilters(AdData parcelable, AdFilters.Builder builder) {
138             sLogger.v("Frequency cap filtering is disabled, so fromServiceObject is a no op");
139         }
140     }
141 
142     private static class AppInstallConversionImpl implements AppInstallFiltersConversionStrategy {
143 
144         @Override
toJson(AppInstallFilters appInstallFilters, JSONObject toReturn)145         public void toJson(AppInstallFilters appInstallFilters, JSONObject toReturn)
146                 throws JSONException {
147             if (appInstallFilters != null) {
148                 toReturn.put(APP_INSTALL_FIELD_NAME, appInstallFilters.toJson());
149             }
150         }
151 
152         @Override
fromJson(JSONObject json, AdFilters.Builder builder)153         public void fromJson(JSONObject json, AdFilters.Builder builder) throws JSONException {
154             if (json.has(APP_INSTALL_FIELD_NAME)) {
155                 builder.setAppInstallFilters(
156                         AppInstallFilters.fromJson(json.getJSONObject(APP_INSTALL_FIELD_NAME)));
157             }
158         }
159 
160         @Override
fromServiceObject(AdData parcelable, AdFilters.Builder builder)161         public void fromServiceObject(AdData parcelable, AdFilters.Builder builder) {
162             builder.setAppInstallFilters(parcelable.getAdFilters().getAppInstallFilters());
163         }
164     }
165 
166     private static class AppInstallConversionNoOp implements AppInstallFiltersConversionStrategy {
167         @Override
toJson(AppInstallFilters appInstallFilters, JSONObject toReturn)168         public void toJson(AppInstallFilters appInstallFilters, JSONObject toReturn)
169                 throws JSONException {
170             sLogger.v("App install cap filtering is disabled, so toJSON is a no op");
171         }
172 
173         @Override
fromJson(JSONObject json, AdFilters.Builder builder)174         public void fromJson(JSONObject json, AdFilters.Builder builder) throws JSONException {
175             sLogger.v("App install filtering is disabled, so fromJSON is a no op");
176         }
177 
178         @Override
fromServiceObject(AdData parcelable, AdFilters.Builder builder)179         public void fromServiceObject(AdData parcelable, AdFilters.Builder builder) {
180             sLogger.v("App install filtering is disabled, so fromServiceObject is a no op");
181         }
182     }
183 
184     private static class FilteringEnabledConversionStrategy
185             implements AdDataOptionalConversionStrategy {
186         private final FrequencyCapFiltersConversionStrategy mFrequencyCapFiltersConversionStrategy;
187         private final AppInstallFiltersConversionStrategy mAppInstallFiltersConversionStrategy;
188 
FilteringEnabledConversionStrategy( boolean frequencyCapFilteringEnabled, boolean appInstallFilteringEnabled)189         FilteringEnabledConversionStrategy(
190                 boolean frequencyCapFilteringEnabled, boolean appInstallFilteringEnabled) {
191             if (frequencyCapFilteringEnabled) {
192                 mFrequencyCapFiltersConversionStrategy = new FrequencyCapConversionImpl();
193             } else {
194                 mFrequencyCapFiltersConversionStrategy = new FrequencyCapConversionNoOp();
195             }
196 
197             if (appInstallFilteringEnabled) {
198                 mAppInstallFiltersConversionStrategy = new AppInstallConversionImpl();
199             } else {
200                 mAppInstallFiltersConversionStrategy = new AppInstallConversionNoOp();
201             }
202         }
203 
toJson(@onNull DBAdData adData, @NonNull JSONObject toReturn)204         public void toJson(@NonNull DBAdData adData, @NonNull JSONObject toReturn)
205                 throws JSONException {
206             mFrequencyCapFiltersConversionStrategy.toJsonAdCounterKeys(adData, toReturn);
207             if (adData.getAdFilters() != null) {
208                 JSONObject adFilterWrapper = new JSONObject();
209                 mFrequencyCapFiltersConversionStrategy.toJsonFilters(
210                         adData.getAdFilters().getFrequencyCapFilters(), adFilterWrapper);
211                 mAppInstallFiltersConversionStrategy.toJson(
212                         adData.getAdFilters().getAppInstallFilters(), adFilterWrapper);
213                 toReturn.put(AD_FILTERS_FIELD_NAME, adFilterWrapper);
214             }
215         }
216 
fromJson(@onNull JSONObject json, @NonNull DBAdData.Builder adDataBuilder)217         public void fromJson(@NonNull JSONObject json, @NonNull DBAdData.Builder adDataBuilder)
218                 throws JSONException {
219             mFrequencyCapFiltersConversionStrategy.fromJsonAdCounterKeys(json, adDataBuilder);
220             AdFilters adFilters = null;
221             if (json.has(AD_FILTERS_FIELD_NAME)) {
222                 AdFilters.Builder builder = new AdFilters.Builder();
223                 JSONObject adFiltersObject = json.getJSONObject(AD_FILTERS_FIELD_NAME);
224                 mFrequencyCapFiltersConversionStrategy.fromJsonFilters(adFiltersObject, builder);
225                 mAppInstallFiltersConversionStrategy.fromJson(adFiltersObject, builder);
226                 adFilters = builder.build();
227             }
228             adDataBuilder.setAdFilters(adFilters);
229         }
230 
231         @Override
fromServiceObject( @onNull AdData parcelable, @NonNull DBAdData.Builder adDataBuilder)232         public void fromServiceObject(
233                 @NonNull AdData parcelable, @NonNull DBAdData.Builder adDataBuilder) {
234             mFrequencyCapFiltersConversionStrategy.fromServiceObjectAdCounterKeys(
235                     parcelable, adDataBuilder);
236             if (parcelable.getAdFilters() != null) {
237                 AdFilters adFilters;
238                 AdFilters.Builder adFiltersBuilder = new AdFilters.Builder();
239                 mAppInstallFiltersConversionStrategy.fromServiceObject(
240                         parcelable, adFiltersBuilder);
241                 mFrequencyCapFiltersConversionStrategy.fromServiceObjectFilters(
242                         parcelable, adFiltersBuilder);
243                 adFilters = adFiltersBuilder.build();
244                 sLogger.v("Final Ad Filters in fromServiceObject: %s", adFilters);
245                 adDataBuilder.setAdFilters(adFilters);
246             }
247         }
248     }
249 
250     private static class AdRenderIdEnabledConversionStrategy
251             implements AdDataOptionalConversionStrategy {
toJson(@onNull DBAdData adData, @NonNull JSONObject toReturn)252         public void toJson(@NonNull DBAdData adData, @NonNull JSONObject toReturn)
253                 throws JSONException {
254             if (adData.getAdRenderId() != null) {
255                 toReturn.put(AD_RENDER_ID_FIELD_NAME, adData.getAdRenderId());
256             }
257         }
258 
fromJson(@onNull JSONObject json, @NonNull DBAdData.Builder adDataBuilder)259         public void fromJson(@NonNull JSONObject json, @NonNull DBAdData.Builder adDataBuilder)
260                 throws JSONException {
261             if (json.has(AD_RENDER_ID_FIELD_NAME)) {
262                 adDataBuilder.setAdRenderId(json.getString(AD_RENDER_ID_FIELD_NAME));
263             }
264         }
265 
266         @Override
fromServiceObject( @onNull AdData parcelable, @NonNull DBAdData.Builder adDataBuilder)267         public void fromServiceObject(
268                 @NonNull AdData parcelable, @NonNull DBAdData.Builder adDataBuilder) {
269             sLogger.v("Setting ad render id " + parcelable.getAdRenderId());
270             adDataBuilder.setAdRenderId(parcelable.getAdRenderId());
271         }
272     }
273 
274     /**
275      * Conversion strategy with no optional feature enabled. This is the baseline of all
276      * conversions.
277      */
278     private static class BaseConversionStrategy implements AdDataConversionStrategy {
279         /**
280          * Serialize {@link DBAdData} to {@link JSONObject}, but ignore filter fields.
281          *
282          * @param adData the {@link DBAdData} object to serialize
283          * @return the json serialization of the AdData object
284          */
toJson(DBAdData adData)285         public JSONObject toJson(DBAdData adData) throws JSONException {
286             return new org.json.JSONObject()
287                     .put(
288                             RENDER_URI_FIELD_NAME,
289                             FledgeRoomConverters.serializeUri(adData.getRenderUri()))
290                     .put(METADATA_FIELD_NAME, adData.getMetadata());
291         }
292 
293         /**
294          * Deserialize {@link DBAdData} to {@link JSONObject} but ignore filter fields.
295          *
296          * @param json the {@link JSONObject} object to deserialize
297          * @return the {@link DBAdData} deserialized from the json
298          */
fromJson(JSONObject json)299         public DBAdData.Builder fromJson(JSONObject json) throws JSONException {
300             String renderUriString = json.getString(RENDER_URI_FIELD_NAME);
301             String metadata = json.getString(METADATA_FIELD_NAME);
302             Uri renderUri = FledgeRoomConverters.deserializeUri(renderUriString);
303             return new DBAdData.Builder().setRenderUri(renderUri).setMetadata(metadata);
304         }
305 
306         /**
307          * Parse parcelable {@link AdData} to storage model {@link DBAdData}.
308          *
309          * @param parcelable the service model.
310          * @return storage model
311          */
312         @NonNull
313         @Override
fromServiceObject(@onNull AdData parcelable)314         public DBAdData.Builder fromServiceObject(@NonNull AdData parcelable) {
315             return new DBAdData.Builder()
316                     .setRenderUri(parcelable.getRenderUri())
317                     .setMetadata(parcelable.getMetadata())
318                     .setAdCounterKeys(Collections.emptySet());
319         }
320     }
321 
322     private static class CompositeConversionStrategy implements AdDataConversionStrategy {
323         private final AdDataConversionStrategy mBaseStrategy;
324         private final List<AdDataOptionalConversionStrategy> mOptionalStrategies;
325 
CompositeConversionStrategy(AdDataConversionStrategy baseStrategy)326         CompositeConversionStrategy(AdDataConversionStrategy baseStrategy) {
327             this.mBaseStrategy = baseStrategy;
328             this.mOptionalStrategies = new ArrayList<>();
329         }
330 
331         @Override
toJson(DBAdData adData)332         public JSONObject toJson(DBAdData adData) throws JSONException {
333             JSONObject result = mBaseStrategy.toJson(adData);
334             for (AdDataOptionalConversionStrategy optionalStrategy : mOptionalStrategies) {
335                 optionalStrategy.toJson(adData, result);
336             }
337             return result;
338         }
339 
340         @Override
fromJson(JSONObject json)341         public DBAdData.Builder fromJson(JSONObject json) throws JSONException {
342             DBAdData.Builder result = mBaseStrategy.fromJson(json);
343             for (AdDataOptionalConversionStrategy optionalStrategy : mOptionalStrategies) {
344                 optionalStrategy.fromJson(json, result);
345             }
346             return result;
347         }
348 
349         @NonNull
350         @Override
fromServiceObject(@onNull AdData parcelable)351         public DBAdData.Builder fromServiceObject(@NonNull AdData parcelable) {
352             DBAdData.Builder result = mBaseStrategy.fromServiceObject(parcelable);
353             for (AdDataOptionalConversionStrategy optionalStrategy : mOptionalStrategies) {
354                 optionalStrategy.fromServiceObject(parcelable, result);
355             }
356             return result;
357         }
358 
359         @NonNull
composeWith(AdDataOptionalConversionStrategy strategy)360         public CompositeConversionStrategy composeWith(AdDataOptionalConversionStrategy strategy) {
361             mOptionalStrategies.add(strategy);
362             return this;
363         }
364     }
365 
366     /**
367      * Returns the appropriate AdDataConversionStrategy based whether which kind of filtering is
368      * enabled
369      *
370      * @param frequencyCapFilteringEnabled denotes if frequency cap filtering is enabled
371      * @param appInstallFilteringEnabled denotes if app install filtering is enabled
372      * @return An implementation of AdDataConversionStrategy
373      */
getAdDataConversionStrategy( boolean frequencyCapFilteringEnabled, boolean appInstallFilteringEnabled, boolean adRenderIdEnabled)374     public static AdDataConversionStrategy getAdDataConversionStrategy(
375             boolean frequencyCapFilteringEnabled,
376             boolean appInstallFilteringEnabled,
377             boolean adRenderIdEnabled) {
378 
379         CompositeConversionStrategy result =
380                 new CompositeConversionStrategy(new BaseConversionStrategy());
381 
382         if (frequencyCapFilteringEnabled || appInstallFilteringEnabled) {
383             sLogger.v("Adding Filtering Conversion Strategy to Composite Conversion Strategy");
384             result.composeWith(
385                     new FilteringEnabledConversionStrategy(
386                             frequencyCapFilteringEnabled, appInstallFilteringEnabled));
387         }
388         if (adRenderIdEnabled) {
389             sLogger.v("Adding Ad Render Conversion Strategy to Composite Conversion Strategy");
390             result.composeWith(new AdRenderIdEnabledConversionStrategy());
391         }
392         return result;
393     }
394 }
395