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