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 android.adservices.measurement;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.Uri;
22 import android.os.Build;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.view.InputEvent;
26 
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Objects;
30 
31 /** Class to hold input to measurement source registration calls from web context. */
32 public final class WebSourceRegistrationRequest implements Parcelable {
33     private static final String ANDROID_APP_SCHEME = "android-app";
34     private static final int WEB_SOURCE_PARAMS_MAX_COUNT = 80;
35 
36     /** Creator for Paracelable (via reflection). */
37     @NonNull
38     public static final Parcelable.Creator<WebSourceRegistrationRequest> CREATOR =
39             new Parcelable.Creator<WebSourceRegistrationRequest>() {
40                 @Override
41                 public WebSourceRegistrationRequest createFromParcel(Parcel in) {
42                     return new WebSourceRegistrationRequest(in);
43                 }
44 
45                 @Override
46                 public WebSourceRegistrationRequest[] newArray(int size) {
47                     return new WebSourceRegistrationRequest[size];
48                 }
49             };
50     /** Registration info to fetch sources. */
51     @NonNull private final List<WebSourceParams> mWebSourceParams;
52 
53     /** Top level origin of publisher. */
54     @NonNull private final Uri mTopOriginUri;
55 
56     /**
57      * User Interaction {@link InputEvent} used by the AttributionReporting API to distinguish
58      * clicks from views.
59      */
60     @Nullable private final InputEvent mInputEvent;
61 
62     /**
63      * App destination of the source. It is the android app {@link Uri} where corresponding
64      * conversion is expected. This field is compared with the corresponding field in Source
65      * Registration Response, if matching fails the registration is rejected. If null is provided,
66      * no destination matching will be performed.
67      */
68     @Nullable private final Uri mAppDestination;
69 
70     /**
71      * Web destination of the source. It is the website {@link Uri} where corresponding conversion
72      * is expected. This field is compared with the corresponding field in Source Registration
73      * Response, if matching fails the registration is rejected. If null is provided, no destination
74      * matching will be performed.
75      */
76     @Nullable private final Uri mWebDestination;
77 
78     /** Verified destination by the caller. This is where the user actually landed. */
79     @Nullable private final Uri mVerifiedDestination;
80 
WebSourceRegistrationRequest(@onNull Builder builder)81     private WebSourceRegistrationRequest(@NonNull Builder builder) {
82         mWebSourceParams = builder.mWebSourceParams;
83         mInputEvent = builder.mInputEvent;
84         mTopOriginUri = builder.mTopOriginUri;
85         mAppDestination = builder.mAppDestination;
86         mWebDestination = builder.mWebDestination;
87         mVerifiedDestination = builder.mVerifiedDestination;
88     }
89 
WebSourceRegistrationRequest(@onNull Parcel in)90     private WebSourceRegistrationRequest(@NonNull Parcel in) {
91         Objects.requireNonNull(in);
92         ArrayList<WebSourceParams> sourceRegistrations = new ArrayList<>();
93         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
94             in.readList(sourceRegistrations, WebSourceParams.class.getClassLoader());
95         } else {
96             in.readList(
97                     sourceRegistrations,
98                     WebSourceParams.class.getClassLoader(),
99                     WebSourceParams.class);
100         }
101         mWebSourceParams = sourceRegistrations;
102         mTopOriginUri = Uri.CREATOR.createFromParcel(in);
103         if (in.readBoolean()) {
104             mInputEvent = InputEvent.CREATOR.createFromParcel(in);
105         } else {
106             mInputEvent = null;
107         }
108         if (in.readBoolean()) {
109             mAppDestination = Uri.CREATOR.createFromParcel(in);
110         } else {
111             mAppDestination = null;
112         }
113         if (in.readBoolean()) {
114             mWebDestination = Uri.CREATOR.createFromParcel(in);
115         } else {
116             mWebDestination = null;
117         }
118         if (in.readBoolean()) {
119             mVerifiedDestination = Uri.CREATOR.createFromParcel(in);
120         } else {
121             mVerifiedDestination = null;
122         }
123     }
124 
125     @Override
equals(Object o)126     public boolean equals(Object o) {
127         if (this == o) return true;
128         if (!(o instanceof WebSourceRegistrationRequest)) return false;
129         WebSourceRegistrationRequest that = (WebSourceRegistrationRequest) o;
130         return Objects.equals(mWebSourceParams, that.mWebSourceParams)
131                 && Objects.equals(mTopOriginUri, that.mTopOriginUri)
132                 && Objects.equals(mInputEvent, that.mInputEvent)
133                 && Objects.equals(mAppDestination, that.mAppDestination)
134                 && Objects.equals(mWebDestination, that.mWebDestination)
135                 && Objects.equals(mVerifiedDestination, that.mVerifiedDestination);
136     }
137 
138     @Override
hashCode()139     public int hashCode() {
140         return Objects.hash(
141                 mWebSourceParams,
142                 mTopOriginUri,
143                 mInputEvent,
144                 mAppDestination,
145                 mWebDestination,
146                 mVerifiedDestination);
147     }
148 
149     /** Getter for source params. */
150     @NonNull
getSourceParams()151     public List<WebSourceParams> getSourceParams() {
152         return mWebSourceParams;
153     }
154 
155     /** Getter for top origin Uri. */
156     @NonNull
getTopOriginUri()157     public Uri getTopOriginUri() {
158         return mTopOriginUri;
159     }
160 
161     /** Getter for input event. */
162     @Nullable
getInputEvent()163     public InputEvent getInputEvent() {
164         return mInputEvent;
165     }
166 
167     /**
168      * Getter for the app destination. It is the android app {@link Uri} where corresponding
169      * conversion is expected. At least one of app destination or web destination is required.
170      */
171     @Nullable
getAppDestination()172     public Uri getAppDestination() {
173         return mAppDestination;
174     }
175 
176     /**
177      * Getter for web destination. It is the website {@link Uri} where corresponding conversion is
178      * expected. At least one of app destination or web destination is required.
179      */
180     @Nullable
getWebDestination()181     public Uri getWebDestination() {
182         return mWebDestination;
183     }
184 
185     /** Getter for verified destination. */
186     @Nullable
getVerifiedDestination()187     public Uri getVerifiedDestination() {
188         return mVerifiedDestination;
189     }
190 
191     @Override
describeContents()192     public int describeContents() {
193         return 0;
194     }
195 
196     @Override
writeToParcel(@onNull Parcel out, int flags)197     public void writeToParcel(@NonNull Parcel out, int flags) {
198         Objects.requireNonNull(out);
199         out.writeList(mWebSourceParams);
200         mTopOriginUri.writeToParcel(out, flags);
201 
202         if (mInputEvent != null) {
203             out.writeBoolean(true);
204             mInputEvent.writeToParcel(out, flags);
205         } else {
206             out.writeBoolean(false);
207         }
208         if (mAppDestination != null) {
209             out.writeBoolean(true);
210             mAppDestination.writeToParcel(out, flags);
211         } else {
212             out.writeBoolean(false);
213         }
214         if (mWebDestination != null) {
215             out.writeBoolean(true);
216             mWebDestination.writeToParcel(out, flags);
217         } else {
218             out.writeBoolean(false);
219         }
220         if (mVerifiedDestination != null) {
221             out.writeBoolean(true);
222             mVerifiedDestination.writeToParcel(out, flags);
223         } else {
224             out.writeBoolean(false);
225         }
226     }
227 
228     /** Builder for {@link WebSourceRegistrationRequest}. */
229     public static final class Builder {
230         /** Registration info to fetch sources. */
231         @NonNull private final List<WebSourceParams> mWebSourceParams;
232         /** Top origin {@link Uri} of publisher. */
233         @NonNull private final Uri mTopOriginUri;
234         /**
235          * User Interaction InputEvent used by the AttributionReporting API to distinguish clicks
236          * from views.
237          */
238         @Nullable private InputEvent mInputEvent;
239         /**
240          * App destination of the source. It is the android app {@link Uri} where corresponding
241          * conversion is expected.
242          */
243         @Nullable private Uri mAppDestination;
244         /**
245          * Web destination of the source. It is the website {@link Uri} where corresponding
246          * conversion is expected.
247          */
248         @Nullable private Uri mWebDestination;
249         /**
250          * Verified destination by the caller. If available, sources should be checked against it.
251          */
252         @Nullable private Uri mVerifiedDestination;
253 
254         /**
255          * Builder constructor for {@link WebSourceRegistrationRequest}.
256          *
257          * @param webSourceParams source parameters containing source registration parameters, the
258          *     list should not be empty
259          * @param topOriginUri source publisher {@link Uri}
260          */
Builder(@onNull List<WebSourceParams> webSourceParams, @NonNull Uri topOriginUri)261         public Builder(@NonNull List<WebSourceParams> webSourceParams, @NonNull Uri topOriginUri) {
262             Objects.requireNonNull(webSourceParams);
263             Objects.requireNonNull(topOriginUri);
264             if (webSourceParams.isEmpty() || webSourceParams.size() > WEB_SOURCE_PARAMS_MAX_COUNT) {
265                 throw new IllegalArgumentException(
266                         "web source params size is not within bounds, size: "
267                                 + webSourceParams.size());
268             }
269             mWebSourceParams = webSourceParams;
270             mTopOriginUri = topOriginUri;
271         }
272 
273         /**
274          * Setter for input event.
275          *
276          * @param inputEvent User Interaction InputEvent used by the AttributionReporting API to
277          *     distinguish clicks from views.
278          * @return builder
279          */
280         @NonNull
setInputEvent(@ullable InputEvent inputEvent)281         public Builder setInputEvent(@Nullable InputEvent inputEvent) {
282             mInputEvent = inputEvent;
283             return this;
284         }
285 
286         /**
287          * Setter for app destination. It is the android app {@link Uri} where corresponding
288          * conversion is expected. At least one of app destination or web destination is required.
289          *
290          * @param appDestination app destination {@link Uri}
291          * @return builder
292          */
293         @NonNull
setAppDestination(@ullable Uri appDestination)294         public Builder setAppDestination(@Nullable Uri appDestination) {
295             if (appDestination != null) {
296                 String scheme = appDestination.getScheme();
297                 Uri destination;
298                 if (scheme == null) {
299                     destination = Uri.parse(ANDROID_APP_SCHEME + "://" + appDestination);
300                 } else if (!scheme.equals(ANDROID_APP_SCHEME)) {
301                     throw new IllegalArgumentException(
302                             String.format(
303                                     "appDestination scheme must be %s " + "or null. Received: %s",
304                                     ANDROID_APP_SCHEME, scheme));
305                 } else {
306                     destination = appDestination;
307                 }
308                 mAppDestination = destination;
309             }
310             return this;
311         }
312 
313         /**
314          * Setter for web destination. It is the website {@link Uri} where corresponding conversion
315          * is expected. At least one of app destination or web destination is required.
316          *
317          * @param webDestination web destination {@link Uri}
318          * @return builder
319          */
320         @NonNull
setWebDestination(@ullable Uri webDestination)321         public Builder setWebDestination(@Nullable Uri webDestination) {
322             if (webDestination != null) {
323                 validateScheme("Web destination", webDestination);
324                 mWebDestination = webDestination;
325             }
326             return this;
327         }
328 
329         /**
330          * Setter for verified destination.
331          *
332          * @param verifiedDestination verified destination
333          * @return builder
334          */
335         @NonNull
setVerifiedDestination(@ullable Uri verifiedDestination)336         public Builder setVerifiedDestination(@Nullable Uri verifiedDestination) {
337             mVerifiedDestination = verifiedDestination;
338             return this;
339         }
340 
341         /** Pre-validates parameters and builds {@link WebSourceRegistrationRequest}. */
342         @NonNull
build()343         public WebSourceRegistrationRequest build() {
344             return new WebSourceRegistrationRequest(this);
345         }
346     }
347 
validateScheme(String name, Uri uri)348     private static void validateScheme(String name, Uri uri) throws IllegalArgumentException {
349         if (uri.getScheme() == null) {
350             throw new IllegalArgumentException(name + " must have a scheme.");
351         }
352     }
353 }
354