chromium/components/autofill/android/java/src/org/chromium/components/autofill/AutofillProfile.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.autofill;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;

import java.util.HashMap;
import java.util.Map;

/**
 * Autofill address information.
 * The creation and/or modification of an AutofillProfile is assumed to involve the user (e.g.
 * data reviewed by the user in the {@link
 * org.chromium.chrome.browser.autofill.settings.AddressEditor}), therefore all new values gain
 * {@link VerificationStatus.USER_VERIFIED} status.
 */
@JNINamespace("autofill")
public class AutofillProfile {
    private String mGUID;
    private @RecordType int mRecordType;
    private Map<Integer, ValueWithStatus> mFields;
    private String mLabel;
    private String mLanguageCode;

    @VisibleForTesting
    static class ValueWithStatus {
        static final ValueWithStatus EMPTY = new ValueWithStatus("", VerificationStatus.NO_STATUS);

        private final String mValue;
        private final @VerificationStatus int mStatus;

        ValueWithStatus(String value, @VerificationStatus int status) {
            mValue = value;
            mStatus = status;
        }

        String getValue() {
            return mValue;
        }

        @VerificationStatus
        int getStatus() {
            return mStatus;
        }
    }

    /** Builder for the {@link AutofillProfile}. */
    public static final class Builder {
        private String mGUID = "";
        private @RecordType int mRecordType = RecordType.LOCAL_OR_SYNCABLE;
        private ValueWithStatus mFullName = ValueWithStatus.EMPTY;
        private ValueWithStatus mCompanyName = ValueWithStatus.EMPTY;
        private ValueWithStatus mStreetAddress = ValueWithStatus.EMPTY;
        private ValueWithStatus mRegion = ValueWithStatus.EMPTY;
        private ValueWithStatus mLocality = ValueWithStatus.EMPTY;
        private ValueWithStatus mDependentLocality = ValueWithStatus.EMPTY;
        private ValueWithStatus mPostalCode = ValueWithStatus.EMPTY;
        private ValueWithStatus mSortingCode = ValueWithStatus.EMPTY;
        private ValueWithStatus mCountryCode = ValueWithStatus.EMPTY;
        private ValueWithStatus mPhoneNumber = ValueWithStatus.EMPTY;
        private ValueWithStatus mEmailAddress = ValueWithStatus.EMPTY;
        private String mLabel = "";
        private String mLanguageCode = "";

        public Builder setGUID(String guid) {
            mGUID = guid;
            return this;
        }

        public Builder setRecordType(@RecordType int recordType) {
            mRecordType = recordType;
            return this;
        }

