// Copyright 2023 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_textfield.h"
#include <string>
#include "ash/auth/views/auth_textfield_timer.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/system_textfield.h"
#include "ash/style/system_textfield_controller.h"
#include "ash/style/typography.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/observer_list_types.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/font_list.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/view.h"
namespace ash {
namespace {
// Spacing between glyphs, used when password is in hidden state.
constexpr int kPasswordGlyphSpacing = 6;
// Size (width/height) of the different icons belonging to the password row
// (the display password icon and the caps lock icon).
constexpr int kIconSizeDp = 20;
// The max width of the AuthTextfield.
constexpr int kAuthTextfieldMaxWidthDp = 216;
// Font type for text.
constexpr TypographyToken kTextFont = TypographyToken::kCrosBody2;
// Font type for hidden text.
constexpr TypographyToken kHiddenTextFont = TypographyToken::kCrosDisplay5;
}
AuthTextfield::AuthTextfield(AuthType auth_type)
: SystemTextfield(Type::kMedium),
SystemTextfieldController(this),
auth_type_(auth_type) {
SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
SetFocusBehavior(FocusBehavior::ALWAYS);
set_placeholder_font_list(
ash::TypographyProvider::Get()->ResolveTypographyToken(kTextFont));
SetFontList(
ash::TypographyProvider::Get()->ResolveTypographyToken(kHiddenTextFont));
SetObscuredGlyphSpacing(kPasswordGlyphSpacing);
// Remove focus ring to remain consistent with other implementations of
// login input fields.
views::FocusRing::Remove(this);
// Don't show background.
SetShowBackground(false);
SetBackgroundEnabled(false);
SetBackground(nullptr);
// Remove the border.
SetBorder(nullptr);
// Set the text colors.
SetTextColorId(cros_tokens::kCrosSysOnSurface);
SetPlaceholderTextColorId(cros_tokens::kCrosSysDisabled);
// Set Accessible name
if (auth_type_ == AuthType::kPassword) {
GetViewAccessibility().SetName(l10n_util::GetStringUTF16(
IDS_ASH_AUTH_TEXTFIELD_PASSWORD_ACCESSIBLE_NAME));
SetPlaceholderText(l10n_util::GetStringUTF16(
IDS_ASH_IN_SESSION_AUTH_PASSWORD_PLACEHOLDER));
} else {
CHECK_EQ(auth_type_, AuthType::kPin);
GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_ASH_AUTH_TEXTFIELD_PIN_ACCESSIBLE_NAME));
SetPlaceholderText(
l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_PLACEHOLDER));
}
}
AuthTextfield::~AuthTextfield() = default;
void AuthTextfield::AboutToRequestFocusFromTabTraversal(bool reverse) {
if (!GetText().empty()) {
SelectAll(/*reversed=*/false);
}
}
void AuthTextfield::OnBlur() {
SystemTextfield::OnBlur();
for (auto& observer : observers_) {
observer.OnTextfieldBlur();
}
}
void AuthTextfield::OnFocus() {
SystemTextfield::OnFocus();
for (auto& observer : observers_) {
observer.OnTextfieldFocus();
}
}
ui::TextInputMode AuthTextfield::GetTextInputMode() const {
if (auth_type_ == AuthType::kPin) {
return ui::TextInputMode::TEXT_INPUT_MODE_NUMERIC;
}
return ui::TextInputMode::TEXT_INPUT_MODE_DEFAULT;
}
bool AuthTextfield::ShouldDoLearning() {
return false;
}
bool AuthTextfield::HandleKeyEvent(views::Textfield* sender,
const ui::KeyEvent& key_event) {
CHECK_EQ(sender, this);
if (GetReadOnly()) {
return false;
}
if (key_event.type() != ui::EventType::kKeyPressed) {
return false;
}
const ui::KeyboardCode key_code = key_event.key_code();
if (key_code == ui::VKEY_RETURN) {
if (!GetText().empty()) {
for (auto& observer : observers_) {
observer.OnSubmit();
}
}
return true;
}
if (key_code == ui::VKEY_ESCAPE) {
for (auto& observer : observers_) {
observer.OnEscape();
}
return true;
}
if (auth_type_ == AuthType::kPassword) {
return SystemTextfieldController::HandleKeyEvent(sender, key_event);
}
CHECK_EQ(auth_type_, AuthType::kPin);
// Default handling for events with Alt modifier like spoken feedback.
if (key_event.IsAltDown()) {
return false;
}
// Default handling for events with Control modifier like sign out.
if (key_event.IsControlDown()) {
return false;
}
// All key pressed events not handled below are ignored.
if (key_code == ui::VKEY_TAB || key_code == ui::VKEY_BACKTAB) {
// Allow using tab for keyboard navigation.
return false;
} else if (key_code == ui::VKEY_PROCESSKEY) {
// Default handling for keyboard events that are not generated by physical
// key press. This can happen, for example, when virtual keyboard button
// is pressed.
return false;
} else if (key_code >= ui::VKEY_0 && key_code <= ui::VKEY_9) {
InsertDigit(key_code - ui::VKEY_0);
} else if (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_NUMPAD9) {
InsertDigit(key_code - ui::VKEY_NUMPAD0);
} else if (key_code == ui::VKEY_BACK) {
return false;
} else if (key_code == ui::VKEY_DELETE) {
return false;
} else if (key_code == ui::VKEY_LEFT) {
return false;
} else if (key_code == ui::VKEY_RIGHT) {
return false;
}
return true;
}
void AuthTextfield::ContentsChanged(Textfield* sender,
const std::u16string& new_contents) {
for (auto& observer : observers_) {
observer.OnContentsChanged(new_contents);
}
}
gfx::Size AuthTextfield::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
return gfx::Size(kAuthTextfieldMaxWidthDp, kIconSizeDp);
}
void AuthTextfield::Reset() {
if (!GetText().empty()) {
SetText(std::u16string());
for (auto& observer : observers_) {
observer.OnContentsChanged(GetText());
}
}
HideText();
ClearEditHistory();
}
void AuthTextfield::InsertDigit(int digit) {
CHECK(auth_type_ == AuthType::kPin);
CHECK(0 <= digit && digit <= 9);
if (GetReadOnly()) {
return;
}
if (!HasFocus()) {
// RequestFocus on textfield to activate cursor.
RequestFocus();
}
InsertOrReplaceText(base::NumberToString16(digit));
}
void AuthTextfield::Backspace() {
// Instead of just adjusting textfield_ text directly, fire a backspace key
// event as this handles the various edge cases (ie, selected text).
// views::Textfield::OnKeyPressed is private, so we call it via views::View.
if (GetReadOnly()) {
return;
}
if (!HasFocus()) {
// RequestFocus on textfield to activate cursor.
RequestFocus();
}
auto* view = static_cast<views::View*>(this);
view->OnKeyPressed(ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_BACK,
ui::DomCode::BACKSPACE, ui::EF_NONE));
view->OnKeyPressed(ui::KeyEvent(ui::EventType::kKeyReleased, ui::VKEY_BACK,
ui::DomCode::BACKSPACE, ui::EF_NONE));
}
void AuthTextfield::SetTextVisible(bool visible) {
if (visible) {
ShowText();
} else {
HideText();
}
}
void AuthTextfield::ShowText() {
if (IsTextVisible()) {
return;
}
SetFontList(
ash::TypographyProvider::Get()->ResolveTypographyToken(kTextFont));
switch (auth_type_) {
case AuthType::kPassword:
SetTextInputType(ui::TEXT_INPUT_TYPE_NULL);
break;
case AuthType::kPin:
SetTextInputType(ui::TEXT_INPUT_TYPE_NUMBER);
break;
}
for (auto& observer : observers_) {
observer.OnTextVisibleChanged(true);
}
}
void AuthTextfield::HideText() {
if (!IsTextVisible()) {
return;
}
SetFontList(
ash::TypographyProvider::Get()->ResolveTypographyToken(kHiddenTextFont));
SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
for (auto& observer : observers_) {
observer.OnTextVisibleChanged(false);
}
}
bool AuthTextfield::IsTextVisible() const {
return GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD;
}
void AuthTextfield::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void AuthTextfield::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void AuthTextfield::ApplyTimerLogic() {
CHECK_EQ(timer_logic_.get(), nullptr);
timer_logic_ = std::make_unique<AuthTextfieldTimer>(this);
}
void AuthTextfield::ResetTimerLogic() {
CHECK(timer_logic_.get());
timer_logic_.reset();
}
BEGIN_METADATA(AuthTextfield)
END_METADATA
} // namespace ash