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