// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/keyboard_accessory/android/address_accessory_controller_impl.h"
#include <utility>
#include "base/containers/span.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/android/preferences/autofill/settings_launcher_helper.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/keyboard_accessory/android/manual_filling_controller.h"
#include "chrome/browser/keyboard_accessory/android/manual_filling_utils.h"
#include "chrome/browser/plus_addresses/plus_address_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/android/plus_addresses/all_plus_addresses_bottom_sheet_controller.h"
#include "chrome/browser/ui/android/plus_addresses/plus_addresses_helper.h"
#include "components/autofill/content/browser/content_autofill_client.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/address_data_manager.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/plus_addresses/features.h"
#include "components/plus_addresses/plus_address_types.h"
#include "components/strings/grit/components_strings.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
namespace autofill {
namespace {
// Defines which types to load from the Personal data manager and add as field
// to the address sheet. Order matters.
constexpr FieldType kTypesToInclude[] = {
FieldType::NAME_FULL,
FieldType::COMPANY_NAME,
FieldType::ADDRESS_HOME_LINE1,
FieldType::ADDRESS_HOME_LINE2,
FieldType::ADDRESS_HOME_ZIP,
FieldType::ADDRESS_HOME_CITY,
FieldType::ADDRESS_HOME_STATE,
FieldType::ADDRESS_HOME_COUNTRY,
FieldType::PHONE_HOME_WHOLE_NUMBER,
FieldType::EMAIL_ADDRESS,
};
void AddProfileInfoAsSelectableField(UserInfo* info,
const AutofillProfile* profile,
FieldType type) {
std::u16string field = profile->GetRawInfo(type);
if (type == FieldType::NAME_MIDDLE && field.empty()) {
field = profile->GetRawInfo(FieldType::NAME_MIDDLE_INITIAL);
}
info->add_field(AccessorySheetField::Builder()
.SetDisplayText(std::move(field))
.SetSelectable(true)
.Build());
}
UserInfo TranslateProfile(const AutofillProfile* profile) {
UserInfo info;
for (FieldType field_type : kTypesToInclude) {
AddProfileInfoAsSelectableField(&info, profile, field_type);
}
return info;
}
std::vector<UserInfo> UserInfosForProfiles(
const std::vector<const AutofillProfile*>& profiles) {
std::vector<UserInfo> infos(profiles.size());
base::ranges::transform(profiles, infos.begin(), TranslateProfile);
return infos;
}
} // namespace
AddressAccessoryControllerImpl::~AddressAccessoryControllerImpl() {
if (personal_data_manager_)
personal_data_manager_->RemoveObserver(this);
if (plus_profiles_provider_) {
plus_profiles_provider_->RemoveObserver(this);
}
}
// static
AddressAccessoryController* AddressAccessoryController::GetOrCreate(
content::WebContents* web_contents) {
AddressAccessoryControllerImpl::CreateForWebContents(web_contents);
return AddressAccessoryControllerImpl::FromWebContents(web_contents);
}
void AddressAccessoryControllerImpl::RegisterFillingSourceObserver(
FillingSourceObserver observer) {
source_observer_ = std::move(observer);
}
std::optional<autofill::AccessorySheetData>
AddressAccessoryControllerImpl::GetSheetData() const {
base::span<const plus_addresses::PlusProfile> plus_profiles;
if (plus_profiles_provider_) {
plus_profiles = plus_profiles_provider_->GetAffiliatedPlusProfiles();
}
std::vector<const AutofillProfile*> profiles;
if (personal_data_manager_) {
profiles =
personal_data_manager_->address_data_manager().GetProfilesToSuggest();
}
std::u16string user_info_title, plus_address_title;
if (profiles.empty()) {
// User info title is not empty if and only if the list of addresses is
// empty.
user_info_title =
l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SHEET_EMPTY_MESSAGE);
auto* client = ContentAutofillClient::FromWebContents(&GetWebContents());
if (client && !plus_profiles.empty()) {
const std::u16string elided_url =
url_formatter::FormatOriginForSecurityDisplay(
client->GetLastCommittedPrimaryMainFrameOrigin(),
url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
plus_address_title = l10n_util::GetStringFUTF16(
IDS_PLUS_ADDRESS_FALLBACK_MANUAL_FILLING_SHEET_TITLE, elided_url);
}
}
AccessorySheetData sheet_data = autofill::CreateAccessorySheetData(
autofill::AccessoryTabType::ADDRESSES, user_info_title,
plus_address_title, UserInfosForProfiles(profiles),
CreateManageAddressesFooter());
for (const plus_addresses::PlusProfile& plus_profile : plus_profiles) {
sheet_data.add_plus_address_info(
PlusAddressInfo(plus_profile.facet.canonical_spec(),
base::UTF8ToUTF16(*plus_profile.plus_address)));
}
return sheet_data;
}
void AddressAccessoryControllerImpl::OnFillingTriggered(
FieldGlobalId focused_field_id,
const AccessorySheetField& selection) {
FillValueIntoField(focused_field_id, selection.display_text());
}
void AddressAccessoryControllerImpl::OnPasskeySelected(
const std::vector<uint8_t>& passkey_id) {
NOTIMPLEMENTED() << "Passkey support not available in address controller.";
}
void AddressAccessoryControllerImpl::OnOptionSelected(
AccessoryAction selected_action) {
switch (selected_action) {
case AccessoryAction::MANAGE_ADDRESSES:
autofill::ShowAutofillProfileSettings(&GetWebContents());
return;
case AccessoryAction::CREATE_PLUS_ADDRESS_FROM_ADDRESS_SHEET:
if (auto* client =
ContentAutofillClient::FromWebContents(&GetWebContents())) {
client->OfferPlusAddressCreation(
client->GetLastCommittedPrimaryMainFrameOrigin(),
base::BindOnce(
&AddressAccessoryControllerImpl::OnPlusAddressCreated,
weak_ptr_factory_.GetWeakPtr(),
GetManualFillingController()->GetLastFocusedFieldId()));
GetManualFillingController()->Hide();
}
return;
case AccessoryAction::SELECT_PLUS_ADDRESS_FROM_ADDRESS_SHEET:
if (!all_plus_addresses_bottom_sheet_controller_) {
all_plus_addresses_bottom_sheet_controller_ = std::make_unique<
plus_addresses::AllPlusAddressesBottomSheetController>(
&GetWebContents());
all_plus_addresses_bottom_sheet_controller_->Show(base::BindOnce(
&AddressAccessoryControllerImpl::OnPlusAddressSelected,
weak_ptr_factory_.GetWeakPtr(),
GetManualFillingController()->GetLastFocusedFieldId()));
GetManualFillingController()->Hide();
}
return;
case AccessoryAction::MANAGE_PLUS_ADDRESS_FROM_ADDRESS_SHEET:
plus_addresses::ShowManagePlusAddressesPage(GetWebContents());
return;
default:
NOTREACHED() << "Unhandled selected action: "
<< static_cast<int>(selected_action);
}
}
void AddressAccessoryControllerImpl::OnToggleChanged(
AccessoryAction toggled_action,
bool enabled) {
NOTREACHED() << "Unhandled toggled action: "
<< static_cast<int>(toggled_action);
}
void AddressAccessoryControllerImpl::RegisterPlusProfilesProvider(
base::WeakPtr<AffiliatedPlusProfilesProvider> provider) {
plus_profiles_provider_ = provider;
if (plus_profiles_provider_) {
plus_profiles_provider_->AddObserver(this);
}
}
void AddressAccessoryControllerImpl::RefreshSuggestions() {
TRACE_EVENT0("passwords",
"AddressAccessoryControllerImpl::RefreshSuggestions");
if (!personal_data_manager_) {
personal_data_manager_ =
autofill::PersonalDataManagerFactory::GetForBrowserContext(
GetWebContents().GetBrowserContext());
personal_data_manager_->AddObserver(this);
}
CHECK(source_observer_);
const bool address_data_available =
personal_data_manager_ && !personal_data_manager_->address_data_manager()
.GetProfilesToSuggest()
.empty();
const bool plus_profiles_data_available =
plus_profiles_provider_ &&
!plus_profiles_provider_->GetAffiliatedPlusProfiles().empty();
source_observer_.Run(this,
IsFillingSourceAvailable(address_data_available ||
plus_profiles_data_available));
}
base::WeakPtr<AddressAccessoryController>
AddressAccessoryControllerImpl::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void AddressAccessoryControllerImpl::OnPersonalDataChanged() {
RefreshSuggestions();
}
void AddressAccessoryControllerImpl::OnAffiliatedPlusProfilesFetched() {
RefreshSuggestions();
}
// static
void AddressAccessoryControllerImpl::CreateForWebContentsForTesting(
content::WebContents* web_contents,
base::WeakPtr<ManualFillingController> mf_controller) {
DCHECK(web_contents) << "Need valid WebContents to attach controller to!";
DCHECK(!FromWebContents(web_contents)) << "Controller already attached!";
DCHECK(mf_controller);
web_contents->SetUserData(UserDataKey(),
base::WrapUnique(new AddressAccessoryControllerImpl(
web_contents, std::move(mf_controller))));
}
AddressAccessoryControllerImpl::AddressAccessoryControllerImpl(
content::WebContents* web_contents)
: AddressAccessoryControllerImpl(web_contents, nullptr) {}
// Additional creation functions in unit tests only:
AddressAccessoryControllerImpl::AddressAccessoryControllerImpl(
content::WebContents* web_contents,
base::WeakPtr<ManualFillingController> mf_controller)
: content::WebContentsUserData<AddressAccessoryControllerImpl>(
*web_contents),
mf_controller_(std::move(mf_controller)),
personal_data_manager_(nullptr),
plus_address_service_(PlusAddressServiceFactory::GetForBrowserContext(
GetWebContents().GetBrowserContext())) {}
std::vector<FooterCommand>
AddressAccessoryControllerImpl::CreateManageAddressesFooter() const {
std::vector<FooterCommand> commands = {FooterCommand(
l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SHEET_ALL_ADDRESSES_LINK),
AccessoryAction::MANAGE_ADDRESSES)};
if (!base::FeatureList::IsEnabled(
plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled)) {
return commands;
}
// Both `ContentAutofillClient and this controller are instances of the
// `WebContentsUserData`. There's no no well-defined destruction order between
// two different `WebContentsUserData` objects. That's why
// `ContentAutofillClient` cannot be stored in a `raw_ptr` member variable
// like `PlusAddressService`.
auto* autofill_client =
autofill::ContentAutofillClient::FromWebContents(&GetWebContents());
if (!autofill_client || !plus_address_service_) {
return commands;
}
// Offer plus address creation if it's supported for the current user session
// and if the user doesn't have any plus addresses created for the current
// domain.
if (plus_address_service_->IsPlusAddressCreationEnabled(
autofill_client->GetLastCommittedPrimaryMainFrameOrigin(),
autofill_client->IsOffTheRecord()) &&
plus_profiles_provider_ &&
plus_profiles_provider_->GetAffiliatedPlusProfiles().empty()) {
commands.emplace_back(FooterCommand(
l10n_util::GetStringUTF16(
IDS_PLUS_ADDRESS_CREATE_NEW_PLUS_ADDRESSES_LINK_ANDROID),
AccessoryAction::CREATE_PLUS_ADDRESS_FROM_ADDRESS_SHEET));
}
// Offer the user to select the plus address manually if plus address filling
// is supported for the last committed origin and the user has at least 1 plus
// address.
if (plus_address_service_->IsPlusAddressFillingEnabled(
autofill_client->GetLastCommittedPrimaryMainFrameOrigin()) &&
!plus_address_service_->GetPlusProfiles().empty()) {
commands.emplace_back(
FooterCommand(l10n_util::GetStringUTF16(
IDS_PLUS_ADDRESS_SELECT_PLUS_ADDRESS_LINK_ANDROID),
AccessoryAction::SELECT_PLUS_ADDRESS_FROM_ADDRESS_SHEET));
}
if (plus_profiles_provider_ &&
!plus_profiles_provider_->GetAffiliatedPlusProfiles().empty()) {
commands.emplace_back(
FooterCommand(l10n_util::GetStringUTF16(
IDS_PLUS_ADDRESS_MANAGE_PLUS_ADDRESSES_LINK_ANDROID),
AccessoryAction::MANAGE_PLUS_ADDRESS_FROM_ADDRESS_SHEET));
}
return commands;
}
void AddressAccessoryControllerImpl::OnPlusAddressCreated(
FieldGlobalId focused_field_id,
const std::string& plus_address) {
FillValueIntoField(focused_field_id, base::UTF8ToUTF16(plus_address));
}
void AddressAccessoryControllerImpl::OnPlusAddressSelected(
FieldGlobalId focused_field_id,
base::optional_ref<const std::string> plus_address) {
if (plus_address) {
FillValueIntoField(focused_field_id,
base::UTF8ToUTF16(plus_address.value()));
}
all_plus_addresses_bottom_sheet_controller_.reset();
}
void AddressAccessoryControllerImpl::FillValueIntoField(
FieldGlobalId focused_field_id,
const std::u16string& value) {
// Since the data we fill is scoped to the profile and not to a frame, we can
// fill the focused frame - we basically behave like a keyboard here.
content::RenderFrameHost* rfh = GetWebContents().GetFocusedFrame();
if (!rfh) {
return;
}
autofill::ContentAutofillDriver* driver =
autofill::ContentAutofillDriver::GetForRenderFrameHost(rfh);
if (!driver) {
return;
}
driver->browser_events().ApplyFieldAction(mojom::FieldActionType::kReplaceAll,
mojom::ActionPersistence::kFill,
focused_field_id, value);
}
base::WeakPtr<ManualFillingController>
AddressAccessoryControllerImpl::GetManualFillingController() {
if (!mf_controller_)
mf_controller_ = ManualFillingController::GetOrCreate(&GetWebContents());
DCHECK(mf_controller_);
return mf_controller_;
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(AddressAccessoryControllerImpl);
} // namespace autofill