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