blob: a999f1f0110485a7a0e6604bf1a5940b086b6e46 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill_assistant/browser/actions/collect_user_data_action.h"
#include <algorithm>
#include <set>
#include <utility>
#include "base/android/locale_utils.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/geo/address_i18n.h"
#include "components/autofill_assistant/browser/actions/action_delegate.h"
#include "components/autofill_assistant/browser/client_memory.h"
#include "components/autofill_assistant/browser/client_status.h"
#include "components/autofill_assistant/browser/metrics.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/website_login_fetcher_impl.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
#include "third_party/libaddressinput/chromium/addressinput_util.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
using autofill_assistant::CollectUserDataOptions;
using autofill_assistant::DateTimeProto;
using autofill_assistant::TermsAndConditionsState;
bool IsCompleteContact(
const autofill::AutofillProfile* profile,
const CollectUserDataOptions& collect_user_data_options) {
if (!collect_user_data_options.request_payer_name &&
!collect_user_data_options.request_payer_email &&
!collect_user_data_options.request_payer_phone) {
return true;
}
if (!profile) {
return false;
}
if (collect_user_data_options.request_payer_name &&
profile->GetRawInfo(autofill::NAME_FULL).empty()) {
return false;
}
if (collect_user_data_options.request_payer_email &&
profile->GetRawInfo(autofill::EMAIL_ADDRESS).empty()) {
return false;
}
if (collect_user_data_options.request_payer_phone &&
profile->GetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER).empty()) {
return false;
}
return true;
}
bool IsCompleteAddress(const autofill::AutofillProfile* profile,
bool require_postal_code) {
if (!profile) {
return false;
}
auto address_data = autofill::i18n::CreateAddressDataFromAutofillProfile(
*profile, base::android::GetDefaultLocaleString());
if (!autofill::addressinput::HasAllRequiredFields(*address_data)) {
return false;
}
if (require_postal_code && address_data->postal_code.empty()) {
return false;
}
return true;
}
bool IsCompleteBillingAddress(
const autofill::AutofillProfile* profile,
const CollectUserDataOptions& collect_user_data_options) {
return !collect_user_data_options.request_payment_method ||
IsCompleteAddress(
profile, collect_user_data_options.require_billing_postal_code);
}
bool IsCompleteShippingAddress(
const autofill::AutofillProfile* profile,
const CollectUserDataOptions& collect_user_data_options) {
return !collect_user_data_options.request_shipping ||
IsCompleteAddress(profile, /* require_postal_code = */ false);
}
bool IsCompleteCreditCard(
autofill::PersonalDataManager* personal_data_manager,
const autofill::CreditCard* credit_card,
const CollectUserDataOptions& collect_user_data_options) {
if (!collect_user_data_options.request_payment_method) {
return true;
}
if (!credit_card) {
return false;
}
if (credit_card->record_type() != autofill::CreditCard::MASKED_SERVER_CARD &&
!credit_card->HasValidCardNumber()) {
// Can't check validity of masked server card numbers because they are
// incomplete until decrypted.
return false;
}
if (!credit_card->HasValidExpirationDate() ||
credit_card->billing_address_id().empty()) {
return false;
}
auto* address_profile = personal_data_manager->GetProfileByGUID(
credit_card->billing_address_id());
if (!IsCompleteBillingAddress(address_profile, collect_user_data_options)) {
return false;
}
std::string basic_card_network =
autofill::data_util::GetPaymentRequestData(credit_card->network())
.basic_card_issuer_network;
if (!collect_user_data_options.supported_basic_card_networks.empty() &&
std::find(collect_user_data_options.supported_basic_card_networks.begin(),
collect_user_data_options.supported_basic_card_networks.end(),
basic_card_network) ==
collect_user_data_options.supported_basic_card_networks.end()) {
return false;
}
return true;
}
bool IsValidLoginChoice(
const std::string& choice_identifier,
const CollectUserDataOptions& collect_user_data_options) {
return !collect_user_data_options.request_login_choice ||
!choice_identifier.empty();
}
bool IsValidTermsChoice(
TermsAndConditionsState terms_state,
const CollectUserDataOptions& collect_user_data_options) {
return collect_user_data_options.accept_terms_and_conditions_text.empty() ||
terms_state != TermsAndConditionsState::NOT_SELECTED;
}
// Comparison function for |DateTimeProto|.
// Returns 0 if equal, < 0 if |first| < |second|, > 0 if |second| > |first|.
int CompareDateTimes(const DateTimeProto& first, const DateTimeProto& second) {
auto first_tuple = std::make_tuple(
first.date().year(), first.date().month(), first.date().day(),
first.time().hour(), first.time().minute(), first.time().second());
auto second_tuple = std::make_tuple(
second.date().year(), second.date().month(), second.date().day(),
second.time().hour(), second.time().minute(), second.time().second());
if (first_tuple < second_tuple) {
return -1;
} else if (second_tuple < first_tuple) {
return 1;
}
return 0;
}
bool IsValidDateTimeRange(
const DateTimeProto& start,
const DateTimeProto& end,
const CollectUserDataOptions& collect_user_data_options) {
return !collect_user_data_options.request_date_time_range ||
CompareDateTimes(start, end) < 0;
}
bool IsValidUserFormSection(
const autofill_assistant::UserFormSectionProto& proto) {
if (proto.title().empty()) {
DVLOG(2) << "UserFormSectionProto: Empty title not allowed.";
return false;
}
switch (proto.section_case()) {
case autofill_assistant::UserFormSectionProto::kStaticTextSection:
if (proto.static_text_section().text().empty()) {
DVLOG(2) << "StaticTextSectionProto: Empty text not allowed.";
return false;
}
break;
case autofill_assistant::UserFormSectionProto::kTextInputSection: {
if (proto.text_input_section().input_fields().empty()) {
DVLOG(2) << "TextInputProto: At least one input must be specified.";
return false;
}
std::set<std::string> memory_keys;
for (const auto& input_field :
proto.text_input_section().input_fields()) {
if (input_field.client_memory_key().empty()) {
DVLOG(2) << "TextInputProto: Memory key must be specified.";
return false;
}
if (!memory_keys.insert(input_field.client_memory_key()).second) {
DVLOG(2) << "TextInputProto: Duplicate memory keys ("
<< input_field.client_memory_key() << ").";
return false;
}
}
break;
}
case autofill_assistant::UserFormSectionProto::SECTION_NOT_SET:
DVLOG(2) << "UserFormSectionProto: section oneof not set.";
return false;
}
return true;
}
} // namespace
namespace autofill_assistant {
CollectUserDataAction::LoginDetails::LoginDetails(
bool _choose_automatically_if_no_other_options,
const std::string& _payload,
const WebsiteLoginFetcher::Login& _login)
: choose_automatically_if_no_other_options(
_choose_automatically_if_no_other_options),
payload(_payload),
login(_login) {}
CollectUserDataAction::LoginDetails::LoginDetails(
bool _choose_automatically_if_no_other_options,
const std::string& _payload)
: choose_automatically_if_no_other_options(
_choose_automatically_if_no_other_options),
payload(_payload) {}
CollectUserDataAction::LoginDetails::~LoginDetails() = default;
CollectUserDataAction::CollectUserDataAction(ActionDelegate* delegate,
const ActionProto& proto)
: Action(delegate, proto) {
DCHECK(proto_.has_collect_user_data());
}
CollectUserDataAction::~CollectUserDataAction() {
delegate_->GetPersonalDataManager()->RemoveObserver(this);
// Report UMA histograms.
if (shown_to_user_) {
Metrics::RecordPaymentRequestPrefilledSuccess(initially_prefilled,
action_successful_);
Metrics::RecordPaymentRequestAutofillChanged(personal_data_changed_,
action_successful_);
Metrics::RecordPaymentRequestMandatoryPostalCode(
proto_.collect_user_data().require_billing_postal_code(),
initial_card_has_billing_postal_code_, action_successful_);
}
}
void CollectUserDataAction::InternalProcessAction(
ProcessActionCallback callback) {
callback_ = std::move(callback);
auto collect_user_data_options = CreateOptionsFromProto();
if (!collect_user_data_options) {
EndAction(ClientStatus(INVALID_ACTION));
return;
}
// If Chrome password manager logins are requested, we need to asynchronously
// obtain them before showing the UI.
auto collect_user_data = proto_.collect_user_data();
auto password_manager_option = std::find_if(
collect_user_data.login_details().login_options().begin(),
collect_user_data.login_details().login_options().end(),
[&](const LoginDetailsProto::LoginOptionProto& option) {
return option.type_case() ==
LoginDetailsProto::LoginOptionProto::kPasswordManager;
});
bool requests_pwm_logins =
password_manager_option !=
collect_user_data.login_details().login_options().end();
collect_user_data_options->confirm_callback = base::BindOnce(
&CollectUserDataAction::OnGetUserData, weak_ptr_factory_.GetWeakPtr(),
std::move(collect_user_data));
collect_user_data_options->additional_actions_callback =
base::BindOnce(&CollectUserDataAction::OnAdditionalActionTriggered,
weak_ptr_factory_.GetWeakPtr());
collect_user_data_options->terms_link_callback =
base::BindOnce(&CollectUserDataAction::OnTermsAndConditionsLinkClicked,
weak_ptr_factory_.GetWeakPtr());
if (requests_pwm_logins) {
delegate_->GetWebsiteLoginFetcher()->GetLoginsForUrl(
delegate_->GetWebContents()->GetLastCommittedURL(),
base::BindOnce(&CollectUserDataAction::OnGetLogins,
weak_ptr_factory_.GetWeakPtr(), *password_manager_option,
std::move(collect_user_data_options)));
} else {
ShowToUser(std::move(collect_user_data_options));
}
}
void CollectUserDataAction::EndAction(const ClientStatus& status) {
action_successful_ = status.ok();
UpdateProcessedAction(status);
std::move(callback_).Run(std::move(processed_action_proto_));
}
void CollectUserDataAction::OnGetLogins(
const LoginDetailsProto::LoginOptionProto& login_option,
std::unique_ptr<CollectUserDataOptions> collect_user_data_options,
std::vector<WebsiteLoginFetcher::Login> logins) {
for (const auto& login : logins) {
LoginChoice choice = {
base::NumberToString(collect_user_data_options->login_choices.size()),
login.username, login_option.preselection_priority()};
collect_user_data_options->login_choices.emplace_back(std::move(choice));
login_details_map_.emplace(
choice.identifier,
std::make_unique<LoginDetails>(
login_option.choose_automatically_if_no_other_options(),
login_option.payload(), login));
}
ShowToUser(std::move(collect_user_data_options));
}
void CollectUserDataAction::ShowToUser(
std::unique_ptr<CollectUserDataOptions> collect_user_data_options) {
// Create and set initial state.
auto user_data = std::make_unique<UserData>();
auto collect_user_data = proto_.collect_user_data();
switch (collect_user_data.terms_and_conditions_state()) {
case CollectUserDataProto::NOT_SELECTED:
user_data->terms_and_conditions = NOT_SELECTED;
break;
case CollectUserDataProto::ACCEPTED:
user_data->terms_and_conditions = ACCEPTED;
break;
case CollectUserDataProto::REVIEW_REQUIRED:
user_data->terms_and_conditions = REQUIRES_REVIEW;
break;
}
for (const auto& additional_section :
collect_user_data.additional_prepended_sections()) {
if (additional_section.section_case() ==
UserFormSectionProto::kTextInputSection) {
for (const auto& text_input :
additional_section.text_input_section().input_fields()) {
user_data->additional_values_to_store.emplace(
text_input.client_memory_key(), text_input.value());
}
}
}
for (const auto& additional_section :
collect_user_data.additional_appended_sections()) {
if (additional_section.section_case() ==
UserFormSectionProto::kTextInputSection) {
for (const auto& text_input :
additional_section.text_input_section().input_fields()) {
user_data->additional_values_to_store.emplace(
text_input.client_memory_key(), text_input.value());
}
}
}
if (collect_user_data_options->request_login_choice &&
collect_user_data_options->login_choices.empty()) {
EndAction(ClientStatus(COLLECT_USER_DATA_ERROR));
return;
}
// Special case: if the only available login option has
// |choose_automatically_if_no_other_options=true|, the section will not be
// shown.
bool only_login_requested =
collect_user_data_options->request_login_choice &&
!collect_user_data_options->request_payer_name &&
!collect_user_data_options->request_payer_email &&
!collect_user_data_options->request_payer_phone &&
!collect_user_data_options->request_shipping &&
!collect_user_data_options->request_payment_method &&
!collect_user_data.request_terms_and_conditions();
if (collect_user_data_options->login_choices.size() == 1 &&
login_details_map_
.at(collect_user_data_options->login_choices.at(0).identifier)
->choose_automatically_if_no_other_options) {
collect_user_data_options->request_login_choice = false;
user_data->login_choice_identifier.assign(
collect_user_data_options->login_choices[0].identifier);
// If only the login section is requested and the choice has already been
// made implicitly, the entire UI will not be shown and the action will
// complete immediately.
if (only_login_requested) {
user_data->succeed = true;
std::move(collect_user_data_options->confirm_callback)
.Run(std::move(user_data));
return;
}
}
// Gather info for UMA histograms.
if (!shown_to_user_) {
shown_to_user_ = true;
initially_prefilled = CheckInitialAutofillDataComplete(
delegate_->GetPersonalDataManager(), *collect_user_data_options);
delegate_->GetPersonalDataManager()->AddObserver(this);
}
if (collect_user_data.has_prompt()) {
delegate_->SetStatusMessage(collect_user_data.prompt());
}
delegate_->CollectUserData(std::move(collect_user_data_options),
std::move(user_data));
}
void CollectUserDataAction::OnGetUserData(
const CollectUserDataProto& collect_user_data,
std::unique_ptr<UserData> user_data) {
if (!callback_)
return;
bool succeed = user_data->succeed;
if (succeed) {
if (collect_user_data.request_payment_method()) {
DCHECK(user_data->card);
std::string card_issuer_network =
autofill::data_util::GetPaymentRequestData(user_data->card->network())
.basic_card_issuer_network;
processed_action_proto_->mutable_collect_user_data_result()
->set_card_issuer_network(card_issuer_network);
delegate_->GetClientMemory()->set_selected_card(
std::move(user_data->card));
if (!collect_user_data.billing_address_name().empty()) {
DCHECK(user_data->billing_address);
delegate_->GetClientMemory()->set_selected_address(
collect_user_data.billing_address_name(),
std::move(user_data->billing_address));
}
}
if (!collect_user_data.shipping_address_name().empty()) {
DCHECK(user_data->shipping_address);
delegate_->GetClientMemory()->set_selected_address(
collect_user_data.shipping_address_name(),
std::move(user_data->shipping_address));
}
if (collect_user_data.has_contact_details()) {
DCHECK(user_data->contact_profile);
auto contact_details_proto = collect_user_data.contact_details();
if (contact_details_proto.request_payer_name()) {
Metrics::RecordPaymentRequestFirstNameOnly(
user_data->contact_profile
->GetRawInfo(autofill::ServerFieldType::NAME_LAST)
.empty());
}
if (contact_details_proto.request_payer_email()) {
processed_action_proto_->mutable_collect_user_data_result()
->set_payer_email(
base::UTF16ToUTF8(user_data->contact_profile->GetRawInfo(
autofill::ServerFieldType::EMAIL_ADDRESS)));
}
if (!contact_details_proto.contact_details_name().empty()) {
delegate_->GetClientMemory()->set_selected_address(
contact_details_proto.contact_details_name(),
std::move(user_data->contact_profile));
}
}
if (collect_user_data.has_login_details()) {
auto login_details =
login_details_map_.find(user_data->login_choice_identifier);
DCHECK(login_details != login_details_map_.end());
if (login_details->second->login.has_value()) {
delegate_->GetClientMemory()->set_selected_login(
*login_details->second->login);
}
processed_action_proto_->mutable_collect_user_data_result()
->set_login_payload(login_details->second->payload);
}
if (collect_user_data.has_date_time_range()) {
*processed_action_proto_->mutable_collect_user_data_result()
->mutable_date_time_start() = user_data->date_time_range_start;
*processed_action_proto_->mutable_collect_user_data_result()
->mutable_date_time_end() = user_data->date_time_range_end;
}
for (const auto& value : user_data->additional_values_to_store) {
delegate_->GetClientMemory()->set_additional_value(value.first,
value.second);
}
processed_action_proto_->mutable_collect_user_data_result()
->set_is_terms_and_conditions_accepted(
user_data->terms_and_conditions ==
TermsAndConditionsState::ACCEPTED);
}
EndAction(succeed ? ClientStatus(ACTION_APPLIED)
: ClientStatus(COLLECT_USER_DATA_ERROR));
}
void CollectUserDataAction::OnAdditionalActionTriggered(int index) {
if (!callback_)
return;
processed_action_proto_->mutable_collect_user_data_result()
->set_additional_action_index(index);
EndAction(ClientStatus(ACTION_APPLIED));
}
void CollectUserDataAction::OnTermsAndConditionsLinkClicked(int link) {
if (!callback_)
return;
processed_action_proto_->mutable_collect_user_data_result()->set_terms_link(
link);
EndAction(ClientStatus(ACTION_APPLIED));
}
std::unique_ptr<CollectUserDataOptions>
CollectUserDataAction::CreateOptionsFromProto() {
auto collect_user_data_options = std::make_unique<CollectUserDataOptions>();
auto collect_user_data = proto_.collect_user_data();
if (collect_user_data.has_contact_details()) {
auto contact_details = collect_user_data.contact_details();
collect_user_data_options->request_payer_email =
contact_details.request_payer_email();
collect_user_data_options->request_payer_name =
contact_details.request_payer_name();
collect_user_data_options->request_payer_phone =
contact_details.request_payer_phone();
}
std::copy(collect_user_data.supported_basic_card_networks().begin(),
collect_user_data.supported_basic_card_networks().end(),
std::back_inserter(
collect_user_data_options->supported_basic_card_networks));
collect_user_data_options->request_shipping =
!collect_user_data.shipping_address_name().empty();
collect_user_data_options->request_payment_method =
collect_user_data.request_payment_method();
collect_user_data_options->require_billing_postal_code =
collect_user_data.require_billing_postal_code();
collect_user_data_options->billing_postal_code_missing_text =
collect_user_data.billing_postal_code_missing_text();
if (collect_user_data_options->require_billing_postal_code &&
collect_user_data_options->billing_postal_code_missing_text.empty()) {
return nullptr;
}
collect_user_data_options->request_login_choice =
collect_user_data.has_login_details();
collect_user_data_options->login_section_title.assign(
collect_user_data.login_details().section_title());
// Transform login options to concrete login choices.
for (const auto& login_option :
collect_user_data.login_details().login_options()) {
switch (login_option.type_case()) {
case LoginDetailsProto::LoginOptionProto::kCustom: {
LoginChoice choice = {
base::NumberToString(
collect_user_data_options->login_choices.size()),
login_option.custom().label(),
login_option.has_preselection_priority()
? login_option.preselection_priority()
: -1};
collect_user_data_options->login_choices.emplace_back(
std::move(choice));
login_details_map_.emplace(
choice.identifier,
std::make_unique<LoginDetails>(
login_option.choose_automatically_if_no_other_options(),
login_option.payload()));
break;
}
case LoginDetailsProto::LoginOptionProto::kPasswordManager: {
// Will be retrieved later.
break;
}
case LoginDetailsProto::LoginOptionProto::TYPE_NOT_SET: {
// Login option specified, but type not set (should never happen).
return nullptr;
}
}
}
if (collect_user_data.has_date_time_range()) {
if (!collect_user_data.date_time_range().has_start_label() ||
!collect_user_data.date_time_range().has_end_label() ||
!collect_user_data.date_time_range().has_start() ||
!collect_user_data.date_time_range().has_end() ||
!collect_user_data.date_time_range().has_min() ||
!collect_user_data.date_time_range().has_max()) {
DVLOG(1) << "Invalid action: missing one or more of the required fields "
"'start', 'end', 'min', 'max', 'start_label', end_label'.";
return nullptr;
}
collect_user_data_options->request_date_time_range = true;
collect_user_data_options->date_time_range =
collect_user_data.date_time_range();
}
for (const auto& section :
collect_user_data.additional_prepended_sections()) {
if (!IsValidUserFormSection(section)) {
DVLOG(1)
<< "Invalid UserFormSectionProto in additional_prepended_sections";
return nullptr;
}
collect_user_data_options->additional_prepended_sections.emplace_back(
section);
}
for (const auto& section : collect_user_data.additional_appended_sections()) {
if (!IsValidUserFormSection(section)) {
DVLOG(1)
<< "Invalid UserFormSectionProto in additional_appended_sections";
return nullptr;
}
collect_user_data_options->additional_appended_sections.emplace_back(
section);
}
// TODO(crbug.com/806868): Maybe we could refactor this to make the confirm
// chip and direct_action part of the additional_actions.
std::string confirm_text = collect_user_data.confirm_button_text();
if (confirm_text.empty()) {
confirm_text =
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM);
}
collect_user_data_options->confirm_action.mutable_chip()->set_text(
confirm_text);
collect_user_data_options->confirm_action.mutable_chip()->set_type(
HIGHLIGHTED_ACTION);
*collect_user_data_options->confirm_action.mutable_direct_action() =
collect_user_data.confirm_direct_action();
for (auto action : collect_user_data.additional_actions()) {
collect_user_data_options->additional_actions.push_back(action);
}
if (collect_user_data.request_terms_and_conditions()) {
collect_user_data_options->show_terms_as_checkbox =
collect_user_data.show_terms_as_checkbox();
collect_user_data_options->accept_terms_and_conditions_text =
collect_user_data.accept_terms_and_conditions_text();
if (collect_user_data_options->accept_terms_and_conditions_text.empty()) {
collect_user_data_options->accept_terms_and_conditions_text =
l10n_util::GetStringUTF8(
IDS_AUTOFILL_ASSISTANT_3RD_PARTY_TERMS_ACCEPT);
}
}
collect_user_data_options->default_email =
delegate_->GetAccountEmailAddress();
return collect_user_data_options;
}
bool CollectUserDataAction::CheckInitialAutofillDataComplete(
autofill::PersonalDataManager* personal_data_manager,
const CollectUserDataOptions& collect_user_data_options) {
bool request_contact = collect_user_data_options.request_payer_name ||
collect_user_data_options.request_payer_email ||
collect_user_data_options.request_payer_phone;
if (request_contact || collect_user_data_options.request_shipping) {
auto profiles = personal_data_manager->GetProfiles();
if (request_contact) {
auto completeContactIter = std::find_if(
profiles.begin(), profiles.end(),
[&collect_user_data_options](const auto& profile) {
return IsCompleteContact(profile, collect_user_data_options);
});
if (completeContactIter == profiles.end()) {
return false;
}
}
if (collect_user_data_options.request_shipping) {
auto completeAddressIter =
std::find_if(profiles.begin(), profiles.end(),
[&collect_user_data_options](const auto* profile) {
return IsCompleteShippingAddress(
profile, collect_user_data_options);
});
if (completeAddressIter == profiles.end()) {
return false;
}
}
}
if (collect_user_data_options.request_payment_method) {
auto credit_cards = personal_data_manager->GetCreditCards();
auto completeCardIter = std::find_if(
credit_cards.begin(), credit_cards.end(),
[&collect_user_data_options,
personal_data_manager](const auto* credit_card) {
return IsCompleteCreditCard(personal_data_manager, credit_card,
collect_user_data_options);
});
if (completeCardIter == credit_cards.end()) {
return false;
}
if (collect_user_data_options.require_billing_postal_code) {
initial_card_has_billing_postal_code_ = true;
}
}
return true;
}
// static
bool CollectUserDataAction::IsUserDataComplete(
autofill::PersonalDataManager* personal_data_manager,
const UserData& user_data,
const CollectUserDataOptions& options) {
return IsCompleteContact(user_data.contact_profile.get(), options) &&
IsCompleteBillingAddress(user_data.billing_address.get(), options) &&
IsCompleteShippingAddress(user_data.shipping_address.get(), options) &&
IsCompleteCreditCard(personal_data_manager, user_data.card.get(),
options) &&
IsValidLoginChoice(user_data.login_choice_identifier, options) &&
IsValidTermsChoice(user_data.terms_and_conditions, options) &&
IsValidDateTimeRange(user_data.date_time_range_start,
user_data.date_time_range_end, options);
}
void CollectUserDataAction::OnPersonalDataChanged() {
personal_data_changed_ = true;
delegate_->GetPersonalDataManager()->RemoveObserver(this);
}
} // namespace autofill_assistant