// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/auth/views/auth_container_view.h"
#include <memory>
#include <string>
#include "ash/auth/views/auth_input_row_view.h"
#include "ash/auth/views/auth_view_utils.h"
#include "ash/auth/views/fingerprint_view.h"
#include "ash/auth/views/pin_container_view.h"
#include "ash/auth/views/pin_status_view.h"
#include "ash/login/ui/non_accessible_view.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/pill_button.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/enum_set.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
namespace ash {
namespace {
// The Auth container width.
constexpr int kAuthContainerViewWidthDp = 268;
// Distance between the switch button and the above view.
constexpr int kSwitchButtonTopDistanceDp = 28;
// Adapter classes to observe password and pin UI events.
class PinObserverAdapter : public PinContainerView::Observer {
public:
explicit PinObserverAdapter(AuthContainerView* view)
: auth_container_(view) {}
~PinObserverAdapter() override = default;
PinObserverAdapter(const PinObserverAdapter&) = delete;
PinObserverAdapter& operator=(const PinObserverAdapter&) = delete;
// PinContainerView::Observer:
void OnSubmit(const std::u16string& text) override {
auth_container_->PinSubmit(text);
}
void OnEscape() override { auth_container_->Escape(); }
void OnContentsChanged(const std::u16string& text) override {
auth_container_->ContentsChanged();
}
void OnTextVisibleChanged(bool visible) override {
auth_container_->ContentsChanged();
}
private:
raw_ptr<AuthContainerView> auth_container_;
};
class PasswordObserverAdapter : public AuthInputRowView::Observer {
public:
explicit PasswordObserverAdapter(AuthContainerView* view)
: auth_container_(view) {}
~PasswordObserverAdapter() override = default;
PasswordObserverAdapter(const PinObserverAdapter&) = delete;
PasswordObserverAdapter& operator=(const PasswordObserverAdapter&) = delete;
// AuthInputRowView::Observer:
void OnSubmit(const std::u16string& text) override {
auth_container_->PasswordSubmit(text);
}
void OnEscape() override { auth_container_->Escape(); }
void OnContentsChanged(const std::u16string& text) override {
auth_container_->ContentsChanged();
}
void OnTextVisibleChanged(bool visible) override {
auth_container_->ContentsChanged();
}
private:
raw_ptr<AuthContainerView> auth_container_;
};
} // namespace
AuthContainerView::TestApi::TestApi(AuthContainerView* view) : view_(view) {}
AuthContainerView::TestApi::~TestApi() = default;
raw_ptr<PinContainerView> AuthContainerView::TestApi::GetPinContainerView() {
return view_->pin_container_;
}
raw_ptr<AuthInputRowView> AuthContainerView::TestApi::GetPasswordView() {
return view_->password_view_;
}
raw_ptr<views::Button> AuthContainerView::TestApi::GetSwitchButton() {
return view_->switch_button_;
}
raw_ptr<PinStatusView> AuthContainerView::TestApi::GetPinStatusView() {
return view_->pin_status_;
}
raw_ptr<FingerprintView> AuthContainerView::TestApi::GetFingerprintView() {
return view_->fingerprint_view_;
}
AuthInputType AuthContainerView::TestApi::GetCurrentInputType() {
return view_->current_input_type_;
}
raw_ptr<AuthContainerView> AuthContainerView::TestApi::GetView() {
return view_;
}
AuthContainerView::AuthContainerView(AuthFactorSet auth_factors)
: available_auth_factors_(auth_factors) {
CHECK(!auth_factors.empty());
CHECK(auth_factors.Has(AuthInputType::kPassword) ||
auth_factors.Has(AuthInputType::kPin));
if (!auth_factors.Has(AuthInputType::kPassword)) {
current_input_type_ = AuthInputType::kPin;
}
// Initialize layout.
auto layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical);
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
layout_ = SetLayoutManager(std::move(layout));
// Add child views and adjust their visibility.
AddPasswordView();
AddPinView();
AddSwitchButton();
AddPinStatusView();
AddFingerprintView();
}
AuthContainerView::~AuthContainerView() {
pin_container_->RemoveObserver(pin_observer_.get());
pin_container_ = nullptr;
pin_observer_.reset();
password_view_->RemoveObserver(password_observer_.get());
password_view_ = nullptr;
password_observer_.reset();
pin_status_ = nullptr;
fingerprint_view_ = nullptr;
}
void AuthContainerView::AddPasswordView() {
CHECK_EQ(password_view_, nullptr);
password_view_ = AddChildView(std::make_unique<AuthInputRowView>(
AuthInputRowView::AuthType::kPassword));
password_view_->SetVisible(current_input_type_ == AuthInputType::kPassword);
password_observer_ = std::make_unique<PasswordObserverAdapter>(this);
password_view_->AddObserver(password_observer_.get());
}
void AuthContainerView::AddPinView() {
CHECK_EQ(pin_container_, nullptr);
pin_container_ = AddChildView(std::make_unique<PinContainerView>());
pin_container_->SetVisible(current_input_type_ == AuthInputType::kPin);
pin_observer_ = std::make_unique<PinObserverAdapter>(this);
pin_container_->AddObserver(pin_observer_.get());
}
void AuthContainerView::AddSwitchButton() {
// Add separator between the switch and the above view.
switch_button_spacer_ = AddVerticalSpace(this, kSwitchButtonTopDistanceDp);
switch_button_ = AddChildView(
views::Builder<ash::PillButton>()
.SetText(l10n_util::GetStringUTF16(IDS_ASH_LOGIN_SWITCH_TO_PIN))
.SetPillButtonType(ash::PillButton::Type::kDefaultElevatedWithoutIcon)
.SetCallback(
base::BindRepeating(&AuthContainerView::ToggleCurrentAuthType,
weak_ptr_factory_.GetWeakPtr()))
.Build());
switch_button_->SetVisible(HasPassword() && HasPin());
switch_button_spacer_->SetVisible(switch_button_->GetVisible());
}
void AuthContainerView::AddPinStatusView() {
CHECK_EQ(pin_status_, nullptr);
pin_status_ = AddChildView(std::make_unique<PinStatusView>(std::u16string()));
pin_status_->SetVisible(false);
}
void AuthContainerView::AddFingerprintView() {
CHECK_EQ(fingerprint_view_, nullptr);
fingerprint_view_ = AddChildView(std::make_unique<FingerprintView>());
if (available_auth_factors_.Has(AuthInputType::kFingerprint)) {
SetFingerprintState(FingerprintState::AVAILABLE_DEFAULT);
}
}
gfx::Size AuthContainerView::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
int preferred_height = 0;
if (pin_container_->GetVisible()) {
preferred_height +=
pin_container_->GetPreferredSize(available_size).height();
}
if (password_view_->GetVisible()) {
preferred_height +=
password_view_->GetPreferredSize(available_size).height();
}
if (pin_status_->GetVisible()) {
preferred_height += pin_status_->GetPreferredSize(available_size).height();
}
if (fingerprint_view_->GetVisible()) {
preferred_height +=
fingerprint_view_->GetPreferredSize(available_size).height();
}
if (switch_button_->GetVisible()) {
preferred_height +=
switch_button_->GetPreferredSize(available_size).height();
preferred_height +=
switch_button_spacer_->GetPreferredSize(available_size).height();
}
return gfx::Size(kAuthContainerViewWidthDp, preferred_height);
}
void AuthContainerView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->AddState(ax::mojom::State::kInvisible);
}
std::string AuthContainerView::GetObjectName() const {
return "AuthContainerView";
}
void AuthContainerView::RequestFocus() {
if (current_input_type_ == AuthInputType::kPassword) {
password_view_->RequestFocus();
} else if (current_input_type_ == AuthInputType::kPin) {
pin_container_->RequestFocus();
}
}
void AuthContainerView::SetHasPassword(bool has_password) {
if (has_password == HasPassword()) {
return;
}
available_auth_factors_.PutOrRemove(AuthInputType::kPassword, has_password);
UpdateAuthInput();
UpdateSwitchButtonState();
PreferredSizeChanged();
}
bool AuthContainerView::HasPassword() const {
return available_auth_factors_.Has(AuthInputType::kPassword);
}
void AuthContainerView::SetHasPin(bool has_pin) {
if (has_pin == HasPin()) {
return;
}
available_auth_factors_.PutOrRemove(AuthInputType::kPin, has_pin);
CHECK(fingerprint_view_);
fingerprint_view_->SetHasPin(has_pin);
UpdateAuthInput();
UpdateSwitchButtonState();
PreferredSizeChanged();
}
bool AuthContainerView::HasPin() const {
return available_auth_factors_.Has(AuthInputType::kPin);
}
void AuthContainerView::SetPinStatus(const std::u16string& status_str) {
pin_status_->SetText(status_str);
pin_status_->SetVisible(!status_str.empty());
PreferredSizeChanged();
}
void AuthContainerView::SetFingerprintState(FingerprintState state) {
CHECK(fingerprint_view_);
fingerprint_view_->SetState(state);
}
void AuthContainerView::NotifyFingerprintAuthFailure() {
CHECK(fingerprint_view_);
fingerprint_view_->NotifyAuthFailure();
}
void AuthContainerView::SetInputEnabled(bool enabled) {
SetEnabled(enabled);
pin_container_->SetInputEnabled(enabled);
password_view_->SetInputEnabled(enabled);
switch_button_->SetEnabled(enabled);
}
void AuthContainerView::UpdateAuthInput() {
// If necessary change to the available factor
if (current_input_type_ == AuthInputType::kPassword && !HasPassword()) {
current_input_type_ = AuthInputType::kPin;
}
if (current_input_type_ == AuthInputType::kPin && !HasPin()) {
current_input_type_ = AuthInputType::kPassword;
}
// Show the current_input_type_'s view.
if (current_input_type_ == AuthInputType::kPassword &&
!password_view_->GetVisible()) {
pin_container_->SetVisible(false);
password_view_->SetVisible(true);
password_view_->RequestFocus();
} else if (current_input_type_ == AuthInputType::kPin &&
!pin_container_->GetVisible()) {
password_view_->SetVisible(false);
pin_container_->SetVisible(true);
pin_container_->RequestFocus();
}
PreferredSizeChanged();
}
void AuthContainerView::UpdateSwitchButtonState() {
CHECK(HasPassword() || HasPin());
switch_button_->SetVisible(HasPassword() && HasPin());
switch_button_spacer_->SetVisible(switch_button_->GetVisible());
if (HasPassword() && HasPin()) {
switch_button_->SetText(l10n_util::GetStringUTF16(
current_input_type_ == AuthInputType::kPassword
? (IDS_ASH_LOGIN_SWITCH_TO_PIN)
: IDS_ASH_LOGIN_SWITCH_TO_PASSWORD));
}
}
void AuthContainerView::PinSubmit(const std::u16string& pin) const {
for (auto& observer : observers_) {
observer.OnPinSubmit(pin);
}
}
void AuthContainerView::PasswordSubmit(const std::u16string& password) const {
for (auto& observer : observers_) {
observer.OnPasswordSubmit(password);
}
}
void AuthContainerView::Escape() const {
for (auto& observer : observers_) {
observer.OnEscape();
}
}
void AuthContainerView::ContentsChanged() const {
for (auto& observer : observers_) {
observer.OnContentsChanged();
}
}
void AuthContainerView::ToggleCurrentAuthType() {
CHECK(HasPassword() && HasPin());
if (current_input_type_ == AuthInputType::kPassword) {
current_input_type_ = AuthInputType::kPin;
} else {
current_input_type_ = AuthInputType::kPassword;
}
// Clear the input fields.
ResetInputfields();
UpdateSwitchButtonState();
UpdateAuthInput();
for (auto& observer : observers_) {
observer.OnContentsChanged();
}
}
void AuthContainerView::ResetInputfields() {
password_view_->ResetState();
pin_container_->ResetState();
}
void AuthContainerView::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void AuthContainerView::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
BEGIN_METADATA(AuthContainerView)
END_METADATA
} // namespace ash