UserAgentMetadata.java

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.webkit;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Holds user-agent metadata information and uses to generate user-agent client
 * hints.
 * <p>
 * This class is functionally equivalent to
 * <a href="https://wicg.github.io/ua-client-hints/#interface">UADataValues</a>.
 */
public final class UserAgentMetadata {
    /**
     * Use this value for bitness to use the platform's default bitness value, which is an empty
     * string for Android WebView.
     */
    public static final int BITNESS_DEFAULT = 0;

    private final List<BrandVersion> mBrandVersionList;

    private final String mFullVersion;
    private final String mPlatform;
    private final String mPlatformVersion;
    private final String mArchitecture;
    private final String mModel;
    private boolean mMobile = true;
    private int mBitness = BITNESS_DEFAULT;
    private boolean mWow64 = false;

    @RestrictTo(RestrictTo.Scope.LIBRARY)
    private UserAgentMetadata(@NonNull List<BrandVersion> brandVersionList,
            @Nullable String fullVersion, @Nullable String platform,
            @Nullable String platformVersion, @Nullable String architecture,
            @Nullable String model,
            boolean mobile,
            int bitness, boolean wow64) {
        mBrandVersionList = brandVersionList;
        mFullVersion = fullVersion;
        mPlatform = platform;
        mPlatformVersion = platformVersion;
        mArchitecture = architecture;
        mModel = model;
        mMobile = mobile;
        mBitness = bitness;
        mWow64 = wow64;
    }

