1 /* 2 * Copyright (C) 2022 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.service.measurement; 18 19 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.END; 20 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.EXPIRY; 21 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.FILTER_DATA; 22 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.POST_INSTALL_EXCLUSIVITY_WINDOW; 23 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.PRIORITY; 24 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.SOURCE_EXPIRY_OVERRIDE; 25 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.SOURCE_FILTERS; 26 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.SOURCE_NETWORK; 27 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.SOURCE_NOT_FILTERS; 28 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.SOURCE_PRIORITY_RANGE; 29 import static com.android.adservices.service.measurement.AttributionConfig.AttributionConfigContract.START; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.util.Pair; 34 35 import com.android.adservices.LoggerFactory; 36 import com.android.adservices.service.Flags; 37 import com.android.adservices.service.measurement.util.Filter; 38 import com.android.adservices.service.measurement.util.MathUtils; 39 40 import org.json.JSONArray; 41 import org.json.JSONException; 42 import org.json.JSONObject; 43 44 import java.util.List; 45 import java.util.Objects; 46 47 /** POJO for AttributionConfig. */ 48 public class AttributionConfig { 49 50 @NonNull private final String mSourceAdtech; 51 @Nullable private final Pair<Long, Long> mSourcePriorityRange; 52 @Nullable private final List<FilterMap> mSourceFilters; 53 @Nullable private final List<FilterMap> mSourceNotFilters; 54 @Nullable private final Long mSourceExpiryOverride; 55 @Nullable private final Long mPriority; 56 @Nullable private final Long mExpiry; 57 @Nullable private final FilterMap mFilterData; 58 @Nullable private final Long mPostInstallExclusivityWindow; 59 AttributionConfig(@onNull AttributionConfig.Builder builder)60 private AttributionConfig(@NonNull AttributionConfig.Builder builder) { 61 mSourceAdtech = builder.mSourceAdtech; 62 mSourcePriorityRange = builder.mSourcePriorityRange; 63 mSourceFilters = builder.mSourceFilters; 64 mSourceNotFilters = builder.mSourceNotFilters; 65 mSourceExpiryOverride = builder.mSourceExpiryOverride; 66 mPriority = builder.mPriority; 67 mExpiry = builder.mExpiry; 68 mFilterData = builder.mFilterData; 69 mPostInstallExclusivityWindow = builder.mPostInstallExclusivityWindow; 70 } 71 72 @Override equals(Object obj)73 public boolean equals(Object obj) { 74 if (!(obj instanceof AttributionConfig)) { 75 return false; 76 } 77 AttributionConfig attributionConfig = (AttributionConfig) obj; 78 return Objects.equals(mSourceAdtech, attributionConfig.mSourceAdtech) 79 && Objects.equals(mSourcePriorityRange, attributionConfig.mSourcePriorityRange) 80 && Objects.equals(mSourceFilters, attributionConfig.mSourceFilters) 81 && Objects.equals(mSourceNotFilters, attributionConfig.mSourceNotFilters) 82 && Objects.equals(mSourceExpiryOverride, attributionConfig.mSourceExpiryOverride) 83 && Objects.equals(mPriority, attributionConfig.mPriority) 84 && Objects.equals(mExpiry, attributionConfig.mExpiry) 85 && Objects.equals(mFilterData, attributionConfig.mFilterData) 86 && Objects.equals( 87 mPostInstallExclusivityWindow, 88 attributionConfig.mPostInstallExclusivityWindow); 89 } 90 91 @Override hashCode()92 public int hashCode() { 93 return Objects.hash( 94 mSourceAdtech, 95 mSourcePriorityRange, 96 mSourceFilters, 97 mSourceNotFilters, 98 mSourceExpiryOverride, 99 mPriority, 100 mExpiry, 101 mFilterData, 102 mPostInstallExclusivityWindow); 103 } 104 105 /** Returns the source adtech as String. */ 106 @NonNull getSourceAdtech()107 public String getSourceAdtech() { 108 return mSourceAdtech; 109 } 110 111 /** 112 * Returns source priority range JSONObject as a Pair of Long values. example: 113 * "source_priority_range": { “start”: 100, “end”: 1000 } 114 */ 115 @Nullable getSourcePriorityRange()116 public Pair<Long, Long> getSourcePriorityRange() { 117 return mSourcePriorityRange; 118 } 119 120 /** 121 * Returns source filter JSONObject as List of FilterMap. example: "source_filters": { 122 * "campaign_type": ["install"], "source_type": ["navigation"] } 123 */ 124 @Nullable getSourceFilters()125 public List<FilterMap> getSourceFilters() { 126 return mSourceFilters; 127 } 128 129 /** 130 * Returns source not filter JSONObject as List of FilterMap. example: "source_not_filters": { 131 * "campaign_type": ["install"] } 132 */ 133 @Nullable getSourceNotFilters()134 public List<FilterMap> getSourceNotFilters() { 135 return mSourceNotFilters; 136 } 137 138 /** 139 * Returns source expiry override as long. Source registration time + source expiry override < 140 * trigger time. 141 */ 142 @Nullable getSourceExpiryOverride()143 public Long getSourceExpiryOverride() { 144 return mSourceExpiryOverride; 145 } 146 147 /** Returns the derived priority of the source as long */ 148 @Nullable getPriority()149 public Long getPriority() { 150 return mPriority; 151 } 152 153 /** 154 * Returns the derived source expiry in {@link java.util.concurrent.TimeUnit#SECONDS} as long. 155 */ 156 @Nullable getExpiry()157 public Long getExpiry() { 158 return mExpiry; 159 } 160 161 /** 162 * Returns the derived filter data of the source as List of FilterMap. example: "filter_data": { 163 * "conversion_subdomain": ["electronics.megastore"], "product": ["1234", "234"] } 164 */ 165 @Nullable getFilterData()166 public FilterMap getFilterData() { 167 return mFilterData; 168 } 169 170 /** Returns the derived post install exclusivity window as long. */ 171 @Nullable getPostInstallExclusivityWindow()172 public Long getPostInstallExclusivityWindow() { 173 return mPostInstallExclusivityWindow; 174 } 175 176 /** 177 * Serializes the object as JSON. This is consistent with the format that is received in the 178 * response headers as well as stored as is in the database. 179 * 180 * @return serialized JSON object 181 */ 182 @Nullable serializeAsJson(Flags flags)183 public JSONObject serializeAsJson(Flags flags) { 184 try { 185 JSONObject attributionConfig = new JSONObject(); 186 attributionConfig.put(SOURCE_NETWORK, mSourceAdtech); 187 188 if (mSourcePriorityRange != null) { 189 JSONObject sourcePriorityRange = new JSONObject(); 190 sourcePriorityRange.put(START, mSourcePriorityRange.first); 191 sourcePriorityRange.put(END, mSourcePriorityRange.second); 192 attributionConfig.put(SOURCE_PRIORITY_RANGE, sourcePriorityRange); 193 } 194 195 Filter filter = new Filter(flags); 196 if (mSourceFilters != null) { 197 attributionConfig.put(SOURCE_FILTERS, filter.serializeFilterSet(mSourceFilters)); 198 } 199 200 if (mSourceNotFilters != null) { 201 attributionConfig.put( 202 SOURCE_NOT_FILTERS, filter.serializeFilterSet(mSourceNotFilters)); 203 } 204 205 if (mSourceExpiryOverride != null) { 206 attributionConfig.put(SOURCE_EXPIRY_OVERRIDE, mSourceExpiryOverride); 207 } 208 209 if (mPriority != null) { 210 attributionConfig.put(PRIORITY, mPriority); 211 } 212 213 if (mExpiry != null) { 214 attributionConfig.put(EXPIRY, mExpiry); 215 } 216 217 if (mFilterData != null) { 218 attributionConfig.put(FILTER_DATA, mFilterData.serializeAsJson(flags)); 219 } 220 221 if (mPostInstallExclusivityWindow != null) { 222 attributionConfig.put( 223 POST_INSTALL_EXCLUSIVITY_WINDOW, mPostInstallExclusivityWindow); 224 } 225 226 return attributionConfig; 227 } catch (JSONException e) { 228 LoggerFactory.getMeasurementLogger().d(e, "Serializing attribution config failed"); 229 return null; 230 } 231 } 232 233 /** Builder for {@link AttributionConfig}. */ 234 public static final class Builder { 235 private String mSourceAdtech; 236 private Pair<Long, Long> mSourcePriorityRange; 237 private List<FilterMap> mSourceFilters; 238 private List<FilterMap> mSourceNotFilters; 239 private Long mSourceExpiryOverride; 240 private Long mPriority; 241 private Long mExpiry; 242 private FilterMap mFilterData; 243 private Long mPostInstallExclusivityWindow; 244 Builder()245 public Builder() {} 246 247 /** 248 * Parses the string serialized json object under an {@link AttributionConfig}. 249 * 250 * @throws JSONException if JSON parsing fails 251 */ Builder(@onNull JSONObject attributionConfigsJson, Flags flags)252 public Builder(@NonNull JSONObject attributionConfigsJson, Flags flags) 253 throws JSONException { 254 if (attributionConfigsJson == null) { 255 throw new JSONException( 256 "AttributionConfig.Builder: Empty or null attributionConfigsJson"); 257 } 258 if (attributionConfigsJson.isNull(SOURCE_NETWORK)) { 259 throw new JSONException( 260 "AttributionConfig.Builder: Required field source_network is not present."); 261 } 262 263 mSourceAdtech = attributionConfigsJson.getString(SOURCE_NETWORK); 264 Filter filter = new Filter(flags); 265 if (!attributionConfigsJson.isNull(SOURCE_PRIORITY_RANGE)) { 266 JSONObject sourcePriorityRangeJson = 267 attributionConfigsJson.getJSONObject(SOURCE_PRIORITY_RANGE); 268 mSourcePriorityRange = 269 new Pair<>( 270 sourcePriorityRangeJson.getLong(START), 271 sourcePriorityRangeJson.getLong(END)); 272 } 273 if (!attributionConfigsJson.isNull(SOURCE_FILTERS)) { 274 JSONArray filterSet = 275 Filter.maybeWrapFilters(attributionConfigsJson, SOURCE_FILTERS); 276 mSourceFilters = filter.deserializeFilterSet(filterSet); 277 } 278 if (!attributionConfigsJson.isNull(SOURCE_NOT_FILTERS)) { 279 JSONArray filterSet = 280 Filter.maybeWrapFilters(attributionConfigsJson, SOURCE_NOT_FILTERS); 281 mSourceNotFilters = filter.deserializeFilterSet(filterSet); 282 } 283 if (!attributionConfigsJson.isNull(SOURCE_EXPIRY_OVERRIDE)) { 284 long override = attributionConfigsJson.getLong(SOURCE_EXPIRY_OVERRIDE); 285 mSourceExpiryOverride = 286 MathUtils.extractValidNumberInRange( 287 override, 288 flags.getMeasurementMinReportingRegisterSourceExpirationInSeconds(), 289 flags.getMeasurementMaxReportingRegisterSourceExpirationInSeconds()); 290 } 291 if (!attributionConfigsJson.isNull(PRIORITY)) { 292 mPriority = attributionConfigsJson.getLong(PRIORITY); 293 } 294 if (!attributionConfigsJson.isNull(EXPIRY)) { 295 long expiry = attributionConfigsJson.getLong(EXPIRY); 296 mExpiry = 297 MathUtils.extractValidNumberInRange( 298 expiry, 299 flags.getMeasurementMinReportingRegisterSourceExpirationInSeconds(), 300 flags 301 .getMeasurementMaxReportingRegisterSourceExpirationInSeconds()); 302 } 303 if (!attributionConfigsJson.isNull(FILTER_DATA)) { 304 mFilterData = 305 new FilterMap.Builder() 306 .buildFilterData( 307 attributionConfigsJson.optJSONObject(FILTER_DATA), flags) 308 .build(); 309 } 310 if (!attributionConfigsJson.isNull(POST_INSTALL_EXCLUSIVITY_WINDOW)) { 311 mPostInstallExclusivityWindow = 312 attributionConfigsJson.getLong(POST_INSTALL_EXCLUSIVITY_WINDOW); 313 } 314 } 315 316 /** See {@link AttributionConfig#getSourceAdtech()} */ 317 @NonNull setSourceAdtech(@onNull String sourceAdtech)318 public Builder setSourceAdtech(@NonNull String sourceAdtech) { 319 Objects.requireNonNull(sourceAdtech); 320 mSourceAdtech = sourceAdtech; 321 return this; 322 } 323 324 /** See {@link AttributionConfig#getSourcePriorityRange()} */ 325 @NonNull setSourcePriorityRange(@ullable Pair<Long, Long> sourcePriorityRange)326 public Builder setSourcePriorityRange(@Nullable Pair<Long, Long> sourcePriorityRange) { 327 mSourcePriorityRange = sourcePriorityRange; 328 return this; 329 } 330 331 /** See {@link AttributionConfig#getSourceFilters()} */ 332 @NonNull setSourceFilters(@ullable List<FilterMap> sourceFilters)333 public Builder setSourceFilters(@Nullable List<FilterMap> sourceFilters) { 334 mSourceFilters = sourceFilters; 335 return this; 336 } 337 338 /** See {@link AttributionConfig#getSourceNotFilters()} */ 339 @NonNull setSourceNotFilters(@ullable List<FilterMap> sourceNotFilters)340 public Builder setSourceNotFilters(@Nullable List<FilterMap> sourceNotFilters) { 341 mSourceNotFilters = sourceNotFilters; 342 return this; 343 } 344 345 /** See {@link AttributionConfig#getSourceExpiryOverride()} */ 346 @NonNull setSourceExpiryOverride(@ullable Long sourceExpiryOverride)347 public Builder setSourceExpiryOverride(@Nullable Long sourceExpiryOverride) { 348 mSourceExpiryOverride = sourceExpiryOverride; 349 return this; 350 } 351 352 /** See {@link AttributionConfig#getPriority()} */ 353 @NonNull setPriority(@ullable Long priority)354 public Builder setPriority(@Nullable Long priority) { 355 mPriority = priority; 356 return this; 357 } 358 359 /** See {@link AttributionConfig#getExpiry()} */ 360 @NonNull setExpiry(@ullable Long expiry)361 public Builder setExpiry(@Nullable Long expiry) { 362 mExpiry = expiry; 363 return this; 364 } 365 366 /** See {@link AttributionConfig#getFilterData()} */ 367 @NonNull setFilterData(@ullable FilterMap filterData)368 public Builder setFilterData(@Nullable FilterMap filterData) { 369 mFilterData = filterData; 370 return this; 371 } 372 373 /** See {@link AttributionConfig#getPostInstallExclusivityWindow()} */ 374 @NonNull setPostInstallExclusivityWindow( @ullable Long postInstallExclusivityWindow)375 public Builder setPostInstallExclusivityWindow( 376 @Nullable Long postInstallExclusivityWindow) { 377 mPostInstallExclusivityWindow = postInstallExclusivityWindow; 378 return this; 379 } 380 381 /** Build the {@link AttributionConfig}. */ 382 @NonNull build()383 public AttributionConfig build() { 384 Objects.requireNonNull(mSourceAdtech); 385 return new AttributionConfig(this); 386 } 387 } 388 389 /** Attribution Config field keys. */ 390 public interface AttributionConfigContract { 391 String SOURCE_NETWORK = "source_network"; 392 String SOURCE_PRIORITY_RANGE = "source_priority_range"; 393 String SOURCE_FILTERS = "source_filters"; 394 String SOURCE_NOT_FILTERS = "source_not_filters"; 395 String SOURCE_EXPIRY_OVERRIDE = "source_expiry_override"; 396 String PRIORITY = "priority"; 397 String EXPIRY = "expiry"; 398 String FILTER_DATA = "filter_data"; 399 String POST_INSTALL_EXCLUSIVITY_WINDOW = "post_install_exclusivity_window"; 400 String START = "start"; 401 String END = "end"; 402 } 403 } 404