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