        public Builder setFullName(String fullName) {
            mFullName = new ValueWithStatus(fullName, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setFullName(String fullName, @VerificationStatus int status) {
            mFullName = new ValueWithStatus(fullName, status);
            return this;
        }

        public Builder setCompanyName(String companyName) {
            mCompanyName = new ValueWithStatus(companyName, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setCompanyName(String companyName, @VerificationStatus int status) {
            mCompanyName = new ValueWithStatus(companyName, status);
            return this;
        }

        public Builder setStreetAddress(String streetAddress) {
            mStreetAddress = new ValueWithStatus(streetAddress, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setStreetAddress(String streetAddress, @VerificationStatus int status) {
            mStreetAddress = new ValueWithStatus(streetAddress, status);
            return this;
        }

        public Builder setRegion(String region) {
            mRegion = new ValueWithStatus(region, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setRegion(String region, @VerificationStatus int status) {
            mRegion = new ValueWithStatus(region, status);
            return this;
        }

        public Builder setLocality(String locality) {
            mLocality = new ValueWithStatus(locality, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setLocality(String locality, @VerificationStatus int status) {
            mLocality = new ValueWithStatus(locality, status);
            return this;
        }

        public Builder setDependentLocality(String dependentLocality) {
            mDependentLocality =
                    new ValueWithStatus(dependentLocality, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setDependentLocality(
                String dependentLocality, @VerificationStatus int status) {
            mDependentLocality = new ValueWithStatus(dependentLocality, status);
            return this;
        }

        public Builder setPostalCode(String postalCode) {
            mPostalCode = new ValueWithStatus(postalCode, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setPostalCode(String postalCode, @VerificationStatus int status) {
            mPostalCode = new ValueWithStatus(postalCode, status);
            return this;
        }

        public Builder setSortingCode(String sortingCode) {
            mSortingCode = new ValueWithStatus(sortingCode, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setSortingCode(String sortingCode, @VerificationStatus int status) {
            mSortingCode = new ValueWithStatus(sortingCode, status);
            return this;
        }

        public Builder setCountryCode(String countryCode) {
            mCountryCode = new ValueWithStatus(countryCode, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setCountryCode(String countryCode, @VerificationStatus int status) {
            mCountryCode = new ValueWithStatus(countryCode, status);
            return this;
        }

        public Builder setPhoneNumber(String phoneNumber) {
            mPhoneNumber = new ValueWithStatus(phoneNumber, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setPhoneNumber(String phoneNumber, @VerificationStatus int status) {
            mPhoneNumber = new ValueWithStatus(phoneNumber, status);
            return this;
        }

        public Builder setEmailAddress(String emailAddress) {
            mEmailAddress = new ValueWithStatus(emailAddress, VerificationStatus.USER_VERIFIED);
            return this;
        }

        public Builder setEmailAddress(String emailAddress, @VerificationStatus int status) {
            mEmailAddress = new ValueWithStatus(emailAddress, status);
            return this;
        }

        public Builder setLabel(String label) {
            mLabel = label;
            return this;
        }

        public Builder setLanguageCode(String languageCode) {
            mLanguageCode = languageCode;
            return this;
        }

        public AutofillProfile build() {
            return new AutofillProfile(
                    mGUID,
                    mRecordType,
                    mFullName,
                    mCompanyName,
                    mStreetAddress,
                    mRegion,
                    mLocality,
                    mDependentLocality,
                    mPostalCode,
                    mSortingCode,
                    mCountryCode,
                    mPhoneNumber,
                    mEmailAddress,
                    mLanguageCode);
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    @CalledByNative
    private AutofillProfile(
            @JniType("std::string") String guid,
            @RecordType int recordType,
            @JniType("std::string") String languageCode) {
        mGUID = guid;
        mRecordType = recordType;
        mLanguageCode = languageCode;
        mFields = new HashMap<>();
    }

    private AutofillProfile(
            String guid,
            @RecordType int recordType,
            ValueWithStatus fullName,
            ValueWithStatus companyName,
            ValueWithStatus streetAddress,
            ValueWithStatus region,
            ValueWithStatus locality,
            ValueWithStatus dependentLocality,
            ValueWithStatus postalCode,
            ValueWithStatus sortingCode,
            ValueWithStatus countryCode,
            ValueWithStatus phoneNumber,
            ValueWithStatus emailAddress,
            String languageCode) {
        this(guid, recordType, languageCode);
        mFields.put(FieldType.NAME_FULL, fullName);
        mFields.put(FieldType.COMPANY_NAME, companyName);
        mFields.put(FieldType.ADDRESS_HOME_STREET_ADDRESS, streetAddress);
        mFields.put(FieldType.ADDRESS_HOME_STATE, region);
        mFields.put(FieldType.ADDRESS_HOME_CITY, locality);
        mFields.put(FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY, dependentLocality);
        mFields.put(FieldType.ADDRESS_HOME_ZIP, postalCode);
        mFields.put(FieldType.ADDRESS_HOME_SORTING_CODE, sortingCode);
        mFields.put(FieldType.ADDRESS_HOME_COUNTRY, countryCode);
        mFields.put(FieldType.PHONE_HOME_WHOLE_NUMBER, phoneNumber);
        mFields.put(FieldType.EMAIL_ADDRESS, emailAddress);
    }

    /* Builds an AutofillProfile that is an exact copy of the one passed as parameter. */
    public AutofillProfile(AutofillProfile profile) {
        mGUID = profile.getGUID();
        mRecordType = profile.getRecordType();

        mFields = new HashMap<>(profile.mFields);

        mLanguageCode = profile.getLanguageCode();
        mLabel = profile.getLabel();
    }

    @CalledByNative
    private @JniType("std::vector<int32_t>") int[] getFieldTypes() {
        return mFields.keySet().stream().mapToInt(i -> i).toArray();
    }

    @CalledByNative
    public @JniType("std::string") String getGUID() {
        return mGUID;
    }

    @CalledByNative
    public @JniType("AutofillProfile::RecordType") @RecordType int getRecordType() {
        return mRecordType;
    }

    public String getLabel() {
        return mLabel;
    }

    @CalledByNative
    public @JniType("std::u16string") String getInfo(@FieldType int fieldType) {
        if (!mFields.containsKey(fieldType)) {
            return "";
        }
        return mFields.get(fieldType).getValue();
    }

    @CalledByNative
    public @JniType("VerificationStatus") @VerificationStatus int getInfoStatus(
            @FieldType int fieldType) {
        if (!mFields.containsKey(fieldType)) {
            return VerificationStatus.NO_STATUS;
        }
        return mFields.get(fieldType).getStatus();
    }

    public String getFullName() {
        return getInfo(FieldType.NAME_FULL);
    }

    @VisibleForTesting
    @VerificationStatus
    public int getFullNameStatus() {
        return getInfoStatus(FieldType.NAME_FULL);
    }

    public String getCompanyName() {
        return getInfo(FieldType.COMPANY_NAME);
    }

    @VerificationStatus
    int getCompanyNameStatus() {
        return getInfoStatus(FieldType.COMPANY_NAME);
    }

    public String getStreetAddress() {
        return getInfo(FieldType.ADDRESS_HOME_STREET_ADDRESS);
    }

    @VisibleForTesting
    @VerificationStatus
    public int getStreetAddressStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_STREET_ADDRESS);
    }

    public String getRegion() {
        return getInfo(FieldType.ADDRESS_HOME_STATE);
    }

    @VisibleForTesting
    @VerificationStatus
    public int getRegionStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_STATE);
    }

    public String getLocality() {
        return getInfo(FieldType.ADDRESS_HOME_CITY);
    }

    @VisibleForTesting
    @VerificationStatus
    public int getLocalityStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_CITY);
    }

    public String getDependentLocality() {
        return getInfo(FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY);
    }

    private @VerificationStatus int getDependentLocalityStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY);
    }

    public String getPostalCode() {
        return getInfo(FieldType.ADDRESS_HOME_ZIP);
    }

    @VisibleForTesting
    @VerificationStatus
    public int getPostalCodeStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_ZIP);
    }

    public String getSortingCode() {
        return getInfo(FieldType.ADDRESS_HOME_SORTING_CODE);
    }

    private @VerificationStatus int getSortingCodeStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_SORTING_CODE);
    }

    @CalledByNative
    public @JniType("std::string") String getCountryCode() {
        return getInfo(FieldType.ADDRESS_HOME_COUNTRY);
    }

    private @VerificationStatus int getCountryCodeStatus() {
        return getInfoStatus(FieldType.ADDRESS_HOME_COUNTRY);
    }

    public String getPhoneNumber() {
        return getInfo(FieldType.PHONE_HOME_WHOLE_NUMBER);
    }

    private @VerificationStatus int getPhoneNumberStatus() {
        return getInfoStatus(FieldType.PHONE_HOME_WHOLE_NUMBER);
    }

    public String getEmailAddress() {
        return getInfo(FieldType.EMAIL_ADDRESS);
    }

    private @VerificationStatus int getEmailAddressStatus() {
        return getInfoStatus(FieldType.EMAIL_ADDRESS);
    }

    @CalledByNative
    public @JniType("std::string") String getLanguageCode() {
        return mLanguageCode;
    }

    public void setGUID(String guid) {
        mGUID = guid;
    }

    public void setLabel(String label) {
        mLabel = label;
    }

    public void setRecordType(@RecordType int recordType) {
        mRecordType = recordType;
    }

    @CalledByNative
    public void setInfo(
            @FieldType int fieldType,
            @JniType("std::u16string") @Nullable String value,
            @VerificationStatus int status) {
        value = value == null ? "" : value;
        mFields.put(fieldType, new ValueWithStatus(value, status));
    }

    public void setInfo(@FieldType int fieldType, @Nullable String value) {
        setInfo(fieldType, value, VerificationStatus.USER_VERIFIED);
    }

    public void setFullName(String fullName) {
        setInfo(FieldType.NAME_FULL, fullName);
    }

    public void setCompanyName(String companyName) {
        setInfo(FieldType.COMPANY_NAME, companyName);
    }

    public void setStreetAddress(String streetAddress) {
        setInfo(FieldType.ADDRESS_HOME_STREET_ADDRESS, streetAddress);
    }

    public void setRegion(String region) {
        setInfo(FieldType.ADDRESS_HOME_STATE, region);
    }

    public void setLocality(String locality) {
        setInfo(FieldType.ADDRESS_HOME_CITY, locality);
    }

    public void setDependentLocality(String dependentLocality) {
        setInfo(FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY, dependentLocality);
    }

    public void setPostalCode(String postalCode) {
        setInfo(FieldType.ADDRESS_HOME_ZIP, postalCode);
    }

    public void setSortingCode(String sortingCode) {
        setInfo(FieldType.ADDRESS_HOME_SORTING_CODE, sortingCode);
    }

    public void setCountryCode(String countryCode) {
        setInfo(FieldType.ADDRESS_HOME_COUNTRY, countryCode);
    }

    public void setPhoneNumber(String phoneNumber) {
        setInfo(FieldType.PHONE_HOME_WHOLE_NUMBER, phoneNumber);
    }

    public void setEmailAddress(String emailAddress) {
        setInfo(FieldType.EMAIL_ADDRESS, emailAddress);
    }

    public void setLanguageCode(String languageCode) {
        mLanguageCode = languageCode;
    }

    /** Used by ArrayAdapter in credit card settings. */
    @Override
    public String toString() {
        return mLabel;
    }
}