blob: bf5ba0c65d5ea1f256b91eea779c47a7b54afc46 [file] [log] [blame]
// Copyright 2019 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/details.h"
#include <unordered_set>
#include <base/strings/stringprintf.h>
#include "base/i18n/time_formatting.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/geo/country_names.h"
#include "components/autofill_assistant/browser/trigger_context.h"
#include "components/strings/grit/components_strings.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/base/l10n/l10n_util.h"
namespace autofill_assistant {
namespace {
// TODO(b/141850276): Remove hardcoded formatting strings.
constexpr char kDateFormat[] = "EEE, MMM d";
constexpr char kTimeFormat[] = "h:mm a";
constexpr char kDateTimeSeparator[] = " \xE2\x80\xA2 ";
constexpr char kSpaceBetweenCardNumAndDate[] = " ";
// Parse RFC 3339 date-time. Store the value in the datetime proto field.
bool ParseDateTimeStringToProto(const std::string& datetime,
DateTimeProto* result) {
// RFC 3339 format without timezone: yyyy'-'MM'-'dd'T'HH':'mm':'ss
std::string pattern =
R"rgx((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}))rgx";
int year, month, day, hour, minute, second;
if (re2::RE2::FullMatch(datetime, pattern, &year, &month, &day, &hour,
&minute, &second)) {
auto* date = result->mutable_date();
date->set_year(year);
date->set_month(month);
date->set_day(day);
auto* time = result->mutable_time();
time->set_hour(hour);
time->set_minute(minute);
time->set_second(second);
return true;
} else {
return false;
}
}
// Format a datetime proto with current locale.
std::string FormatDateTimeProto(const DateTimeProto& date_time) {
if (!date_time.has_date() || !date_time.has_time()) {
return std::string();
}
auto date_proto = date_time.date();
auto time_proto = date_time.time();
base::Time::Exploded exploded_time = {
date_proto.year(), date_proto.month(),
/* day_of_week = */ -1, date_proto.day(), time_proto.hour(),
time_proto.minute(), time_proto.second(), 0};
base::Time time;
if (base::Time::FromLocalExploded(exploded_time, &time)) {
auto date_string = base::TimeFormatWithPattern(time, kDateFormat);
auto time_string = base::TimeFormatWithPattern(time, kTimeFormat);
return base::StrCat({base::UTF16ToUTF8(time_string), kDateTimeSeparator,
base::UTF16ToUTF8(date_string)});
}
return std::string();
}
// This logic is from NameInfo::FullName.
base::string16 FullName(const autofill::AutofillProfile& profile) {
return autofill::data_util::JoinNameParts(
profile.GetRawInfo(autofill::NAME_FIRST),
profile.GetRawInfo(autofill::NAME_MIDDLE),
profile.GetRawInfo(autofill::NAME_LAST));
}
} // namespace
Details::Details() = default;
Details::~Details() = default;
// static
bool Details::UpdateFromProto(const ShowDetailsProto& proto, Details* details) {
if (!proto.has_details()) {
return false;
}
ShowDetailsProto updated_proto = proto;
// Legacy treatment for old proto fields. Can be removed once the backend
// is updated to set the description_line_1/line_2 fields.
// TODO(crbug.com/806868): Is this still needed?
if (updated_proto.details().has_description() &&
!updated_proto.details().has_description_line_2()) {
updated_proto.mutable_details()->set_description_line_2(
updated_proto.details().description());
}
details->SetDetailsProto(updated_proto.details());
details->SetDetailsChangesProto(updated_proto.change_flags());
return true;
}
// static
bool Details::UpdateFromContactDetails(
const ShowDetailsProto& proto,
const UserData* user_data,
const CollectUserDataOptions* user_data_options,
Details* details) {
if (!user_data_options || !(user_data_options->request_payer_name ||
user_data_options->request_payer_email)) {
return false;
}
std::string contact_details = proto.contact_details();
if (!user_data->has_selected_address(contact_details)) {
return false;
}
ShowDetailsProto updated_proto = proto;
auto* profile = user_data->selected_address(contact_details);
auto* details_proto = updated_proto.mutable_details();
details_proto->set_title(
l10n_util::GetStringUTF8(IDS_PAYMENTS_CONTACT_DETAILS_LABEL));
if (user_data_options->request_payer_name) {
details_proto->set_description_line_1(
base::UTF16ToUTF8(FullName(*profile)));
}
if (user_data_options->request_payer_email) {
details_proto->set_description_line_2(
base::UTF16ToUTF8(profile->GetRawInfo(autofill::EMAIL_ADDRESS)));
}
details->SetDetailsProto(updated_proto.details());
details->SetDetailsChangesProto(updated_proto.change_flags());
return true;
}
// static
bool Details::UpdateFromShippingAddress(const ShowDetailsProto& proto,
const UserData* user_data,
Details* details) {
std::string shipping_address = proto.shipping_address();
if (!user_data->has_selected_address(shipping_address)) {
return false;
}
ShowDetailsProto updated_proto = proto;
auto* profile = user_data->selected_address(shipping_address);
auto* details_proto = updated_proto.mutable_details();
autofill::CountryNames* country_names = autofill::CountryNames::GetInstance();
details_proto->set_title(
l10n_util::GetStringUTF8(IDS_PAYMENTS_SHIPPING_ADDRESS_LABEL));
details_proto->set_description_line_1(base::UTF16ToUTF8(FullName(*profile)));
details_proto->set_description_line_2(base::StrCat({
base::UTF16ToUTF8(
profile->GetRawInfo(autofill::ADDRESS_HOME_STREET_ADDRESS)),
" ",
base::UTF16ToUTF8(profile->GetRawInfo(autofill::ADDRESS_HOME_ZIP)),
" ",
base::UTF16ToUTF8(profile->GetRawInfo(autofill::ADDRESS_HOME_CITY)),
" ",
country_names->GetCountryCode(
profile->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)),
}));
details->SetDetailsProto(updated_proto.details());
details->SetDetailsChangesProto(updated_proto.change_flags());
return true;
}
bool Details::UpdateFromSelectedCreditCard(const ShowDetailsProto& proto,
const UserData* user_data,
Details* details) {
if (user_data->selected_card_.get() == nullptr || !proto.credit_card()) {
return false;
}
ShowDetailsProto updated_proto = proto;
auto* card = user_data->selected_card_.get();
auto* details_proto = updated_proto.mutable_details();
details_proto->set_title(
l10n_util::GetStringUTF8(IDS_PAYMENTS_METHOD_OF_PAYMENT_LABEL));
details_proto->set_description_line_1(
base::StrCat({base::UTF16ToUTF8(card->ObfuscatedLastFourDigits()),
kSpaceBetweenCardNumAndDate,
base::UTF16ToUTF8(card->AbbreviatedExpirationDateForDisplay(
/* with_prefix = */ false))}));
details->SetDetailsProto(updated_proto.details());
details->SetDetailsChangesProto(updated_proto.change_flags());
return true;
}
base::Value Details::GetDebugContext() const {
base::Value dict(base::Value::Type::DICTIONARY);
if (!proto_.title().empty())
dict.SetKey("title", base::Value(proto_.title()));
if (!proto_.image_url().empty())
dict.SetKey("image_url", base::Value(proto_.image_url()));
if (proto_.has_image_accessibility_hint())
dict.SetKey("image_accessibility_hint",
base::Value(proto_.image_accessibility_hint()));
if (!proto_.total_price().empty())
dict.SetKey("total_price", base::Value(proto_.total_price()));
if (!proto_.total_price_label().empty())
dict.SetKey("total_price_label", base::Value(proto_.total_price_label()));
if (!proto_.description_line_1().empty())
dict.SetKey("description_line_1", base::Value(proto_.description_line_1()));
if (!proto_.description_line_2().empty())
dict.SetKey("description_line_2", base::Value(proto_.description_line_2()));
if (!proto_.description_line_3().empty())
dict.SetKey("description_line_3", base::Value(proto_.description_line_3()));
if (proto_.has_datetime()) {
dict.SetKey(
"datetime",
base::Value(base::StringPrintf(
"%d-%02d-%02dT%02d:%02d:%02d",
static_cast<int>(proto_.datetime().date().year()),
proto_.datetime().date().month(), proto_.datetime().date().day(),
proto_.datetime().time().hour(), proto_.datetime().time().minute(),
proto_.datetime().time().second())));
}
dict.SetKey("user_approval_required",
base::Value(change_flags_.user_approval_required()));
dict.SetKey("highlight_title", base::Value(change_flags_.highlight_title()));
dict.SetKey("highlight_line1", base::Value(change_flags_.highlight_line1()));
dict.SetKey("highlight_line2", base::Value(change_flags_.highlight_line2()));
dict.SetKey("highlight_line3", base::Value(change_flags_.highlight_line3()));
dict.SetKey("highlight_line3", base::Value(change_flags_.highlight_line3()));
return dict;
}
bool Details::UpdateFromParameters(const TriggerContext& context) {
base::Optional<std::string> show_initial =
context.GetParameter("DETAILS_SHOW_INITIAL");
if (show_initial.value_or("true") == "false") {
return false;
}
// Whenever details are updated from parameters we want to show a placeholder
// for the image.
proto_.mutable_placeholders()->set_show_image_placeholder(true);
if (MaybeUpdateFromDetailsParameters(context)) {
Update();
return true;
}
// NOTE: The logic below is only needed for backward compatibility.
// Remove once we always pass detail parameters.
bool is_updated = false;
base::Optional<std::string> movie_name =
context.GetParameter("MOVIES_MOVIE_NAME");
if (movie_name) {
proto_.set_title(movie_name.value());
is_updated = true;
}
base::Optional<std::string> theater_name =
context.GetParameter("MOVIES_THEATER_NAME");
if (theater_name) {
proto_.set_description_line_2(theater_name.value());
is_updated = true;
}
base::Optional<std::string> screening_datetime =
context.GetParameter("MOVIES_SCREENING_DATETIME");
if (screening_datetime &&
ParseDateTimeStringToProto(screening_datetime.value(),
proto_.mutable_datetime())) {
is_updated = true;
}
Update();
return is_updated;
}
bool Details::MaybeUpdateFromDetailsParameters(const TriggerContext& context) {
bool details_updated = false;
base::Optional<std::string> title = context.GetParameter("DETAILS_TITLE");
if (title) {
proto_.set_title(title.value());
details_updated = true;
}
base::Optional<std::string> description_line_1 =
context.GetParameter("DETAILS_DESCRIPTION_LINE_1");
if (description_line_1) {
proto_.set_description_line_1(description_line_1.value());
details_updated = true;
}
base::Optional<std::string> description_line_2 =
context.GetParameter("DETAILS_DESCRIPTION_LINE_2");
if (description_line_2) {
proto_.set_description_line_2(description_line_2.value());
details_updated = true;
}
base::Optional<std::string> description_line_3 =
context.GetParameter("DETAILS_DESCRIPTION_LINE_3");
if (description_line_3) {
proto_.set_description_line_3(description_line_3.value());
details_updated = true;
}
base::Optional<std::string> image_url =
context.GetParameter("DETAILS_IMAGE_URL");
if (image_url) {
proto_.set_image_url(image_url.value());
details_updated = true;
}
base::Optional<std::string> image_accessibility_hint =
context.GetParameter("DETAILS_IMAGE_ACCESSIBILITY_HINT");
if (image_accessibility_hint) {
proto_.set_image_accessibility_hint(image_accessibility_hint.value());
details_updated = true;
}
base::Optional<std::string> image_clickthrough_url =
context.GetParameter("DETAILS_IMAGE_CLICKTHROUGH_URL");
if (image_clickthrough_url) {
proto_.mutable_image_clickthrough_data()->set_allow_clickthrough(true);
proto_.mutable_image_clickthrough_data()->set_clickthrough_url(
image_clickthrough_url.value());
details_updated = true;
}
base::Optional<std::string> total_price_label =
context.GetParameter("DETAILS_TOTAL_PRICE_LABEL");
if (total_price_label) {
proto_.set_total_price_label(total_price_label.value());
details_updated = true;
}
base::Optional<std::string> total_price =
context.GetParameter("DETAILS_TOTAL_PRICE");
if (total_price) {
proto_.set_total_price(total_price.value());
details_updated = true;
}
return details_updated;
}
void Details::SetDetailsProto(const DetailsProto& proto) {
proto_ = proto;
Update();
}
const std::string Details::title() const {
return proto_.title();
}
const std::string Details::imageUrl() const {
return proto_.image_url();
}
const base::Optional<std::string> Details::imageAccessibilityHint() const {
if (proto_.has_image_accessibility_hint()) {
return proto_.image_accessibility_hint();
}
return base::nullopt;
}
bool Details::imageAllowClickthrough() const {
return proto_.image_clickthrough_data().allow_clickthrough();
}
const std::string Details::imageDescription() const {
return proto_.image_clickthrough_data().description();
}
const std::string Details::imagePositiveText() const {
return proto_.image_clickthrough_data().positive_text();
}
const std::string Details::imageNegativeText() const {
return proto_.image_clickthrough_data().negative_text();
}
const std::string Details::imageClickthroughUrl() const {
return proto_.image_clickthrough_data().clickthrough_url();
}
const std::string Details::totalPriceLabel() const {
return proto_.total_price_label();
}
const std::string Details::totalPrice() const {
return proto_.total_price();
}
const std::string Details::descriptionLine1() const {
return description_line_1_content_;
}
const std::string Details::descriptionLine2() const {
return proto_.description_line_2();
}
const std::string Details::descriptionLine3() const {
return description_line_3_content_;
}
const std::string Details::priceAttribution() const {
return price_attribution_content_;
}
bool Details::userApprovalRequired() const {
return change_flags_.user_approval_required();
}
bool Details::highlightTitle() const {
return change_flags_.highlight_title();
}
bool Details::highlightLine1() const {
return change_flags_.highlight_line1();
}
bool Details::highlightLine2() const {
return change_flags_.highlight_line2();
}
bool Details::highlightLine3() const {
return change_flags_.highlight_line3();
}
DetailsProto::PlaceholdersConfiguration Details::placeholders() const {
return proto_.placeholders();
}
void Details::ClearChanges() {
change_flags_.Clear();
}
void Details::Update() {
auto formatted_datetime = FormatDateTimeProto(proto_.datetime());
description_line_1_content_.assign(proto_.description_line_1().empty()
? formatted_datetime
: proto_.description_line_1());
description_line_3_content_.assign(proto_.total_price().empty()
? proto_.description_line_3()
: std::string());
price_attribution_content_.assign(proto_.total_price().empty()
? std::string()
: proto_.description_line_3());
}
} // namespace autofill_assistant