| // Copyright 2020 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 "ash/in_session_auth/auth_dialog_contents_view.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/login/resources/grit/login_resources.h" |
| #include "ash/login/ui/horizontal_image_sequence_animation_decoder.h" |
| #include "ash/login/ui/login_password_view.h" |
| #include "ash/login/ui/login_pin_input_view.h" |
| #include "ash/login/ui/login_pin_view.h" |
| #include "ash/login/ui/non_accessible_view.h" |
| #include "ash/login/ui/views_utils.h" |
| #include "ash/public/cpp/webauthn_dialog_controller.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/timer/timer.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/bubble/bubble_border.h" |
| #include "ui/views/controls/button/md_text_button.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| |
| namespace ash { |
| namespace { |
| |
| constexpr int kContainerPreferredWidth = 340; |
| |
| constexpr int kBorderTopDp = 36; |
| constexpr int kBorderLeftDp = 24; |
| constexpr int kBorderBottomDp = 20; |
| constexpr int kBorderRightDp = 24; |
| constexpr int kCornerRadius = 12; |
| |
| constexpr int kTitleFontSizeDeltaDp = 4; |
| constexpr int kOriginNameLineHeight = 18; |
| |
| constexpr int kSpacingAfterAvatar = 18; |
| constexpr int kSpacingAfterTitle = 8; |
| constexpr int kSpacingAfterOriginName = 32; |
| constexpr int kSpacingAfterInputField = 16; |
| |
| constexpr int kAvatarSizeDp = 36; |
| constexpr int kFingerprintIconSizeDp = 28; |
| constexpr int kSpacingBetweenPinPadAndFingerprintIcon = 24; |
| constexpr int kSpacingBetweenPasswordAndFingerprintIcon = 24; |
| constexpr int kSpacingBetweenFingerprintIconAndLabelDp = 15; |
| constexpr int kFingerprintViewWidthDp = 204; |
| constexpr int kFingerprintFailedAnimationNumFrames = 45; |
| constexpr base::TimeDelta kResetToDefaultIconDelay = base::Milliseconds(1300); |
| constexpr base::TimeDelta kResetToDefaultMessageDelay = |
| base::Milliseconds(3000); |
| constexpr base::TimeDelta kFingerprintFailedAnimationDuration = |
| base::Milliseconds(700); |
| |
| // 38% opacity. |
| constexpr SkColor kDisabledFingerprintIconColor = |
| SkColorSetA(gfx::kGoogleGrey900, 97); |
| constexpr SkColor kBackgroundColor = SK_ColorWHITE; |
| constexpr SkColor kTextColorSecondary = gfx::kGoogleGrey700; |
| constexpr SkColor kTextColorPrimary = gfx::kGoogleGrey900; |
| constexpr SkColor kErrorColor = gfx::kGoogleRed600; |
| |
| constexpr int kSpacingBeforeButtons = 32; |
| |
| constexpr int kMaxPinAttempts = 5; |
| |
| } // namespace |
| |
| // Consists of fingerprint icon view and a label. |
| class AuthDialogContentsView::FingerprintView : public views::View { |
| public: |
| // Use a subclass that inherit views::Label so that GetAccessibleNodeData |
| // override is respected. |
| class FingerprintLabel : public views::Label { |
| public: |
| // views::View |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override { |
| node_data->role = ax::mojom::Role::kStaticText; |
| node_data->SetName(accessible_name_); |
| } |
| |
| void SetAccessibleName(const std::u16string& name) { |
| accessible_name_ = name; |
| NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, |
| true /*send_native_event*/); |
| } |
| |
| private: |
| std::u16string accessible_name_; |
| }; |
| |
| FingerprintView() { |
| auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical, gfx::Insets(), |
| kSpacingBetweenFingerprintIconAndLabelDp)); |
| layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| |
| icon_ = AddChildView(std::make_unique<AnimatedRoundedImageView>( |
| gfx::Size(kFingerprintIconSizeDp, kFingerprintIconSizeDp), |
| 0 /*corner_radius*/)); |
| |
| label_ = AddChildView(std::make_unique<FingerprintLabel>()); |
| label_->SetSubpixelRenderingEnabled(false); |
| label_->SetAutoColorReadabilityEnabled(false); |
| label_->SetEnabledColor(kTextColorPrimary); |
| label_->SetMultiLine(true); |
| label_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); |
| |
| DisplayCurrentState(); |
| } |
| FingerprintView(const FingerprintView&) = delete; |
| FingerprintView& operator=(const FingerprintView&) = delete; |
| ~FingerprintView() override = default; |
| |
| void SetState(FingerprintState state) { |
| if (state_ == state) |
| return; |
| |
| state_ = state; |
| DisplayCurrentState(); |
| } |
| |
| void SetCanUsePin(bool can_use_pin) { |
| if (can_use_pin_ == can_use_pin) |
| return; |
| |
| can_use_pin_ = can_use_pin; |
| DisplayCurrentState(); |
| } |
| |
| // Notify the user of the fingerprint auth result. Should be called after |
| // SetState. If fingerprint auth failed and retry is allowed, reset to |
| // default state after animation. |
| void NotifyFingerprintAuthResult(bool success) { |
| reset_state_.Stop(); |
| if (state_ == FingerprintState::DISABLED_FROM_ATTEMPTS) { |
| label_->SetText(l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_DISABLED_FROM_ATTEMPTS)); |
| label_->SetAccessibleName(l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_DISABLED_FROM_ATTEMPTS)); |
| } else if (success) { |
| label_->SetText(l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_SUCCESS)); |
| label_->SetAccessibleName(l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_SUCCESS)); |
| } else { |
| label_->SetText(l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_FAILED)); |
| label_->SetAccessibleName(l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_FAILED)); |
| } |
| |
| if (!success) { |
| // This is just to display the "fingerprint auth failure" animation. It |
| // does not necessarily mean |state_| is DISABLED_FROM_ATTEMPTS. |
| SetIcon(FingerprintState::DISABLED_FROM_ATTEMPTS); |
| // base::Unretained is safe because reset_state_ is owned by |this|. |
| reset_state_.Start(FROM_HERE, kResetToDefaultIconDelay, |
| base::BindOnce(&FingerprintView::DisplayCurrentState, |
| base::Unretained(this))); |
| label_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, |
| true /*send_native_event*/); |
| } |
| } |
| |
| // views::View: |
| gfx::Size CalculatePreferredSize() const override { |
| gfx::Size size = views::View::CalculatePreferredSize(); |
| size.set_width(kFingerprintViewWidthDp); |
| return size; |
| } |
| |
| // views::View: |
| void OnGestureEvent(ui::GestureEvent* event) override { |
| if (event->type() != ui::ET_GESTURE_TAP) |
| return; |
| if (state_ == FingerprintState::AVAILABLE_DEFAULT || |
| state_ == FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING) { |
| SetState(FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING); |
| reset_state_.Start( |
| FROM_HERE, kResetToDefaultMessageDelay, |
| base::BindOnce(&FingerprintView::SetState, base::Unretained(this), |
| FingerprintState::AVAILABLE_DEFAULT)); |
| } |
| } |
| |
| private: |
| void DisplayCurrentState() { |
| SetVisible(state_ != FingerprintState::UNAVAILABLE); |
| SetIcon(state_); |
| if (state_ != FingerprintState::UNAVAILABLE) { |
| std::u16string fingerprint_text = |
| l10n_util::GetStringUTF16(GetTextIdFromState()); |
| label_->SetText(fingerprint_text); |
| label_->SetAccessibleName( |
| state_ == FingerprintState::DISABLED_FROM_ATTEMPTS |
| ? l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_ACCESSIBLE_DISABLED_FROM_ATTEMPTS) |
| : fingerprint_text); |
| } |
| } |
| |
| void SetIcon(FingerprintState state) { |
| const SkColor color = |
| (state == FingerprintState::AVAILABLE_DEFAULT || |
| state == FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING |
| ? kTextColorPrimary |
| : kDisabledFingerprintIconColor); |
| switch (state) { |
| case FingerprintState::UNAVAILABLE: |
| case FingerprintState::AVAILABLE_DEFAULT: |
| case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING: |
| case FingerprintState::DISABLED_FROM_TIMEOUT: |
| icon_->SetImage(gfx::CreateVectorIcon(kLockScreenFingerprintIcon, |
| kFingerprintIconSizeDp, color)); |
| break; |
| case FingerprintState::DISABLED_FROM_ATTEMPTS: |
| icon_->SetAnimationDecoder( |
| std::make_unique<HorizontalImageSequenceAnimationDecoder>( |
| *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| IDR_LOGIN_FINGERPRINT_UNLOCK_SPINNER), |
| kFingerprintFailedAnimationDuration, |
| kFingerprintFailedAnimationNumFrames), |
| AnimatedRoundedImageView::Playback::kSingle); |
| break; |
| } |
| } |
| |
| int GetTextIdFromState() const { |
| switch (state_) { |
| case FingerprintState::AVAILABLE_DEFAULT: |
| return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_AVAILABLE; |
| case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING: |
| return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_TOUCH_SENSOR; |
| case FingerprintState::DISABLED_FROM_ATTEMPTS: |
| return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_DISABLED_FROM_ATTEMPTS; |
| case FingerprintState::DISABLED_FROM_TIMEOUT: |
| if (can_use_pin_) |
| return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_PIN_OR_PASSWORD_REQUIRED; |
| return IDS_ASH_IN_SESSION_AUTH_FINGERPRINT_PASSWORD_REQUIRED; |
| case FingerprintState::UNAVAILABLE: |
| NOTREACHED(); |
| return 0; |
| } |
| } |
| |
| FingerprintLabel* label_ = nullptr; |
| AnimatedRoundedImageView* icon_ = nullptr; |
| FingerprintState state_ = FingerprintState::AVAILABLE_DEFAULT; |
| bool can_use_pin_ = false; |
| base::OneShotTimer reset_state_; |
| }; |
| |
| class AuthDialogContentsView::TitleLabel : public views::Label { |
| public: |
| TitleLabel() { |
| SetSubpixelRenderingEnabled(false); |
| SetAutoColorReadabilityEnabled(false); |
| SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); |
| |
| const gfx::FontList& base_font_list = views::Label::GetDefaultFontList(); |
| |
| SetFontList(base_font_list.Derive(kTitleFontSizeDeltaDp, |
| gfx::Font::FontStyle::NORMAL, |
| gfx::Font::Weight::MEDIUM)); |
| SetMaximumWidthSingleLine(kContainerPreferredWidth); |
| SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL); |
| |
| SetPreferredSize(gfx::Size(kContainerPreferredWidth, |
| GetHeightForWidth(kContainerPreferredWidth))); |
| SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| } |
| |
| bool IsShowingError() const { return is_showing_error_; } |
| |
| void ShowTitle() { |
| std::u16string title = |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_TITLE); |
| SetText(title); |
| SetEnabledColor(kTextColorPrimary); |
| is_showing_error_ = false; |
| SetAccessibleName(title); |
| } |
| |
| void ShowError(const std::u16string& error_text) { |
| SetText(error_text); |
| SetEnabledColor(kErrorColor); |
| is_showing_error_ = true; |
| SetAccessibleName(error_text); |
| NotifyAccessibilityEvent(ax::mojom::Event::kAlert, |
| true /*send_native_event*/); |
| } |
| |
| // views::View |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override { |
| node_data->role = ax::mojom::Role::kStaticText; |
| node_data->SetName(accessible_name_); |
| } |
| |
| private: |
| void SetAccessibleName(const std::u16string& name) { |
| accessible_name_ = name; |
| NotifyAccessibilityEvent(ax::mojom::Event::kTextChanged, |
| true /*send_native_event*/); |
| } |
| |
| bool is_showing_error_ = false; |
| std::u16string accessible_name_; |
| }; |
| |
| AuthDialogContentsView::AuthDialogContentsView( |
| uint32_t auth_methods, |
| const std::string& origin_name, |
| const AuthMethodsMetadata& auth_metadata, |
| const UserAvatar& avatar) |
| : auth_methods_(auth_methods), |
| origin_name_(origin_name), |
| auth_metadata_(auth_metadata) { |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| auto border = std::make_unique<views::BubbleBorder>( |
| views::BubbleBorder::FLOAT, views::BubbleBorder::STANDARD_SHADOW); |
| border->SetColor(kBackgroundColor); |
| border->SetCornerRadius(kCornerRadius); |
| SetBackground(std::make_unique<views::BubbleBackground>(border.get())); |
| SetBorder(std::move(border)); |
| |
| container_ = AddChildView(std::make_unique<NonAccessibleView>()); |
| container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR( |
| kBorderTopDp, kBorderLeftDp, kBorderBottomDp, kBorderRightDp))); |
| |
| main_layout_ = |
| container_->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)); |
| main_layout_->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kStart); |
| main_layout_->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| |
| AddAvatarView(avatar); |
| AddVerticalSpacing(kSpacingAfterAvatar); |
| AddTitleView(); |
| AddVerticalSpacing(kSpacingAfterTitle); |
| AddOriginNameView(); |
| AddVerticalSpacing(kSpacingAfterOriginName); |
| if (auth_methods_ & kAuthPin) { |
| if (LoginPinInputView::IsAutosubmitSupported( |
| auth_metadata_.autosubmit_pin_length)) { |
| pin_autosubmit_on_ = true; |
| AddPinDigitInputView(); |
| } else { |
| pin_autosubmit_on_ = false; |
| AddPinTextInputView(); |
| } |
| AddVerticalSpacing(kSpacingAfterInputField); |
| // PIN pad is always visible regardless of PIN autosubmit status. |
| AddPinPadView(); |
| } else if (auth_methods & kAuthPassword) { |
| AddPasswordView(); |
| } |
| |
| if (auth_methods_ & kAuthFingerprint) { |
| if (pin_pad_view_) { |
| AddVerticalSpacing(kSpacingBetweenPinPadAndFingerprintIcon); |
| } else if (password_view_) { |
| AddVerticalSpacing(kSpacingBetweenPasswordAndFingerprintIcon); |
| } |
| |
| fingerprint_view_ = |
| container_->AddChildView(std::make_unique<FingerprintView>()); |
| fingerprint_view_->SetCanUsePin(auth_methods_ & kAuthPin); |
| } |
| |
| AddVerticalSpacing(kSpacingBeforeButtons); |
| AddActionButtonsView(); |
| } |
| |
| AuthDialogContentsView::~AuthDialogContentsView() = default; |
| |
| void AuthDialogContentsView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| views::View::GetAccessibleNodeData(node_data); |
| node_data->role = ax::mojom::Role::kDialog; |
| node_data->SetName( |
| l10n_util::GetStringFUTF16(IDS_ASH_IN_SESSION_AUTH_ACCESSIBLE_TITLE, |
| base::UTF8ToUTF16(origin_name_))); |
| } |
| |
| void AuthDialogContentsView::RequestFocus() { |
| if (auth_methods_ == kAuthFingerprint) { |
| // There's no PIN input field, so let the focus be on the cancel button |
| // (instead of the help button) because it is more often used. |
| cancel_button_->RequestFocus(); |
| return; |
| } |
| |
| // For other cases, the base method correctly sets focus to the input field. |
| views::View::RequestFocus(); |
| } |
| |
| void AuthDialogContentsView::AddedToWidget() { |
| if (auth_methods_ & kAuthFingerprint) { |
| // Inject a callback from the contents view so that we can show retry |
| // prompt. |
| WebAuthNDialogController::Get()->AuthenticateUserWithFingerprint( |
| base::BindOnce(&AuthDialogContentsView::OnFingerprintAuthComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void AuthDialogContentsView::AddAvatarView(const UserAvatar& avatar) { |
| avatar_view_ = |
| container_->AddChildView(std::make_unique<AnimatedRoundedImageView>( |
| gfx::Size(kAvatarSizeDp, kAvatarSizeDp), |
| kAvatarSizeDp / 2 /*corner_radius*/)); |
| avatar_view_->SetImage(avatar.image); |
| } |
| |
| void AuthDialogContentsView::AddTitleView() { |
| title_ = container_->AddChildView(std::make_unique<TitleLabel>()); |
| title_->ShowTitle(); |
| } |
| |
| void AuthDialogContentsView::AddOriginNameView() { |
| origin_name_view_ = |
| container_->AddChildView(std::make_unique<views::Label>()); |
| origin_name_view_->SetEnabledColor(kTextColorSecondary); |
| origin_name_view_->SetSubpixelRenderingEnabled(false); |
| origin_name_view_->SetAutoColorReadabilityEnabled(false); |
| origin_name_view_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); |
| |
| origin_name_view_->SetText( |
| l10n_util::GetStringFUTF16(IDS_ASH_IN_SESSION_AUTH_ORIGIN_NAME_PROMPT, |
| base::UTF8ToUTF16(origin_name_))); |
| origin_name_view_->SetMultiLine(true); |
| origin_name_view_->SetMaximumWidth(kContainerPreferredWidth); |
| origin_name_view_->SetLineHeight(kOriginNameLineHeight); |
| |
| origin_name_view_->SetPreferredSize(gfx::Size( |
| kContainerPreferredWidth, |
| origin_name_view_->GetHeightForWidth(kContainerPreferredWidth))); |
| origin_name_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| } |
| |
| void AuthDialogContentsView::AddPinTextInputView() { |
| pin_text_input_view_ = |
| container_->AddChildView(std::make_unique<LoginPasswordView>(palette_)); |
| |
| pin_text_input_view_->SetPaintToLayer(); |
| pin_text_input_view_->layer()->SetFillsBoundsOpaquely(false); |
| pin_text_input_view_->SetDisplayPasswordButtonVisible(true); |
| pin_text_input_view_->SetEnabledOnEmptyPassword(false); |
| pin_text_input_view_->SetFocusEnabledForTextfield(true); |
| |
| pin_text_input_view_->SetPlaceholderText( |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_PLACEHOLDER)); |
| } |
| |
| void AuthDialogContentsView::AddPasswordView() { |
| password_view_ = |
| container_->AddChildView(std::make_unique<LoginPasswordView>(palette_)); |
| |
| password_view_->SetPaintToLayer(); |
| password_view_->layer()->SetFillsBoundsOpaquely(false); |
| password_view_->SetDisplayPasswordButtonVisible(true); |
| password_view_->SetEnabledOnEmptyPassword(false); |
| password_view_->SetFocusEnabledForTextfield(true); |
| |
| password_view_->SetPlaceholderText( |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PASSWORD_PLACEHOLDER)); |
| password_view_->Init( |
| base::BindRepeating(&AuthDialogContentsView::OnAuthSubmit, |
| base::Unretained(this), |
| /*authenticated_by_pin=*/false), |
| base::BindRepeating(&AuthDialogContentsView::OnInputTextChanged, |
| base::Unretained(this)), |
| base::DoNothing(), views::Button::PressedCallback()); |
| } |
| |
| void AuthDialogContentsView::AddPinPadView() { |
| DCHECK(auth_methods_ & kAuthPin); |
| if (pin_autosubmit_on_) { |
| pin_pad_view_ = container_->AddChildView(std::make_unique<LoginPinView>( |
| LoginPinView::Style::kAlphanumeric, palette_, |
| base::BindRepeating(&AuthDialogContentsView::OnInsertDigitFromPinPad, |
| base::Unretained(this)), |
| base::BindRepeating(&AuthDialogContentsView::OnBackspaceFromPinPad, |
| base::Unretained(this)))); |
| pin_digit_input_view_->Init( |
| base::BindRepeating(&AuthDialogContentsView::OnAuthSubmit, |
| base::Unretained(this), |
| /*authenticated_by_pin=*/true), |
| base::BindRepeating(&AuthDialogContentsView::OnInputTextChanged, |
| base::Unretained(this))); |
| } else { |
| pin_pad_view_ = container_->AddChildView(std::make_unique<LoginPinView>( |
| LoginPinView::Style::kAlphanumeric, palette_, |
| base::BindRepeating(&AuthDialogContentsView::OnInsertDigitFromPinPad, |
| base::Unretained(this)), |
| base::BindRepeating(&AuthDialogContentsView::OnBackspaceFromPinPad, |
| base::Unretained(this)), |
| base::BindRepeating(&LoginPasswordView::SubmitPassword, |
| base::Unretained(pin_text_input_view_)))); |
| pin_text_input_view_->Init( |
| base::BindRepeating(&AuthDialogContentsView::OnAuthSubmit, |
| base::Unretained(this), |
| /*authenticated_by_pin=*/true), |
| base::BindRepeating(&AuthDialogContentsView::OnInputTextChanged, |
| base::Unretained(this)), |
| base::DoNothing(), views::Button::PressedCallback()); |
| } |
| pin_pad_view_->SetVisible(true); |
| } |
| |
| void AuthDialogContentsView::AddPinDigitInputView() { |
| pin_digit_input_view_ = |
| container_->AddChildView(std::make_unique<LoginPinInputView>(palette_)); |
| pin_digit_input_view_->UpdateLength(auth_metadata_.autosubmit_pin_length); |
| pin_digit_input_view_->SetVisible(true); |
| } |
| |
| void AuthDialogContentsView::AddVerticalSpacing(int height) { |
| auto* spacing = |
| container_->AddChildView(std::make_unique<NonAccessibleView>()); |
| spacing->SetPreferredSize(gfx::Size(kContainerPreferredWidth, height)); |
| } |
| |
| void AuthDialogContentsView::AddActionButtonsView() { |
| action_view_container_ = |
| container_->AddChildView(std::make_unique<NonAccessibleView>()); |
| auto* buttons_layout = action_view_container_->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal)); |
| buttons_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kStart); |
| |
| help_button_ = |
| action_view_container_->AddChildView(std::make_unique<views::LabelButton>( |
| base::BindRepeating(&AuthDialogContentsView::OnNeedHelpButtonPressed, |
| base::Unretained(this)), |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_HELP), |
| views::style::CONTEXT_BUTTON)); |
| help_button_->SetEnabledTextColors(kTextColorPrimary); |
| |
| auto* spacing = action_view_container_->AddChildView( |
| std::make_unique<NonAccessibleView>()); |
| buttons_layout->SetFlexForView(spacing, 1); |
| |
| cancel_button_ = action_view_container_->AddChildView( |
| std::make_unique<views::MdTextButton>( |
| base::BindRepeating(&AuthDialogContentsView::OnCancelButtonPressed, |
| base::Unretained(this)), |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_CANCEL))); |
| |
| action_view_container_->SetPreferredSize( |
| gfx::Size(kContainerPreferredWidth, cancel_button_->height())); |
| } |
| |
| void AuthDialogContentsView::OnInsertDigitFromPinPad(int digit) { |
| // Ignore anything if reached max attempts. |
| if (pin_attempts_ >= kMaxPinAttempts) |
| return; |
| |
| if (title_->IsShowingError()) |
| title_->ShowTitle(); |
| |
| if (pin_autosubmit_on_) { |
| pin_digit_input_view_->InsertDigit(digit); |
| } else { |
| pin_text_input_view_->InsertNumber(digit); |
| } |
| } |
| |
| void AuthDialogContentsView::OnBackspaceFromPinPad() { |
| // Ignore anything if reached max attempts. |
| if (pin_attempts_ >= kMaxPinAttempts) |
| return; |
| |
| if (title_->IsShowingError()) |
| title_->ShowTitle(); |
| |
| if (pin_autosubmit_on_) { |
| pin_digit_input_view_->Backspace(); |
| } else { |
| pin_text_input_view_->Backspace(); |
| } |
| } |
| |
| void AuthDialogContentsView::OnInputTextChanged(bool is_empty) { |
| // If the user is interacting with the input field, restore the title (clear |
| // error message). |
| // |
| // If |is_empty| is true, this call may come from resetting |
| // |pin_text_input_view_| or |pin_digit_input_view_|, when the error message |
| // hasn't been shown and read yet. In this case we don't restore the title. |
| if (title_->IsShowingError() && !is_empty) |
| title_->ShowTitle(); |
| |
| if (pin_pad_view_) { |
| pin_pad_view_->OnPasswordTextChanged(is_empty); |
| } |
| } |
| |
| void AuthDialogContentsView::OnAuthSubmit(bool authenticated_by_pin, |
| const std::u16string& password) { |
| if (authenticated_by_pin) { |
| if (pin_autosubmit_on_) { |
| pin_digit_input_view_->SetReadOnly(true); |
| } else { |
| pin_text_input_view_->SetReadOnly(true); |
| } |
| } else { |
| password_view_->SetReadOnly(true); |
| } |
| WebAuthNDialogController::Get()->AuthenticateUserWithPasswordOrPin( |
| base::UTF16ToUTF8(password), authenticated_by_pin, |
| base::BindOnce(&AuthDialogContentsView::OnPasswordOrPinAuthComplete, |
| weak_factory_.GetWeakPtr(), authenticated_by_pin)); |
| } |
| |
| void AuthDialogContentsView::OnPasswordOrPinAuthComplete( |
| bool authenticated_by_pin, |
| absl::optional<bool> success) { |
| // On success, do nothing, and the dialog will dismiss. |
| if (success.has_value() && success.value()) |
| return; |
| |
| std::u16string error_text; |
| if (authenticated_by_pin) { |
| pin_attempts_++; |
| error_text = |
| pin_attempts_ >= kMaxPinAttempts |
| ? l10n_util::GetStringUTF16( |
| IDS_ASH_IN_SESSION_AUTH_PIN_TOO_MANY_ATTEMPTS) |
| : l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PIN_INCORRECT); |
| } else { |
| error_text = |
| l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_PASSWORD_INCORRECT); |
| } |
| title_->ShowError(error_text); |
| |
| if (!authenticated_by_pin) { |
| password_view_->Reset(); |
| password_view_->SetReadOnly(false); |
| } else if (pin_attempts_ < kMaxPinAttempts) { |
| if (pin_autosubmit_on_) { |
| pin_digit_input_view_->Reset(); |
| pin_digit_input_view_->SetReadOnly(false); |
| } else { |
| pin_text_input_view_->Reset(); |
| pin_text_input_view_->SetReadOnly(false); |
| } |
| } |
| } |
| |
| void AuthDialogContentsView::OnFingerprintAuthComplete( |
| bool success, |
| FingerprintState fingerprint_state) { |
| fingerprint_view_->SetState(fingerprint_state); |
| // Prepare for the next fingerprint scan. |
| if (!success && fingerprint_state == FingerprintState::AVAILABLE_DEFAULT) { |
| WebAuthNDialogController::Get()->AuthenticateUserWithFingerprint( |
| base::BindOnce(&AuthDialogContentsView::OnFingerprintAuthComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| fingerprint_view_->NotifyFingerprintAuthResult(success); |
| } |
| |
| void AuthDialogContentsView::OnCancelButtonPressed(const ui::Event& event) { |
| WebAuthNDialogController::Get()->Cancel(); |
| } |
| |
| void AuthDialogContentsView::OnNeedHelpButtonPressed(const ui::Event& event) { |
| WebAuthNDialogController::Get()->OpenInSessionAuthHelpPage(); |
| } |
| |
| } // namespace ash |