blob: 84ed37181430b03976a927b258201c8f63576127 [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 <array>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/i18n/case_conversion.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.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/field_types.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_status.h"
#include "components/autofill_assistant/browser/field_formatter.h"
#include "components/autofill_assistant/browser/metrics.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/user_data_util.h"
#include "components/autofill_assistant/browser/website_login_manager_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 {
static constexpr int kDefaultMaxNumberContactSummaryLines = 1;
static constexpr std::array<autofill_assistant::AutofillContactField, 2>
kDefaultContactSummaryFields = {autofill_assistant::EMAIL_ADDRESS,
autofill_assistant::NAME_FULL};
static constexpr int kDefaultMaxNumberContactFullLines = 2;
static constexpr std::array<autofill_assistant::AutofillContactField, 2>
kDefaultContactFullFields = {autofill_assistant::NAME_FULL,
autofill_assistant::EMAIL_ADDRESS};
using autofill_assistant::CollectUserDataOptions;
using autofill_assistant::CollectUserDataProto;
using autofill_assistant::DateTimeProto;
using autofill_assistant::TermsAndConditionsState;
using autofill_assistant::UserData;
using autofill_assistant::UserModel;
bool IsReadOnlyAdditionalSection(
const autofill_assistant::UserFormSectionProto& section) {
switch (section.section_case()) {
case autofill_assistant::UserFormSectionProto::kStaticTextSection:
return true;
case autofill_assistant::UserFormSectionProto::kTextInputSection:
case autofill_assistant::UserFormSectionProto::kPopupListSection:
case autofill_assistant::UserFormSectionProto::SECTION_NOT_SET:
return false;
}
}
bool OnlyLoginRequested(
const CollectUserDataOptions& collect_user_data_options) {
if (!collect_user_data_options.request_login_choice) {
return false;
}
auto find_additional_input_sections =
[&](const autofill_assistant::UserFormSectionProto& section) {
return !IsReadOnlyAdditionalSection(section);
};
bool has_input_sections =
std::find_if(
collect_user_data_options.additional_prepended_sections.begin(),
collect_user_data_options.additional_prepended_sections.end(),
find_additional_input_sections) !=
collect_user_data_options.additional_prepended_sections.end() ||
std::find_if(
collect_user_data_options.additional_appended_sections.begin(),
collect_user_data_options.additional_appended_sections.end(),
find_additional_input_sections) !=
collect_user_data_options.additional_appended_sections.end();
return !has_input_sections && !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_options.request_date_time_range &&
collect_user_data_options.accept_terms_and_conditions_text.empty() &&
!collect_user_data_options.additional_model_identifier_to_check
.has_value();
}
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;
}
// Checks |proto| and writes an error message to |error| if fields are missing.
bool IsValidDateTimeRangeProto(
const autofill_assistant::DateTimeRangeProto& proto,
std::string* error) {
std::vector<std::string> missing_fields;
if (proto.start_date_label().empty())
missing_fields.emplace_back("start_date_label");
if (proto.start_time_label().empty())
missing_fields.emplace_back("start_time_label");
if (proto.end_date_label().empty())
missing_fields.emplace_back("end_date_label");
if (proto.end_time_label().empty())
missing_fields.emplace_back("end_time_label");
if (!proto.has_start_date())
missing_fields.emplace_back("start_date");
if (!proto.has_start_time_slot())
missing_fields.emplace_back("start_time_slot");
if (!proto.has_end_date())
missing_fields.emplace_back("end_date");
if (!proto.has_end_time_slot())
missing_fields.emplace_back("end_time_slot");
if (!proto.has_min_date())
missing_fields.emplace_back("min_date");
if (!proto.has_max_date())
missing_fields.emplace_back("max_date");
if (proto.time_slots().empty())
missing_fields.emplace_back("time_slots");
if (proto.date_not_set_error().empty())
missing_fields.emplace_back("date_not_set_error");
if (proto.time_not_set_error().empty())
missing_fields.emplace_back("time_not_set_error");
if (error != nullptr && !missing_fields.empty()) {
error->assign("The following fields are missing or empty: ");
error->append(base::JoinString(missing_fields, ", "));
}
return missing_fields.empty();
}
bool IsValidDateTimeRange(
const base::Optional<autofill_assistant::DateProto>& start_date,
const base::Optional<int> start_timeslot,
const base::Optional<autofill_assistant::DateProto> end_date,
const base::Optional<int> end_timeslot,
const CollectUserDataOptions& collect_user_data_options) {
if (!collect_user_data_options.request_date_time_range) {
return true;
}
if (!start_date.has_value() || !start_timeslot.has_value() ||
!end_date.has_value() || !end_timeslot.has_value()) {
return false;
}
auto temp_start_date = start_date;
auto temp_start_timeslot = start_timeslot;
auto temp_end_date = end_date;
auto temp_end_timeslot = end_timeslot;
return !autofill_assistant::CollectUserDataAction::SanitizeDateTimeRange(
&temp_start_date, &temp_start_timeslot, &temp_end_date,
&temp_end_timeslot, collect_user_data_options, false);
}
bool IsValidUserFormSection(
const autofill_assistant::UserFormSectionProto& proto) {
if (proto.title().empty()) {
VLOG(2) << "UserFormSectionProto: Empty title not allowed.";
return false;
}
switch (proto.section_case()) {
case autofill_assistant::UserFormSectionProto::kStaticTextSection:
if (proto.static_text_section().text().empty()) {
VLOG(2) << "StaticTextSectionProto: Empty text not allowed.";
return false;
}
break;
case autofill_assistant::UserFormSectionProto::kTextInputSection: {
if (proto.text_input_section().input_fields().empty()) {
VLOG(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()) {
VLOG(2) << "TextInputProto: Memory key must be specified.";
return false;
}
if (!memory_keys.insert(input_field.client_memory_key()).second) {
VLOG(2) << "TextInputProto: Duplicate memory keys ("
<< input_field.client_memory_key() << ").";
return false;
}
}
break;
}
case autofill_assistant::UserFormSectionProto::kPopupListSection:
if (proto.popup_list_section().item_names().empty()) {
VLOG(2) << "PopupListProto: At least one item must be specified.";
return false;
}
if (proto.popup_list_section().initial_selection().size() > 1 &&
proto.popup_list_section().allow_multiselect() == false) {
VLOG(2) << "PopupListProto: multiple initial selections for a single "
"selection popup.";
return false;
}
for (int selection : proto.popup_list_section().initial_selection()) {
if (selection >= proto.popup_list_section().item_names().size() ||
selection < 0) {
VLOG(2) << "PopupListProto: an initial selection is out of bounds.";
return false;
}
}
break;
case autofill_assistant::UserFormSectionProto::SECTION_NOT_SET:
VLOG(2) << "UserFormSectionProto: section oneof not set.";
return false;
}
return true;
}
bool IsValidUserModel(const autofill_assistant::UserModel& user_model,
const autofill_assistant::CollectUserDataOptions&
collect_user_data_options) {
if (!collect_user_data_options.additional_model_identifier_to_check
.has_value()) {
return true;
}
auto valid = user_model.GetValue(
*collect_user_data_options.additional_model_identifier_to_check);
if (!valid.has_value()) {
VLOG(1) << "Error evaluating validity of user model: '"
<< *collect_user_data_options.additional_model_identifier_to_check
<< "' not found in user model.";
return false;
}
if (valid->kind_case() != autofill_assistant::ValueProto::kBooleans ||
valid->booleans().values().size() != 1) {
VLOG(1) << "Error evaluating validity of user model: expected single "
"boolean, but got "
<< *valid;
return false;
}
return valid->booleans().values(0);
}
// Merges |model_a| and |model_b| into a new model.
// TODO(arbesser): deal with overlapping keys.
autofill_assistant::ModelProto MergeModelProtos(
const autofill_assistant::ModelProto& model_a,
const autofill_assistant::ModelProto& model_b) {
autofill_assistant::ModelProto model_merged;
for (const auto& value : model_a.values()) {
*model_merged.add_values() = value;
}
for (const auto& value : model_b.values()) {
*model_merged.add_values() = value;
}
return model_merged;
}
void FillProtoForAdditionalSection(
const autofill_assistant::UserFormSectionProto& additional_section,
const UserData& user_data,
autofill_assistant::ProcessedActionProto* processed_action_proto) {
switch (additional_section.section_case()) {
case autofill_assistant::UserFormSectionProto::kTextInputSection:
for (const auto& text_input :
additional_section.text_input_section().input_fields()) {
if (user_data.has_additional_value(text_input.client_memory_key())) {
processed_action_proto->mutable_collect_user_data_result()
->add_set_text_input_memory_keys(text_input.client_memory_key());
if (additional_section.send_result_to_backend()) {
auto value = user_data.additional_values_.find(
text_input.client_memory_key());
autofill_assistant::ModelProto_ModelValue model_value;
model_value.set_identifier(text_input.client_memory_key());
*model_value.mutable_value() = value->second;
*processed_action_proto->mutable_collect_user_data_result()
->add_additional_sections_values() = model_value;
}
}
}
break;
case autofill_assistant::UserFormSectionProto::kPopupListSection:
if (user_data.has_additional_value(
additional_section.popup_list_section().additional_value_key()) &&
additional_section.send_result_to_backend()) {
auto value = user_data.additional_values_.find(
additional_section.popup_list_section().additional_value_key());
autofill_assistant::ModelProto_ModelValue model_value;
model_value.set_identifier(
additional_section.popup_list_section().additional_value_key());
*model_value.mutable_value() = value->second;
*processed_action_proto->mutable_collect_user_data_result()
->add_additional_sections_values() = model_value;
}
break;
case autofill_assistant::UserFormSectionProto::kStaticTextSection:
case autofill_assistant::UserFormSectionProto::SECTION_NOT_SET:
// Do nothing.
break;
}
}
bool IsAdditionalSectionComplete(
const std::map<std::string, autofill_assistant::ValueProto>&
additional_sections,
const autofill_assistant::UserFormSectionProto& section) {
if (section.section_case() !=
autofill_assistant::UserFormSectionProto::kPopupListSection ||
!section.popup_list_section().selection_mandatory()) {
return true;
}
auto find_result = additional_sections.find(
section.popup_list_section().additional_value_key());
if (find_result != additional_sections.end() &&
!find_result->second.ints().values().empty()) {
return true;
}
return false;
}
bool AreAdditionalSectionsComplete(
const std::map<std::string, autofill_assistant::ValueProto>&
additional_sections,
const CollectUserDataOptions& collect_user_data_options) {
for (const auto& section :
collect_user_data_options.additional_prepended_sections) {
if (!IsAdditionalSectionComplete(additional_sections, section)) {
return false;
}
}
for (const auto& section :
collect_user_data_options.additional_appended_sections) {
if (!IsAdditionalSectionComplete(additional_sections, section)) {
return false;
}
}
return true;
}
void SetInitialUserDataForAdditionalSection(
const autofill_assistant::UserFormSectionProto& additional_section,
UserData* user_data) {
switch (additional_section.section_case()) {
case autofill_assistant::UserFormSectionProto::kTextInputSection: {
for (const auto& text_input :
additional_section.text_input_section().input_fields()) {
autofill_assistant::ValueProto value;
value.mutable_strings()->add_values(text_input.value());
user_data->additional_values_[text_input.client_memory_key()] = value;
}
break;
}
case autofill_assistant::UserFormSectionProto::kPopupListSection: {
autofill_assistant::ValueProto value;
for (const auto& selection :
additional_section.popup_list_section().initial_selection()) {
value.mutable_ints()->add_values(selection);
}
user_data->additional_values_[additional_section.popup_list_section()
.additional_value_key()] = value;
break;
}
case autofill_assistant::UserFormSectionProto::kStaticTextSection:
case autofill_assistant::UserFormSectionProto::SECTION_NOT_SET:
// Do nothing.
break;
}
}
void AddNonEmptyFieldNames(
const autofill::AutofillProfile* profile,
google::protobuf::RepeatedPtrField<std::string>* dest) {
DCHECK(profile != nullptr);
const auto& map = autofill_assistant::field_formatter::CreateAutofillMappings(
*profile, /* locale= */ "en-US");
for (const auto& it : map) {
*dest->Add() = it.first;
}
}
} // namespace
namespace autofill_assistant {
CollectUserDataAction::LoginDetails::LoginDetails(
bool _choose_automatically_if_no_stored_login,
const std::string& _payload,
const WebsiteLoginManager::Login& _login)
: choose_automatically_if_no_stored_login(
_choose_automatically_if_no_stored_login),
payload(_payload),
login(_login) {}
CollectUserDataAction::LoginDetails::LoginDetails(
bool _choose_automatically_if_no_stored_login,
const std::string& _payload)
: choose_automatically_if_no_stored_login(
_choose_automatically_if_no_stored_login),
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_);
}
}
bool CollectUserDataAction::ShouldInterruptOnPause() const {
return true;
}
void CollectUserDataAction::InternalProcessAction(
ProcessActionCallback callback) {
callback_ = std::move(callback);
if (!CreateOptionsFromProto()) {
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(), 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_->GetWebsiteLoginManager()->GetLoginsForUrl(
delegate_->GetWebContents()->GetLastCommittedURL(),
base::BindOnce(&CollectUserDataAction::OnGetLogins,
weak_ptr_factory_.GetWeakPtr(),
*password_manager_option));
} else {
ShowToUser();
}
action_stopwatch_.StartWaitTime();
}
void CollectUserDataAction::EndAction(const ClientStatus& status) {
delegate_->CleanUpAfterPrompt();
action_successful_ = status.ok();
UpdateProcessedAction(status);
if (action_successful_) {
delegate_->SetLastSuccessfulUserDataOptions(
std::move(collect_user_data_options_));
}
std::move(callback_).Run(std::move(processed_action_proto_));
}
void CollectUserDataAction::OnGetLogins(
const LoginDetailsProto::LoginOptionProto& login_option,
std::vector<WebsiteLoginManager::Login> logins) {
for (const auto& login : logins) {
auto identifier =
base::NumberToString(collect_user_data_options_->login_choices.size());
collect_user_data_options_->login_choices.emplace_back(
identifier, login.username, login_option.sublabel(),
login_option.sublabel_accessibility_hint(),
login_option.preselection_priority(),
login_option.has_info_popup()
? base::make_optional(login_option.info_popup())
: base::nullopt,
login_option.has_edit_button_content_description()
? base::make_optional(
login_option.edit_button_content_description())
: base::nullopt);
login_details_map_.emplace(
identifier, std::make_unique<LoginDetails>(
login_option.choose_automatically_if_no_stored_login(),
login_option.payload(), login));
}
ShowToUser();
}
void CollectUserDataAction::ShowToUser() {
// Set initial state.
delegate_->WriteUserData(base::BindOnce(&CollectUserDataAction::OnShowToUser,
weak_ptr_factory_.GetWeakPtr()));
}
void CollectUserDataAction::OnShowToUser(UserData* user_data,
UserData::FieldChange* field_change) {
// merge the new proto_ into the existing user_data. the proto_ always takes
// precedence over the existing user_data.
*field_change = UserData::FieldChange::ALL;
auto collect_user_data = proto_.collect_user_data();
// the backend should explicitly set the terms and conditions state on every
// new action.
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()) {
SetInitialUserDataForAdditionalSection(additional_section, user_data);
}
for (const auto& additional_section :
collect_user_data.additional_appended_sections()) {
SetInitialUserDataForAdditionalSection(additional_section, user_data);
}
if (collect_user_data_options_->request_login_choice &&
collect_user_data_options_->login_choices.empty()) {
EndAction(ClientStatus(COLLECT_USER_DATA_ERROR));
return;
}
bool has_password_manager_logins =
std::find_if(login_details_map_.begin(), login_details_map_.end(),
[&](const auto& pair) {
return pair.second->login.has_value();
}) != login_details_map_.end();
auto automatic_choice_it = std::find_if(
login_details_map_.begin(), login_details_map_.end(),
[&](const auto& pair) {
return pair.second->choose_automatically_if_no_stored_login;
});
// Special case: if the login choice can be made implicitly (there are no PWM
// logins and there is a |choose_automatically_if_no_stored_login| choice),
// the section will not be shown.
if (automatic_choice_it != login_details_map_.end() &&
!has_password_manager_logins) {
user_data->login_choice_identifier_.assign(automatic_choice_it->first);
// 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 (OnlyLoginRequested(*collect_user_data_options_)) {
std::move(collect_user_data_options_->confirm_callback)
.Run(user_data, nullptr);
return;
}
if (collect_user_data_options_->login_choices.size() == 1) {
// The login section does not offer a meaningful choice, but there is at
// least one other section being shown. Hide the logins, show the rest.
collect_user_data_options_->request_login_choice = false;
}
}
// Clear previously selected info, if requested.
if (proto_.collect_user_data().clear_previous_credit_card_selection()) {
user_data->selected_card_.reset();
}
if (proto_.collect_user_data().clear_previous_login_selection()) {
user_data->selected_login_.reset();
}
for (const auto& profile_name :
proto_.collect_user_data().clear_previous_profile_selection()) {
user_data->selected_addresses_.erase(profile_name);
}
// Add available profiles and start listening.
delegate_->GetPersonalDataManager()->AddObserver(this);
UpdatePersonalDataManagerProfiles(user_data);
UpdatePersonalDataManagerCards(user_data);
UpdateDateTimeRangeStart(user_data);
UpdateDateTimeRangeEnd(user_data);
// Gather info for UMA histograms.
if (!shown_to_user_) {
shown_to_user_ = true;
initially_prefilled =
CheckInitialAutofillDataComplete(delegate_->GetPersonalDataManager());
}
if (collect_user_data.has_prompt()) {
delegate_->SetStatusMessage(collect_user_data.prompt());
}
delegate_->Prompt(/* user_actions = */ nullptr,
/* disable_force_expand_sheet = */ false);
delegate_->CollectUserData(collect_user_data_options_.get());
}
void CollectUserDataAction::OnGetUserData(
const CollectUserDataProto& collect_user_data,
UserData* user_data,
const UserModel* user_model) {
if (!callback_)
return;
action_stopwatch_.StartActiveTime();
delegate_->GetPersonalDataManager()->RemoveObserver(this);
WriteProcessedAction(user_data, user_model);
DCHECK(
IsUserDataComplete(*user_data, *user_model, *collect_user_data_options_));
EndAction(ClientStatus(ACTION_APPLIED));
}
void CollectUserDataAction::OnAdditionalActionTriggered(
int index,
UserData* user_data,
const UserModel* user_model) {
if (!callback_)
return;
action_stopwatch_.StartActiveTime();
delegate_->GetPersonalDataManager()->RemoveObserver(this);
processed_action_proto_->mutable_collect_user_data_result()
->set_additional_action_index(index);
WriteProcessedAction(user_data, user_model);
EndAction(ClientStatus(ACTION_APPLIED));
}
void CollectUserDataAction::OnTermsAndConditionsLinkClicked(
int link,
UserData* user_data,
const UserModel* user_model) {
if (!callback_)
return;
action_stopwatch_.StartActiveTime();
delegate_->GetPersonalDataManager()->RemoveObserver(this);
processed_action_proto_->mutable_collect_user_data_result()->set_terms_link(
link);
WriteProcessedAction(user_data, user_model);
EndAction(ClientStatus(ACTION_APPLIED));
}
bool CollectUserDataAction::CreateOptionsFromProto() {
DCHECK(collect_user_data_options_ == nullptr);
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();
// TODO(b/146405276): Remove legacy support for |summary_fields| and
// |full_fields|.
if (contact_details.summary_fields().empty()) {
collect_user_data_options_->contact_summary_max_lines =
kDefaultMaxNumberContactSummaryLines;
collect_user_data_options_->contact_summary_fields.assign(
kDefaultContactSummaryFields.begin(),
kDefaultContactSummaryFields.end());
} else {
for (const auto& field : contact_details.summary_fields()) {
collect_user_data_options_->contact_summary_fields.emplace_back(
(AutofillContactField)field);
}
if (contact_details.max_number_summary_lines() <= 0) {
VLOG(1) << "max_number_summary_lines must be > 0";
return false;
}
collect_user_data_options_->contact_summary_max_lines =
contact_details.max_number_summary_lines();
}
if (contact_details.full_fields().empty()) {
collect_user_data_options_->contact_full_max_lines =
kDefaultMaxNumberContactFullLines;
collect_user_data_options_->contact_full_fields.assign(
kDefaultContactFullFields.begin(), kDefaultContactFullFields.end());
} else {
for (const auto& field : contact_details.full_fields()) {
collect_user_data_options_->contact_full_fields.emplace_back(
(AutofillContactField)field);
}
if (contact_details.max_number_full_lines() <= 0) {
VLOG(1) << "max_number_full_lines must be > 0";
return false;
}
collect_user_data_options_->contact_full_max_lines =
contact_details.max_number_full_lines();
}
if (collect_user_data_options_->request_payer_email ||
collect_user_data_options_->request_payer_name ||
collect_user_data_options_->request_payer_phone) {
if (!contact_details.has_contact_details_name()) {
VLOG(1) << "Contact details name missing";
return false;
}
}
collect_user_data_options_->contact_details_name =
contact_details.contact_details_name();
collect_user_data_options_->contact_details_section_title.assign(
contact_details.contact_details_section_title().empty()
? l10n_util::GetStringUTF8(IDS_PAYMENTS_CONTACT_DETAILS_LABEL)
: contact_details.contact_details_section_title());
}
for (const auto& network :
collect_user_data.supported_basic_card_networks()) {
if (!autofill::data_util::IsValidBasicCardIssuerNetwork(network)) {
VLOG(1) << "Invalid basic card network: " << network;
return false;
}
}
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_->shipping_address_name =
collect_user_data.shipping_address_name();
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()) {
VLOG(1) << "Required postal code without error text";
return false;
}
collect_user_data_options_->billing_address_name =
collect_user_data.billing_address_name();
if (collect_user_data_options_->request_payment_method &&
collect_user_data_options_->billing_address_name.empty()) {
VLOG(1) << "Required payment method without address name";
return false;
}
collect_user_data_options_->credit_card_expired_text =
collect_user_data.credit_card_expired_text();
// TODO(b/146195295): Remove fallback and enforce non-empty backend string.
if (collect_user_data_options_->credit_card_expired_text.empty()) {
collect_user_data_options_->credit_card_expired_text =
l10n_util::GetStringUTF8(
IDS_PAYMENTS_VALIDATION_INVALID_CREDIT_CARD_EXPIRED);
}
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());
collect_user_data_options_->shipping_address_section_title.assign(
collect_user_data.shipping_address_section_title().empty()
? l10n_util::GetStringUTF8(IDS_PAYMENTS_SHIPPING_ADDRESS_LABEL)
: collect_user_data.shipping_address_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: {
const std::string identifier = base::NumberToString(
collect_user_data_options_->login_choices.size());
LoginChoice choice = {
identifier,
login_option.custom().label(),
login_option.sublabel(),
login_option.has_sublabel_accessibility_hint()
? base::make_optional(
login_option.sublabel_accessibility_hint())
: base::nullopt,
login_option.has_preselection_priority()
? login_option.preselection_priority()
: -1,
login_option.has_info_popup()
? base::make_optional(login_option.info_popup())
: base::nullopt,
login_option.has_edit_button_content_description()
? base::make_optional(
login_option.edit_button_content_description())
: base::nullopt};
collect_user_data_options_->login_choices.emplace_back(
std::move(choice));
login_details_map_.emplace(
identifier,
std::make_unique<LoginDetails>(
login_option.choose_automatically_if_no_stored_login(),
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).
VLOG(1) << "LoginDetailsProto::LoginOptionProto::TYPE_NOT_SET";
return false;
}
}
}
if (collect_user_data.has_date_time_range()) {
std::string error_message;
if (!IsValidDateTimeRangeProto(collect_user_data.date_time_range(),
&error_message)) {
VLOG(1) << "Invalid action: " << error_message;
return false;
}
if (collect_user_data.date_time_range().start_time_slot() < 0 ||
collect_user_data.date_time_range().end_time_slot() < 0 ||
collect_user_data.date_time_range().start_time_slot() >=
collect_user_data.date_time_range().time_slots().size() ||
collect_user_data.date_time_range().end_time_slot() >=
collect_user_data.date_time_range().time_slots().size()) {
VLOG(1) << "Invalid action: time slot index out of range";
return false;
}
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)) {
VLOG(1)
<< "Invalid UserFormSectionProto in additional_prepended_sections";
return false;
}
collect_user_data_options_->additional_prepended_sections.emplace_back(
section);
}
for (const auto& section : collect_user_data.additional_appended_sections()) {
if (!IsValidUserFormSection(section)) {
VLOG(1) << "Invalid UserFormSectionProto in additional_appended_sections";
return false;
}
collect_user_data_options_->additional_appended_sections.emplace_back(
section);
}
if (collect_user_data.has_info_section_text() &&
collect_user_data.info_section_text().empty()) {
VLOG(1) << "Info text set but empty.";
return false;
}
if (collect_user_data.has_generic_user_interface_prepended()) {
collect_user_data_options_->generic_user_interface_prepended =
collect_user_data.generic_user_interface_prepended();
}
if (collect_user_data.has_generic_user_interface_appended()) {
collect_user_data_options_->generic_user_interface_appended =
collect_user_data.generic_user_interface_appended();
}
if (collect_user_data.has_additional_model_identifier_to_check()) {
collect_user_data_options_->additional_model_identifier_to_check =
collect_user_data.additional_model_identifier_to_check();
}
auto* confirm_chip =
collect_user_data_options_->confirm_action.mutable_chip();
if (collect_user_data.has_confirm_chip()) {
*confirm_chip = collect_user_data.confirm_chip();
} else {
confirm_chip->set_text(
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM));
confirm_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();
if (collect_user_data.accept_terms_and_conditions_text().empty()) {
VLOG(1) << "Required terms and conditions without text";
return false;
}
collect_user_data_options_->accept_terms_and_conditions_text =
collect_user_data.accept_terms_and_conditions_text();
if (!collect_user_data.show_terms_as_checkbox() &&
collect_user_data.terms_require_review_text().empty()) {
VLOG(1) << "Required terms review without text";
return false;
}
collect_user_data_options_->terms_require_review_text =
collect_user_data.terms_require_review_text();
}
if (collect_user_data.has_info_section_text()) {
collect_user_data_options_->info_section_text =
collect_user_data.info_section_text();
collect_user_data_options_->info_section_text_center =
collect_user_data.info_section_text_center();
}
collect_user_data_options_->privacy_notice_text =
collect_user_data.privacy_notice_text();
collect_user_data_options_->default_email =
delegate_->GetEmailAddressForAccessTokenAccount();
return true;
}
bool CollectUserDataAction::CheckInitialAutofillDataComplete(
autofill::PersonalDataManager* personal_data_manager) {
DCHECK(collect_user_data_options_ != nullptr);
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(), [this](const auto& profile) {
return IsCompleteContact(profile,
*this->collect_user_data_options_.get());
});
if (completeContactIter == profiles.end()) {
return false;
}
}
if (collect_user_data_options_->request_shipping) {
auto completeAddressIter = std::find_if(
profiles.begin(), profiles.end(), [this](const auto* profile) {
return IsCompleteShippingAddress(
profile, *this->collect_user_data_options_.get());
});
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(),
[this, personal_data_manager](const auto* credit_card) {
// TODO(b/142630213): Figure out how to retrieve billing profile if
// user has turned off addresses in Chrome settings.
return IsCompleteCreditCard(
credit_card,
credit_card != nullptr
? personal_data_manager->GetProfileByGUID(credit_card->guid())
: nullptr,
*this->collect_user_data_options_.get());
});
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;
}
// TODO(b/148448649): Move to dedicated helper namespace.
// static
bool CollectUserDataAction::IsUserDataComplete(
const UserData& user_data,
const UserModel& user_model,
const CollectUserDataOptions& options) {
auto* selected_profile =
user_data.selected_address(options.contact_details_name);
auto* billing_address =
user_data.selected_address(options.billing_address_name);
auto* shipping_address =
user_data.selected_address(options.shipping_address_name);
return IsCompleteContact(selected_profile, options) &&
IsCompleteShippingAddress(shipping_address, options) &&
IsCompleteCreditCard(user_data.selected_card_.get(), billing_address,
options) &&
IsValidLoginChoice(user_data.login_choice_identifier_, options) &&
IsValidTermsChoice(user_data.terms_and_conditions_, options) &&
IsValidDateTimeRange(user_data.date_time_range_start_date_,
user_data.date_time_range_start_timeslot_,
user_data.date_time_range_end_date_,
user_data.date_time_range_end_timeslot_,
options) &&
AreAdditionalSectionsComplete(user_data.additional_values_, options) &&
IsValidUserModel(user_model, options);
}
// TODO(b/148448649): Move to dedicated helper namespace.
// static
int CollectUserDataAction::CompareDates(const DateProto& first,
const DateProto& second) {
auto first_tuple = std::make_tuple(first.year(), first.month(), first.day());
auto second_tuple =
std::make_tuple(second.year(), second.month(), second.day());
if (first_tuple < second_tuple) {
return -1;
} else if (second_tuple < first_tuple) {
return 1;
}
return 0;
}
// TODO(b/148448649): Move to dedicated helper namespace.
// static
bool CollectUserDataAction::SanitizeDateTimeRange(
base::Optional<DateProto>* start_date,
base::Optional<int>* start_timeslot,
base::Optional<DateProto>* end_date,
base::Optional<int>* end_timeslot,
const CollectUserDataOptions& collect_user_data_options,
bool change_start) {
if (!collect_user_data_options.request_date_time_range) {
return false;
}
DCHECK(start_date);
DCHECK(start_timeslot);
DCHECK(end_date);
DCHECK(end_timeslot);
if (!start_date->has_value() || !end_date->has_value()) {
return false;
}
auto date_comparison = CompareDates(**start_date, **end_date);
if (date_comparison < 0) {
return false;
}
// Start date > end date, reset date.
if (date_comparison > 0) {
if (change_start) {
start_date->reset();
} else {
end_date->reset();
}
return true;
}
if (!start_timeslot->has_value() || !end_timeslot->has_value()) {
return false;
}
DCHECK(**start_timeslot >= 0 &&
**start_timeslot <
collect_user_data_options.date_time_range.time_slots().size());
DCHECK(**end_timeslot >= 0 &&
**end_timeslot <
collect_user_data_options.date_time_range.time_slots().size());
auto start_time =
collect_user_data_options.date_time_range.time_slots(**start_timeslot);
auto end_time =
collect_user_data_options.date_time_range.time_slots(**end_timeslot);
auto time_comparison =
start_time.comparison_value() - end_time.comparison_value();
if (time_comparison < 0) {
return false;
}
// Start date == end date and start time >= end time, reset time.
if (time_comparison >= 0) {
if (change_start) {
start_timeslot->reset();
} else {
end_timeslot->reset();
}
return true;
}
NOTREACHED();
return false;
}
void CollectUserDataAction::WriteProcessedAction(UserData* user_data,
const UserModel* user_model) {
if (proto().collect_user_data().request_payment_method() &&
user_data->selected_card_) {
std::string card_issuer_network =
autofill::data_util::GetPaymentRequestData(
user_data->selected_card_->network())
.basic_card_issuer_network;
processed_action_proto_->mutable_collect_user_data_result()
->set_card_issuer_network(card_issuer_network);
}
if (proto().collect_user_data().has_contact_details()) {
auto contact_details_proto = proto().collect_user_data().contact_details();
auto* selected_profile = user_data->selected_address(
contact_details_proto.contact_details_name());
if (selected_profile != nullptr) {
AddNonEmptyFieldNames(
selected_profile,
processed_action_proto_->mutable_collect_user_data_result()
->mutable_non_empty_contact_field());
if (contact_details_proto.request_payer_name()) {
Metrics::RecordPaymentRequestFirstNameOnly(
selected_profile->GetRawInfo(autofill::NAME_LAST).empty());
}
if (contact_details_proto.request_payer_email()) {
processed_action_proto_->mutable_collect_user_data_result()
->set_payer_email(base::UTF16ToUTF8(
selected_profile->GetRawInfo(autofill::EMAIL_ADDRESS)));
}
}
}
if (!proto().collect_user_data().shipping_address_name().empty()) {
auto* selected_shipping_address = user_data->selected_address(
proto().collect_user_data().shipping_address_name());
if (selected_shipping_address != nullptr) {
AddNonEmptyFieldNames(
selected_shipping_address,
processed_action_proto_->mutable_collect_user_data_result()
->mutable_non_empty_shipping_address_field());
}
}
if (!proto().collect_user_data().billing_address_name().empty()) {
auto* selected_billing_address = user_data->selected_address(
proto().collect_user_data().billing_address_name());
if (selected_billing_address != nullptr) {
AddNonEmptyFieldNames(
selected_billing_address,
processed_action_proto_->mutable_collect_user_data_result()
->mutable_non_empty_billing_address_field());
}
}
if (proto().collect_user_data().has_login_details()) {
auto login_details =
login_details_map_.find(user_data->login_choice_identifier_);
if (login_details != login_details_map_.end()) {
if (login_details->second->login.has_value()) {
user_data->selected_login_ = *login_details->second->login;
if (login_details->second->login->username.empty()) {
processed_action_proto_->mutable_collect_user_data_result()
->set_login_missing_username(true);
}
}
processed_action_proto_->mutable_collect_user_data_result()
->set_login_payload(login_details->second->payload);
}
}
if (proto().collect_user_data().has_date_time_range()) {
if (user_data->date_time_range_start_date_.has_value()) {
*processed_action_proto_->mutable_collect_user_data_result()
->mutable_date_range_start_date() =
*user_data->date_time_range_start_date_;
}
if (user_data->date_time_range_start_timeslot_.has_value()) {
processed_action_proto_->mutable_collect_user_data_result()
->set_date_range_start_timeslot(
*user_data->date_time_range_start_timeslot_);
}
if (user_data->date_time_range_end_date_.has_value()) {
*processed_action_proto_->mutable_collect_user_data_result()
->mutable_date_range_end_date() =
*user_data->date_time_range_end_date_;
}
if (user_data->date_time_range_end_timeslot_.has_value()) {
processed_action_proto_->mutable_collect_user_data_result()
->set_date_range_end_timeslot(
*user_data->date_time_range_end_timeslot_);
}
}
for (const auto& section :
proto().collect_user_data().additional_prepended_sections()) {
FillProtoForAdditionalSection(section, *user_data,
processed_action_proto_.get());
}
for (const auto& section :
proto().collect_user_data().additional_appended_sections()) {
FillProtoForAdditionalSection(section, *user_data,
processed_action_proto_.get());
}
processed_action_proto_->mutable_collect_user_data_result()
->set_is_terms_and_conditions_accepted(user_data->terms_and_conditions_ ==
TermsAndConditionsState::ACCEPTED);
if (user_model != nullptr &&
(proto().collect_user_data().has_generic_user_interface_prepended() ||
proto().collect_user_data().has_generic_user_interface_appended())) {
// Build the union of both models (this assumes that there are no
// overlapping model keys).
*processed_action_proto_->mutable_collect_user_data_result()
->mutable_model() = MergeModelProtos(
proto().collect_user_data().generic_user_interface_prepended().model(),
proto().collect_user_data().generic_user_interface_appended().model());
user_model->UpdateProto(
processed_action_proto_->mutable_collect_user_data_result()
->mutable_model());
}
processed_action_proto_->mutable_collect_user_data_result()
->set_shown_to_user(shown_to_user_);
}
void CollectUserDataAction::UpdatePersonalDataManagerProfiles(
UserData* user_data,
UserData::FieldChange* field_change) {
if (user_data == nullptr) {
return;
}
bool found_profile = false;
bool found_shipping_address = false;
auto* selected_profile = user_data->selected_address(
collect_user_data_options_->contact_details_name);
auto* shipping_address = user_data->selected_address(
collect_user_data_options_->shipping_address_name);
user_data->available_profiles_.clear();
for (const auto* profile :
delegate_->GetPersonalDataManager()->GetProfilesToSuggest()) {
user_data->available_profiles_.emplace_back(
MakeUniqueFromProfile(*profile));
if (selected_profile != nullptr &&
CompareContactDetails(*collect_user_data_options_, profile,
selected_profile)) {
found_profile = true;
}
if (shipping_address != nullptr &&
profile->Compare(*shipping_address) == 0) {
found_shipping_address = true;
}
}
if (!found_profile && selected_profile != nullptr) {
auto it = user_data->selected_addresses_.find(
collect_user_data_options_->contact_details_name);
if (it != user_data->selected_addresses_.end()) {
user_data->selected_addresses_.erase(it);
}
}
if (!user_data->has_selected_address(
collect_user_data_options_->contact_details_name) &&
(collect_user_data_options_->request_payer_name ||
collect_user_data_options_->request_payer_phone ||
collect_user_data_options_->request_payer_email)) {
int default_selection = GetDefaultContactProfile(
*collect_user_data_options_, user_data->available_profiles_);
if (default_selection != -1) {
user_data->selected_addresses_.emplace(
collect_user_data_options_->contact_details_name,
MakeUniqueFromProfile(
*(user_data->available_profiles_[default_selection])));
}
}
if (!found_shipping_address && shipping_address != nullptr) {
auto it = user_data->selected_addresses_.find(
collect_user_data_options_->shipping_address_name);
if (it != user_data->selected_addresses_.end()) {
user_data->selected_addresses_.erase(it);
}
}
if (!user_data->has_selected_address(
collect_user_data_options_->shipping_address_name) &&
collect_user_data_options_->request_shipping) {
int default_selection = GetDefaultAddressProfile(
*collect_user_data_options_, user_data->available_profiles_);
if (default_selection != -1) {
user_data->selected_addresses_.emplace(
collect_user_data_options_->shipping_address_name,
MakeUniqueFromProfile(
*(user_data->available_profiles_[default_selection])));
}
}
if (field_change != nullptr) {
*field_change = UserData::FieldChange::AVAILABLE_PROFILES;
}
}
void CollectUserDataAction::UpdatePersonalDataManagerCards(
UserData* user_data,
UserData::FieldChange* field_change) {
DCHECK(user_data != nullptr);
bool found_card = false;
user_data->available_payment_instruments_.clear();
for (const auto* card :
delegate_->GetPersonalDataManager()->GetCreditCardsToSuggest(true)) {
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(),
autofill::data_util::GetPaymentRequestData(card->network())
.basic_card_issuer_network) !=
collect_user_data_options_->supported_basic_card_networks.end()) {
auto payment_instrument = std::make_unique<PaymentInstrument>();
payment_instrument->card = std::make_unique<autofill::CreditCard>(*card);
if (!card->billing_address_id().empty()) {
auto* billing_address =
delegate_->GetPersonalDataManager()->GetProfileByGUID(
card->billing_address_id());
if (billing_address != nullptr) {
payment_instrument->billing_address =
MakeUniqueFromProfile(*billing_address);
}
}
user_data->available_payment_instruments_.emplace_back(
std::move(payment_instrument));
if (user_data->selected_card_ != nullptr &&
card->Compare(*user_data->selected_card_) == 0) {
found_card = true;
}
}
}
if (!found_card) {
user_data->selected_card_.reset();
auto it = user_data->selected_addresses_.find(
collect_user_data_options_->billing_address_name);
if (it != user_data->selected_addresses_.end()) {
user_data->selected_addresses_.erase(it);
}
}
if (user_data->selected_card_ == nullptr &&
collect_user_data_options_->request_payment_method) {
int default_selection = GetDefaultPaymentInstrument(
*collect_user_data_options_, user_data->available_payment_instruments_);
if (default_selection != -1) {
const auto& default_payment_instrument =
user_data->available_payment_instruments_[default_selection];
user_data->selected_card_ = std::make_unique<autofill::CreditCard>(
*(default_payment_instrument->card));
if (default_payment_instrument->billing_address != nullptr) {
user_data->selected_addresses_.emplace(
collect_user_data_options_->billing_address_name,
std::make_unique<autofill::AutofillProfile>(
*(default_payment_instrument->billing_address)));
}
}
}
if (field_change != nullptr) {
*field_change = UserData::FieldChange::AVAILABLE_PAYMENT_INSTRUMENTS;
}
}
void CollectUserDataAction::OnPersonalDataChanged() {
if (!callback_) {
return;
}
personal_data_changed_ = true;
delegate_->WriteUserData(
base::BindOnce(&CollectUserDataAction::UpdatePersonalDataManagerProfiles,
weak_ptr_factory_.GetWeakPtr()));
delegate_->WriteUserData(
base::BindOnce(&CollectUserDataAction::UpdatePersonalDataManagerCards,
weak_ptr_factory_.GetWeakPtr()));
}
void CollectUserDataAction::UpdateDateTimeRangeStart(
UserData* user_data,
UserData::FieldChange* field_change) {
DCHECK(user_data != nullptr);
DCHECK(collect_user_data_options_ != nullptr);
UserData::FieldChange changed = UserData::FieldChange::NONE;
if (!user_data->date_time_range_start_date_.has_value()) {
user_data->date_time_range_start_date_ =
collect_user_data_options_->date_time_range.start_date();
changed = UserData::FieldChange::DATE_TIME_RANGE_START;
}
if (!user_data->date_time_range_start_timeslot_.has_value()) {
user_data->date_time_range_start_timeslot_ =
collect_user_data_options_->date_time_range.start_time_slot();
changed = UserData::FieldChange::DATE_TIME_RANGE_START;
}
if (field_change != nullptr && changed != UserData::FieldChange::NONE) {
*field_change = changed;
}
}
void CollectUserDataAction::UpdateDateTimeRangeEnd(
UserData* user_data,
UserData::FieldChange* field_change) {
DCHECK(user_data != nullptr);
DCHECK(collect_user_data_options_ != nullptr);
UserData::FieldChange changed = UserData::FieldChange::NONE;
if (!user_data->date_time_range_end_date_.has_value()) {
user_data->date_time_range_end_date_ =
collect_user_data_options_->date_time_range.end_date();
changed = UserData::FieldChange::DATE_TIME_RANGE_END;
}
if (!user_data->date_time_range_end_timeslot_.has_value()) {
user_data->date_time_range_end_timeslot_ =
collect_user_data_options_->date_time_range.end_time_slot();
changed = UserData::FieldChange::DATE_TIME_RANGE_END;
}
if (field_change != nullptr && changed != UserData::FieldChange::NONE) {
*field_change = changed;
}
}
} // namespace autofill_assistant