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