1 /* 2 * Copyright 2023 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 androidx.webkit; 18 19 import androidx.annotation.RestrictTo; 20 21 import org.jspecify.annotations.NonNull; 22 import org.jspecify.annotations.Nullable; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.Objects; 27 28 /** 29 * Holds user-agent metadata information and uses to generate user-agent client 30 * hints. 31 * <p> 32 * This class is functionally equivalent to 33 * <a href="https://wicg.github.io/ua-client-hints/#interface">UADataValues</a>. 34 */ 35 public final class UserAgentMetadata { 36 /** 37 * Use this value for bitness to use the platform's default bitness value, which is an empty 38 * string for Android WebView. 39 */ 40 public static final int BITNESS_DEFAULT = 0; 41 42 private final List<BrandVersion> mBrandVersionList; 43 44 private final String mFullVersion; 45 private final String mPlatform; 46 private final String mPlatformVersion; 47 private final String mArchitecture; 48 private final String mModel; 49 private boolean mMobile = true; 50 private int mBitness = BITNESS_DEFAULT; 51 private boolean mWow64 = false; 52 53 @RestrictTo(RestrictTo.Scope.LIBRARY) UserAgentMetadata(@onNull List<BrandVersion> brandVersionList, @Nullable String fullVersion, @Nullable String platform, @Nullable String platformVersion, @Nullable String architecture, @Nullable String model, boolean mobile, int bitness, boolean wow64)54 private UserAgentMetadata(@NonNull List<BrandVersion> brandVersionList, 55 @Nullable String fullVersion, @Nullable String platform, 56 @Nullable String platformVersion, @Nullable String architecture, 57 @Nullable String model, 58 boolean mobile, 59 int bitness, boolean wow64) { 60 mBrandVersionList = brandVersionList; 61 mFullVersion = fullVersion; 62 mPlatform = platform; 63 mPlatformVersion = platformVersion; 64 mArchitecture = architecture; 65 mModel = model; 66 mMobile = mobile; 67 mBitness = bitness; 68 mWow64 = wow64; 69 } 70 71 /** 72 * Returns the current list of user-agent brand versions which are used to populate 73 * user-agent client hints {@code sec-ch-ua} and {@code sec-ch-ua-full-version-list}. Each 74 * {@link BrandVersion} object holds the brand name, brand major version and brand 75 * full version. 76 * <p> 77 * @see Builder#setBrandVersionList 78 * 79 */ getBrandVersionList()80 public @NonNull List<BrandVersion> getBrandVersionList() { 81 return mBrandVersionList; 82 } 83 84 /** 85 * Returns the value for the {@code sec-ch-ua-full-version} client hint. 86 * <p> 87 * @see Builder#setFullVersion 88 * 89 */ getFullVersion()90 public @Nullable String getFullVersion() { 91 return mFullVersion; 92 } 93 94 /** 95 * Returns the value for the {@code sec-ch-ua-platform} client hint. 96 * <p> 97 * @see Builder#setPlatform 98 * 99 */ getPlatform()100 public @Nullable String getPlatform() { 101 return mPlatform; 102 } 103 104 /** 105 * Returns the value for the {@code sec-ch-ua-platform-version} client hint. 106 * <p> 107 * @see Builder#setPlatformVersion 108 * 109 * @return Platform version string. 110 */ getPlatformVersion()111 public @Nullable String getPlatformVersion() { 112 return mPlatformVersion; 113 } 114 115 /** 116 * Returns the value for the {@code sec-ch-ua-arch} client hint. 117 * <p> 118 * @see Builder#setArchitecture 119 * 120 */ getArchitecture()121 public @Nullable String getArchitecture() { 122 return mArchitecture; 123 } 124 125 /** 126 * Returns the value for the {@code sec-ch-ua-model} client hint. 127 * <p> 128 * @see Builder#setModel 129 * 130 */ getModel()131 public @Nullable String getModel() { 132 return mModel; 133 } 134 135 /** 136 * Returns the value for the {@code sec-ch-ua-mobile} client hint. 137 * <p> 138 * @see Builder#setMobile 139 * 140 * @return A boolean indicates user-agent's device mobileness. 141 */ isMobile()142 public boolean isMobile() { 143 return mMobile; 144 } 145 146 /** 147 * Returns the value for the {@code sec-ch-ua-bitness} client hint. 148 * <p> 149 * @see Builder#setBitness 150 * 151 * @return An integer indicates the CPU bitness, the integer value will convert to string 152 * when generating the user-agent client hint, and {@link UserAgentMetadata#BITNESS_DEFAULT} 153 * means an empty string. 154 */ getBitness()155 public int getBitness() { 156 return mBitness; 157 } 158 159 /** 160 * Returns the value for the {@code sec-ch-ua-wow64} client hint. 161 * <p> 162 * @see Builder#setWow64 163 * 164 * @return A boolean to indicate whether user-agent's binary is running in 32-bit mode on 165 * 64-bit Windows. 166 */ isWow64()167 public boolean isWow64() { 168 return mWow64; 169 } 170 171 /** 172 * Two UserAgentMetadata objects are equal only if all the metadata values are equal. 173 */ 174 @Override equals(Object o)175 public boolean equals(Object o) { 176 if (this == o) return true; 177 if (!(o instanceof UserAgentMetadata)) return false; 178 UserAgentMetadata that = (UserAgentMetadata) o; 179 return mMobile == that.mMobile && mBitness == that.mBitness && mWow64 == that.mWow64 180 && Objects.equals(mBrandVersionList, that.mBrandVersionList) 181 && Objects.equals(mFullVersion, that.mFullVersion) 182 && Objects.equals(mPlatform, that.mPlatform) && Objects.equals( 183 mPlatformVersion, that.mPlatformVersion) && Objects.equals(mArchitecture, 184 that.mArchitecture) && Objects.equals(mModel, that.mModel); 185 } 186 187 @Override hashCode()188 public int hashCode() { 189 return Objects.hash(mBrandVersionList, mFullVersion, mPlatform, mPlatformVersion, 190 mArchitecture, mModel, mMobile, mBitness, mWow64); 191 } 192 193 /** 194 * Class that holds brand name, major version and full version. Brand name and major version 195 * used to generated user-agent client hint {@code sec-cu-ua}. Brand name and full version 196 * used to generated user-agent client hint {@code sec-ch-ua-full-version-list}. 197 * <p> 198 * This class is functionally equivalent to 199 * <a href="https://wicg.github.io/ua-client-hints/#interface">NavigatorUABrandVersion</a>. 200 * 201 */ 202 public static final class BrandVersion { 203 private final String mBrand; 204 private final String mMajorVersion; 205 private final String mFullVersion; 206 207 @RestrictTo(RestrictTo.Scope.LIBRARY) BrandVersion(@onNull String brand, @NonNull String majorVersion, @NonNull String fullVersion)208 private BrandVersion(@NonNull String brand, @NonNull String majorVersion, 209 @NonNull String fullVersion) { 210 mBrand = brand; 211 mMajorVersion = majorVersion; 212 mFullVersion = fullVersion; 213 } 214 215 /** 216 * Returns the brand of user-agent brand version tuple. 217 * 218 */ getBrand()219 public @NonNull String getBrand() { 220 return mBrand; 221 } 222 223 /** 224 * Returns the major version of user-agent brand version tuple. 225 * 226 */ getMajorVersion()227 public @NonNull String getMajorVersion() { 228 return mMajorVersion; 229 } 230 231 /** 232 * Returns the full version of user-agent brand version tuple. 233 * 234 */ getFullVersion()235 public @NonNull String getFullVersion() { 236 return mFullVersion; 237 } 238 239 @Override toString()240 public @NonNull String toString() { 241 return mBrand + "," + mMajorVersion + "," + mFullVersion; 242 } 243 244 /** 245 * Two BrandVersion objects are equal only if brand name, major version and full version 246 * are equal. 247 */ 248 @Override equals(Object o)249 public boolean equals(Object o) { 250 if (this == o) return true; 251 if (!(o instanceof BrandVersion)) return false; 252 BrandVersion that = (BrandVersion) o; 253 return Objects.equals(mBrand, that.mBrand) && Objects.equals(mMajorVersion, 254 that.mMajorVersion) && Objects.equals(mFullVersion, that.mFullVersion); 255 } 256 257 @Override hashCode()258 public int hashCode() { 259 return Objects.hash(mBrand, mMajorVersion, mFullVersion); 260 } 261 262 /** 263 * Builder used to create {@link BrandVersion} objects. 264 * <p> 265 * Examples: 266 * <pre class="prettyprint"> 267 * // Create a setting with a brand version contains brand name: myBrand, 268 * // major version: 100, full version: 100.1.1.1. 269 * new BrandVersion.Builder().setBrand("myBrand") 270 * .setMajorVersion("100") 271 * .setFullVersion("100.1.1.1") 272 * .build(); 273 * </pre> 274 */ 275 public static final class Builder { 276 private String mBrand; 277 private String mMajorVersion; 278 private String mFullVersion; 279 280 /** 281 * Create an empty BrandVersion Builder. 282 */ Builder()283 public Builder() { 284 } 285 286 /** 287 * Create a BrandVersion Builder from an existing BrandVersion object. 288 */ Builder(@onNull BrandVersion brandVersion)289 public Builder(@NonNull BrandVersion brandVersion) { 290 mBrand = brandVersion.getBrand(); 291 mMajorVersion = brandVersion.getMajorVersion(); 292 mFullVersion = brandVersion.getFullVersion(); 293 } 294 295 /** 296 * Builds the current brand, majorVersion and fullVersion into a BrandVersion object. 297 * 298 * @return The BrandVersion object represented by this Builder. 299 * @throws IllegalStateException If any of the value in brand, majorVersion and 300 * fullVersion is null or blank. 301 */ build()302 public @NonNull BrandVersion build() { 303 if (mBrand == null || mBrand.trim().isEmpty() 304 || mMajorVersion == null || mMajorVersion.trim().isEmpty() 305 || mFullVersion == null || mFullVersion.trim().isEmpty()) { 306 throw new IllegalStateException("Brand name, major version and full version " 307 + "should not be null or blank."); 308 } 309 return new BrandVersion(mBrand, mMajorVersion, mFullVersion); 310 } 311 312 /** 313 * Sets the BrandVersion's brand. The brand should not be blank. 314 * 315 * @param brand The brand is used to generate user-agent client hint 316 * {@code sec-ch-ua} and {@code sec-ch-ua-full-version-list}. 317 * 318 */ setBrand(@onNull String brand)319 public BrandVersion.@NonNull Builder setBrand(@NonNull String brand) { 320 if (brand.trim().isEmpty()) { 321 throw new IllegalArgumentException("Brand should not be blank."); 322 } 323 mBrand = brand; 324 return this; 325 } 326 327 /** 328 * Sets the BrandVersion's majorVersion. The majorVersion should not be blank. 329 * 330 * @param majorVersion The majorVersion is used to generate user-agent client hint 331 * {@code sec-ch-ua}. 332 * 333 */ setMajorVersion(@onNull String majorVersion)334 public BrandVersion.@NonNull Builder setMajorVersion(@NonNull String majorVersion) { 335 if (majorVersion.trim().isEmpty()) { 336 throw new IllegalArgumentException("MajorVersion should not be blank."); 337 } 338 mMajorVersion = majorVersion; 339 return this; 340 } 341 342 /** 343 * Sets the BrandVersion's fullVersion. The fullVersion should not be blank. 344 * 345 * @param fullVersion The brand is used to generate user-agent client hint 346 * {@code sec-ch-ua-full-version-list}. 347 * 348 */ setFullVersion(@onNull String fullVersion)349 public BrandVersion.@NonNull Builder setFullVersion(@NonNull String fullVersion) { 350 if (fullVersion.trim().isEmpty()) { 351 throw new IllegalArgumentException("FullVersion should not be blank."); 352 } 353 mFullVersion = fullVersion; 354 return this; 355 } 356 } 357 } 358 359 /** 360 * Builder used to create {@link UserAgentMetadata} objects. 361 * <p> 362 * Examples: 363 * <pre class="prettyprint"> 364 * // Create a setting with default options. 365 * new UserAgentMetadata.Builder().build(); 366 * <p> 367 * // Create a setting with a brand version contains brand name: myBrand, major version: 100, 368 * // full version: 100.1.1.1. 369 * BrandVersion brandVersion = new BrandVersion.Builder().setBrand("myBrand") 370 * .setMajorVersion("100") 371 * .setFullVersion("100.1.1.1") 372 * .build(); 373 * new UserAgentMetadata.Builder().setBrandVersionList(Collections.singletonList(brandVersion)) 374 * .build(); 375 * <p> 376 * // Create a setting brand version, platform, platform version and bitness. 377 * new UserAgentMetadata.Builder().setBrandVersionList(Collections.singletonList(brandVersion)) 378 * .setPlatform("myPlatform") 379 * .setPlatform("1.1.1.1") 380 * .setBitness(BITNESS_64) 381 * .build(); 382 * </pre> 383 */ 384 public static final class Builder { 385 private List<BrandVersion> mBrandVersionList = new ArrayList<>(); 386 private String mFullVersion; 387 private String mPlatform; 388 private String mPlatformVersion; 389 private String mArchitecture; 390 private String mModel; 391 private boolean mMobile = true; 392 private int mBitness = BITNESS_DEFAULT; 393 private boolean mWow64 = false; 394 395 /** 396 * Create an empty UserAgentMetadata Builder. 397 */ Builder()398 public Builder() { 399 } 400 401 /** 402 * Create a UserAgentMetadata Builder from an existing UserAgentMetadata object. 403 */ Builder(@onNull UserAgentMetadata uaMetadata)404 public Builder(@NonNull UserAgentMetadata uaMetadata) { 405 mBrandVersionList = uaMetadata.getBrandVersionList(); 406 mFullVersion = uaMetadata.getFullVersion(); 407 mPlatform = uaMetadata.getPlatform(); 408 mPlatformVersion = uaMetadata.getPlatformVersion(); 409 mArchitecture = uaMetadata.getArchitecture(); 410 mModel = uaMetadata.getModel(); 411 mMobile = uaMetadata.isMobile(); 412 mBitness = uaMetadata.getBitness(); 413 mWow64 = uaMetadata.isWow64(); 414 } 415 416 /** 417 * Builds the current settings into a UserAgentMetadata object. 418 * 419 * @return The UserAgentMetadata object represented by this Builder 420 */ build()421 public @NonNull UserAgentMetadata build() { 422 return new UserAgentMetadata(mBrandVersionList, mFullVersion, mPlatform, 423 mPlatformVersion, mArchitecture, mModel, mMobile, mBitness, mWow64); 424 } 425 426 /** 427 * Sets user-agent metadata brands and their versions. The brand name, major version and 428 * full version should not be blank. The default value is an empty list which means the 429 * system default user-agent metadata brands and versions will be used to generate the 430 * user-agent client hints. 431 * 432 * @param brandVersions a list of {@link BrandVersion} used to generated user-agent client 433 * hints {@code sec-cu-ua} and {@code sec-ch-ua-full-version-list}. 434 * 435 */ setBrandVersionList(@onNull List<BrandVersion> brandVersions)436 public @NonNull Builder setBrandVersionList(@NonNull List<BrandVersion> brandVersions) { 437 mBrandVersionList = brandVersions; 438 return this; 439 } 440 441 /** 442 * Sets the user-agent metadata full version. The full version should not be blank, even 443 * though the <a href="https://wicg.github.io/ua-client-hints">spec<a/> about brand full 444 * version could be empty. A blank full version could cause inconsistent brands when 445 * generating brand version related user-agent client hints. It also provides a bad 446 * experience for developers when processing the brand full version. If null is provided, 447 * the system default value will be used to generate the client hint. 448 * 449 * @param fullVersion The full version is used to generate user-agent client hint 450 * {@code sec-ch-ua-full-version}. 451 * 452 */ setFullVersion(@ullable String fullVersion)453 public @NonNull Builder setFullVersion(@Nullable String fullVersion) { 454 if (fullVersion == null) { 455 mFullVersion = null; 456 return this; 457 } 458 if (fullVersion.trim().isEmpty()) { 459 throw new IllegalArgumentException("Full version should not be blank."); 460 } 461 mFullVersion = fullVersion; 462 return this; 463 } 464 465 /** 466 * Sets the user-agent metadata platform. The platform should not be blank. If null is 467 * provided, the system default value will be used to generate the client hint. 468 * 469 * @param platform The platform is used to generate user-agent client hint 470 * {@code sec-ch-ua-platform}. 471 * 472 */ setPlatform(@ullable String platform)473 public @NonNull Builder setPlatform(@Nullable String platform) { 474 if (platform == null) { 475 mPlatform = null; 476 return this; 477 } 478 if (platform.trim().isEmpty()) { 479 throw new IllegalArgumentException("Platform should not be blank."); 480 } 481 mPlatform = platform; 482 return this; 483 } 484 485 /** 486 * Sets the user-agent metadata platform version. If null is provided, the system default 487 * value will be used to generate the client hint. 488 * 489 * @param platformVersion The platform version is used to generate user-agent client 490 * hint {@code sec-ch-ua-platform-version}. 491 * 492 */ setPlatformVersion(@ullable String platformVersion)493 public @NonNull Builder setPlatformVersion(@Nullable String platformVersion) { 494 mPlatformVersion = platformVersion; 495 return this; 496 } 497 498 /** 499 * Sets the user-agent metadata architecture. If null is provided, the system default 500 * value will be used to generate the client hint. 501 * 502 * @param architecture The architecture is used to generate user-agent client hint 503 * {@code sec-ch-ua-arch}. 504 * 505 */ setArchitecture(@ullable String architecture)506 public @NonNull Builder setArchitecture(@Nullable String architecture) { 507 mArchitecture = architecture; 508 return this; 509 } 510 511 /** 512 * Sets the user-agent metadata model. If null is provided, the system default value will 513 * be used to generate the client hint. 514 * 515 * @param model The model is used to generate user-agent client hint 516 * {@code sec-ch-ua-model}. 517 * 518 */ setModel(@ullable String model)519 public @NonNull Builder setModel(@Nullable String model) { 520 mModel = model; 521 return this; 522 } 523 524 /** 525 * Sets the user-agent metadata mobile, the default value is true. 526 * 527 * @param mobile The mobile is used to generate user-agent client hint 528 * {@code sec-ch-ua-mobile}. 529 * 530 */ setMobile(boolean mobile)531 public @NonNull Builder setMobile(boolean mobile) { 532 mMobile = mobile; 533 return this; 534 } 535 536 /** 537 * Sets the user-agent metadata bitness, the default value is 538 * {@link UserAgentMetadata#BITNESS_DEFAULT}, which indicates an empty string for 539 * {@code sec-ch-ua-bitness}. 540 * 541 * @param bitness The bitness is used to generate user-agent client hint 542 * {@code sec-ch-ua-bitness}. 543 * 544 */ setBitness(int bitness)545 public @NonNull Builder setBitness(int bitness) { 546 mBitness = bitness; 547 return this; 548 } 549 550 /** 551 * Sets the user-agent metadata wow64, the default is false. 552 * 553 * @param wow64 The wow64 is used to generate user-agent client hint 554 * {@code sec-ch-ua-wow64}. 555 * 556 */ setWow64(boolean wow64)557 public @NonNull Builder setWow64(boolean wow64) { 558 mWow64 = wow64; 559 return this; 560 } 561 } 562 } 563