    /**
     * Returns the current list of user-agent brand versions which are used to populate
     * user-agent client hints {@code sec-ch-ua} and {@code sec-ch-ua-full-version-list}. Each
     * {@link BrandVersion} object holds the brand name, brand major version and brand
     * full version.
     * <p>
     * @see Builder#setBrandVersionList
     *
     */
    @NonNull
    public List<BrandVersion> getBrandVersionList() {
        return mBrandVersionList;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-full-version} client hint.
     * <p>
     * @see Builder#setFullVersion
     *
     */
    @Nullable
    public String getFullVersion() {
        return mFullVersion;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-platform} client hint.
     * <p>
     * @see Builder#setPlatform
     *
     */
    @Nullable
    public String getPlatform() {
        return mPlatform;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-platform-version} client hint.
     * <p>
     * @see Builder#setPlatformVersion
     *
     * @return Platform version string.
     */
    @Nullable
    public String getPlatformVersion() {
        return mPlatformVersion;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-arch} client hint.
     * <p>
     * @see Builder#setArchitecture
     *
     */
    @Nullable
    public String getArchitecture() {
        return mArchitecture;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-model} client hint.
     * <p>
     * @see Builder#setModel
     *
     */
    @Nullable
    public String getModel() {
        return mModel;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-mobile} client hint.
     * <p>
     * @see Builder#setMobile
     *
     * @return A boolean indicates user-agent's device mobileness.
     */
    public boolean isMobile() {
        return mMobile;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-bitness} client hint.
     * <p>
     * @see Builder#setBitness
     *
     * @return An integer indicates the CPU bitness, the integer value will convert to string
     * when generating the user-agent client hint, and {@link UserAgentMetadata#BITNESS_DEFAULT}
     * means an empty string.
     */
    public int getBitness() {
        return mBitness;
    }

    /**
     * Returns the value for the {@code sec-ch-ua-wow64} client hint.
     * <p>
     * @see Builder#setWow64
     *
     * @return A boolean to indicate whether user-agent's binary is running in 32-bit mode on
     * 64-bit Windows.
     */
    public boolean isWow64() {
        return mWow64;
    }

    /**
     * Two UserAgentMetadata objects are equal only if all the metadata values are equal.
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof UserAgentMetadata)) return false;
        UserAgentMetadata that = (UserAgentMetadata) o;
        return mMobile == that.mMobile && mBitness == that.mBitness && mWow64 == that.mWow64
                && Objects.equals(mBrandVersionList, that.mBrandVersionList)
                && Objects.equals(mFullVersion, that.mFullVersion)
                && Objects.equals(mPlatform, that.mPlatform) && Objects.equals(
                mPlatformVersion, that.mPlatformVersion) && Objects.equals(mArchitecture,
                that.mArchitecture) && Objects.equals(mModel, that.mModel);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mBrandVersionList, mFullVersion, mPlatform, mPlatformVersion,
                mArchitecture, mModel, mMobile, mBitness, mWow64);
    }

    /**
     * Class that holds brand name, major version and full version. Brand name and major version
     * used to generated user-agent client hint {@code sec-cu-ua}. Brand name and full version
     * used to generated user-agent client hint {@code sec-ch-ua-full-version-list}.
     * <p>
     * This class is functionally equivalent to
     * <a href="https://wicg.github.io/ua-client-hints/#interface">NavigatorUABrandVersion</a>.
     *
     */
    public static final class BrandVersion {
        private final String mBrand;
        private final String mMajorVersion;
        private final String mFullVersion;

        @RestrictTo(RestrictTo.Scope.LIBRARY)
        private BrandVersion(@NonNull String brand, @NonNull String majorVersion,
                @NonNull String fullVersion) {
            mBrand = brand;
            mMajorVersion = majorVersion;
            mFullVersion = fullVersion;
        }

        /**
         * Returns the brand of user-agent brand version tuple.
         *
         */
        @NonNull
        public String getBrand() {
            return mBrand;
        }

        /**
         * Returns the major version of user-agent brand version tuple.
         *
         */
        @NonNull
        public String getMajorVersion() {
            return mMajorVersion;
        }

        /**
         * Returns the full version of user-agent brand version tuple.
         *
         */
        @NonNull
        public String getFullVersion() {
            return mFullVersion;
        }

        @NonNull
        @Override
        public String toString() {
            return mBrand + "," + mMajorVersion + "," + mFullVersion;
        }

        /**
         * Two BrandVersion objects are equal only if brand name, major version and full version
         * are equal.
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof BrandVersion)) return false;
            BrandVersion that = (BrandVersion) o;
            return Objects.equals(mBrand, that.mBrand) && Objects.equals(mMajorVersion,
                    that.mMajorVersion) && Objects.equals(mFullVersion, that.mFullVersion);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mBrand, mMajorVersion, mFullVersion);
        }

        /**
         * Builder used to create {@link BrandVersion} objects.
         * <p>
         * Examples:
         * <pre class="prettyprint">
         *  // Create a setting with a brand version contains brand name: myBrand,
         *  // major version: 100, full version: 100.1.1.1.
         *  new BrandVersion.Builder().setBrand("myBrand")
         *                            .setMajorVersion("100")
         *                            .setFullVersion("100.1.1.1")
         *                            .build();
         * </pre>
         */
        public static final class Builder {
            private String mBrand;
            private String mMajorVersion;
            private String mFullVersion;

            /**
             * Create an empty BrandVersion Builder.
             */
            public Builder() {
            }

            /**
             * Create a BrandVersion Builder from an existing BrandVersion object.
             */
            public Builder(@NonNull BrandVersion brandVersion) {
                mBrand = brandVersion.getBrand();
                mMajorVersion = brandVersion.getMajorVersion();
                mFullVersion = brandVersion.getFullVersion();
            }

            /**
             * Builds the current brand, majorVersion and fullVersion into a BrandVersion object.
             *
             * @return The BrandVersion object represented by this Builder.
             * @throws IllegalStateException If any of the value in brand, majorVersion and
             *                               fullVersion is null or blank.
             */
            @NonNull
            public BrandVersion build() {
                if (mBrand == null || mBrand.trim().isEmpty()
                        || mMajorVersion == null || mMajorVersion.trim().isEmpty()
                        || mFullVersion == null || mFullVersion.trim().isEmpty()) {
                    throw new IllegalStateException("Brand name, major version and full version "
                            + "should not be null or blank.");
                }
                return new BrandVersion(mBrand, mMajorVersion, mFullVersion);
            }

            /**
             * Sets the BrandVersion's brand. The brand should not be blank.
             *
             * @param brand The brand is used to generate user-agent client hint
             *              {@code sec-ch-ua} and {@code sec-ch-ua-full-version-list}.
             *
             */
            @NonNull
            public BrandVersion.Builder setBrand(@NonNull String brand) {
                if (brand.trim().isEmpty()) {
                    throw new IllegalArgumentException("Brand should not be blank.");
                }
                mBrand = brand;
                return this;
            }

            /**
             * Sets the BrandVersion's majorVersion. The majorVersion should not be blank.
             *
             * @param majorVersion The majorVersion is used to generate user-agent client hint
             *                     {@code sec-ch-ua}.
             *
             */
            @NonNull
            public BrandVersion.Builder setMajorVersion(@NonNull String majorVersion) {
                if (majorVersion.trim().isEmpty()) {
                    throw new IllegalArgumentException("MajorVersion should not be blank.");
                }
                mMajorVersion = majorVersion;
                return this;
            }

            /**
             * Sets the BrandVersion's fullVersion. The fullVersion should not be blank.
             *
             * @param fullVersion The brand is used to generate user-agent client hint
             *                    {@code sec-ch-ua-full-version-list}.
             *
             */
            @NonNull
            public BrandVersion.Builder setFullVersion(@NonNull String fullVersion) {
                if (fullVersion.trim().isEmpty()) {
                    throw new IllegalArgumentException("FullVersion should not be blank.");
                }
                mFullVersion = fullVersion;
                return this;
            }
        }
    }

    /**
     * Builder used to create {@link UserAgentMetadata} objects.
     * <p>
     * Examples:
     * <pre class="prettyprint">
     *  // Create a setting with default options.
     *  new UserAgentMetadata.Builder().build();
     *
     *  // Create a setting with a brand version contains brand name: myBrand, major version: 100,
     *  // full version: 100.1.1.1.
     *  BrandVersion brandVersion = new BrandVersion.Builder().setBrand("myBrand")
     *                                                        .setMajorVersion("100")
     *                                                        .setFullVersion("100.1.1.1")
     *                                                        .build();
     *  new UserAgentMetadata.Builder().setBrandVersionList(Collections.singletonList(brandVersion))
     *                                 .build();
     *
     *  // Create a setting brand version, platform, platform version and bitness.
     *  new UserAgentMetadata.Builder().setBrandVersionList(Collections.singletonList(brandVersion))
     *                                 .setPlatform("myPlatform")
     *                                 .setPlatform("1.1.1.1")
     *                                 .setBitness(BITNESS_64)
     *                                 .build();
     * </pre>
     */
    public static final class Builder {
        private List<BrandVersion> mBrandVersionList = new ArrayList<>();
        private String mFullVersion;
        private String mPlatform;
        private String mPlatformVersion;
        private String mArchitecture;
        private String mModel;
        private boolean mMobile = true;
        private int mBitness = BITNESS_DEFAULT;
        private boolean mWow64 = false;

        /**
         * Create an empty UserAgentMetadata Builder.
         */
        public Builder() {
        }

        /**
         * Create a UserAgentMetadata Builder from an existing UserAgentMetadata object.
         */
        public Builder(@NonNull UserAgentMetadata uaMetadata) {
            mBrandVersionList = uaMetadata.getBrandVersionList();
            mFullVersion = uaMetadata.getFullVersion();
            mPlatform = uaMetadata.getPlatform();
            mPlatformVersion = uaMetadata.getPlatformVersion();
            mArchitecture = uaMetadata.getArchitecture();
            mModel = uaMetadata.getModel();
            mMobile = uaMetadata.isMobile();
            mBitness = uaMetadata.getBitness();
            mWow64 = uaMetadata.isWow64();
        }

        /**
         * Builds the current settings into a UserAgentMetadata object.
         *
         * @return The UserAgentMetadata object represented by this Builder
         */
        @NonNull
        public UserAgentMetadata build() {
            return new UserAgentMetadata(mBrandVersionList, mFullVersion, mPlatform,
                    mPlatformVersion, mArchitecture, mModel, mMobile, mBitness, mWow64);
        }

        /**
         * Sets user-agent metadata brands and their versions. The brand name, major version and
         * full version should not be blank. The default value is an empty list which means the
         * system default user-agent metadata brands and versions will be used to generate the
         * user-agent client hints.
         *
         * @param brandVersions a list of {@link BrandVersion} used to generated user-agent client
         *                     hints {@code sec-cu-ua} and {@code sec-ch-ua-full-version-list}.
         *
         */
        @NonNull
        public Builder setBrandVersionList(@NonNull List<BrandVersion> brandVersions) {
            mBrandVersionList = brandVersions;
            return this;
        }

        /**
         * Sets the user-agent metadata full version. The full version should not be blank, even
         * though the <a href="https://wicg.github.io/ua-client-hints">spec<a/> about brand full
         * version could be empty. A blank full version could cause inconsistent brands when
         * generating brand version related user-agent client hints. It also provides a bad
         * experience for developers when processing the brand full version. If null is provided,
         * the system default value will be used to generate the client hint.
         *
         * @param fullVersion The full version is used to generate user-agent client hint
         *                    {@code sec-ch-ua-full-version}.
         *
         */
        @NonNull
        public Builder setFullVersion(@Nullable String fullVersion) {
            if (fullVersion == null) {
                mFullVersion = null;
                return this;
            }
            if (fullVersion.trim().isEmpty()) {
                throw new IllegalArgumentException("Full version should not be blank.");
            }
            mFullVersion = fullVersion;
            return this;
        }

        /**
         * Sets the user-agent metadata platform. The platform should not be blank. If null is
         * provided, the system default value will be used to generate the client hint.
         *
         * @param platform The platform is used to generate user-agent client hint
         *                 {@code sec-ch-ua-platform}.
         *
         */
        @NonNull
        public Builder setPlatform(@Nullable String platform) {
            if (platform == null) {
                mPlatform = null;
                return this;
            }
            if (platform.trim().isEmpty()) {
                throw new IllegalArgumentException("Platform should not be blank.");
            }
            mPlatform = platform;
            return this;
        }

        /**
         * Sets the user-agent metadata platform version. If null is provided, the system default
         * value will be used to generate the client hint.
         *
         * @param platformVersion The platform version is used to generate user-agent client
         *                        hint {@code sec-ch-ua-platform-version}.
         *
         */
        @NonNull
        public Builder setPlatformVersion(@Nullable String platformVersion) {
            mPlatformVersion = platformVersion;
            return this;
        }

        /**
         * Sets the user-agent metadata architecture. If null is provided, the system default
         * value will be used to generate the client hint.
         *
         * @param architecture The architecture is used to generate user-agent client hint
         *                     {@code sec-ch-ua-arch}.
         *
         */
        @NonNull
        public Builder setArchitecture(@Nullable String architecture) {
            mArchitecture = architecture;
            return this;
        }

        /**
         * Sets the user-agent metadata model. If null is provided, the system default value will
         * be used to generate the client hint.
         *
         * @param model The model is used to generate user-agent client hint
         *              {@code sec-ch-ua-model}.
         *
         */
        @NonNull
        public Builder setModel(@Nullable String model) {
            mModel = model;
            return this;
        }

        /**
         * Sets the user-agent metadata mobile, the default value is true.
         *
         * @param mobile The mobile is used to generate user-agent client hint
         *               {@code sec-ch-ua-mobile}.
         *
         */
        @NonNull
        public Builder setMobile(boolean mobile) {
            mMobile = mobile;
            return this;
        }

        /**
         * Sets the user-agent metadata bitness, the default value is
         * {@link UserAgentMetadata#BITNESS_DEFAULT}, which indicates an empty string for
         * {@code sec-ch-ua-bitness}.
         *
         * @param bitness The bitness is used to generate user-agent client hint
         *                {@code sec-ch-ua-bitness}.
         *
         */
        @NonNull
        public Builder setBitness(int bitness) {
            mBitness = bitness;
            return this;
        }

        /**
         * Sets the user-agent metadata wow64, the default is false.
         *
         * @param wow64 The wow64 is used to generate user-agent client hint
         *              {@code sec-ch-ua-wow64}.
         *
         */
        @NonNull
        public Builder setWow64(boolean wow64) {
            mWow64 = wow64;
            return this;
        }
    }
}