| // 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 "ash/login/ui/parent_access_view.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/login/login_screen_controller.h" |
| #include "ash/login/ui/arrow_button_view.h" |
| #include "ash/login/ui/login_button.h" |
| #include "ash/login/ui/login_pin_view.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/wallpaper/wallpaper_controller.h" |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/optional.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_analysis.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/font.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/controls/textfield/textfield_controller.h" |
| #include "ui/views/layout/box_layout.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr const char kParentAccessViewClassName[] = "ParentAccessView"; |
| |
| // Identifier of parent access input views group used for focus traversal. |
| constexpr int kParentAccessInputGroup = 1; |
| |
| // Number of digits displayed in access code input. |
| constexpr int kParentAccessCodePinLength = 6; |
| |
| constexpr int kParentAccessViewWidthDp = 340; |
| constexpr int kParentAccessViewHeightDp = 340; |
| constexpr int kParentAccessViewTabletModeHeightDp = 580; |
| constexpr int kParentAccessViewRoundedCornerRadiusDp = 8; |
| constexpr int kParentAccessViewVerticalInsetDp = 16; |
| constexpr int kParentAccessViewHorizontalInsetDp = 26; |
| |
| constexpr int kLockIconSizeDp = 24; |
| |
| constexpr int kIconToTitleDistanceDp = 28; |
| constexpr int kTitleToDescriptionDistanceDp = 14; |
| constexpr int kDescriptionToAccessCodeDistanceDp = 28; |
| constexpr int kAccessCodeToPinKeyboardDistanceDp = 5; |
| constexpr int kPinKeyboardToFooterDistanceDp = 57; |
| constexpr int kPinKeyboardToFooterTabletModeDistanceDp = 17; |
| constexpr int kSubmitButtonBottomMarginDp = 8; |
| |
| constexpr int kTitleFontSizeDeltaDp = 3; |
| constexpr int kDescriptionFontSizeDeltaDp = -1; |
| constexpr int kDescriptionTextLineHeightDp = 16; |
| |
| constexpr int kAccessCodeInputFieldWidthDp = 24; |
| constexpr int kAccessCodeInputFieldHeightDp = 32; |
| constexpr int kAccessCodeInputFieldUnderlineThicknessDp = 1; |
| constexpr int kAccessCodeBetweenInputFieldsGapDp = 4; |
| |
| constexpr int kArrowButtonSizeDp = 40; |
| constexpr int kArrowSizeDp = 20; |
| |
| constexpr SkColor kTextColor = SK_ColorWHITE; |
| constexpr SkColor kErrorColor = SkColorSetARGB(0xFF, 0xF2, 0x8B, 0x82); |
| constexpr SkColor kArrowButtonColor = SkColorSetARGB(0x57, 0xFF, 0xFF, 0xFF); |
| |
| bool IsTabletMode() { |
| return Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled(); |
| } |
| |
| gfx::Size GetPinKeyboardToFooterSpacerSize() { |
| return gfx::Size(0, IsTabletMode() ? kPinKeyboardToFooterTabletModeDistanceDp |
| : kPinKeyboardToFooterDistanceDp); |
| } |
| |
| gfx::Size GetParentAccessViewSize() { |
| return gfx::Size(kParentAccessViewWidthDp, |
| IsTabletMode() ? kParentAccessViewTabletModeHeightDp |
| : kParentAccessViewHeightDp); |
| } |
| |
| // Accessible input field. Customizes field description and focus behavior. |
| class AccessibleInputField : public views::Textfield { |
| public: |
| AccessibleInputField() = default; |
| ~AccessibleInputField() override = default; |
| |
| void set_accessible_description(const base::string16& description) { |
| accessible_description_ = description; |
| } |
| |
| // views::View: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override { |
| node_data->role = ax::mojom::Role::kLabelText; |
| node_data->SetDescription(accessible_description_); |
| node_data->SetValue(text()); |
| } |
| |
| bool IsGroupFocusTraversable() const override { return false; } |
| |
| View* GetSelectedViewForGroup(int group) override { |
| return parent() ? parent()->GetSelectedViewForGroup(group) : nullptr; |
| } |
| |
| private: |
| base::string16 accessible_description_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AccessibleInputField); |
| }; |
| |
| } // namespace |
| |
| // Digital access code input view for variable length of input codes. |
| // Displays a separate underscored field for every input code digit. |
| class ParentAccessView::AccessCodeInput : public views::View, |
| public views::TextfieldController { |
| public: |
| using OnInputChange = base::RepeatingCallback<void(bool complete)>; |
| using OnEnter = base::RepeatingClosure; |
| |
| // Builds the view for an access code that consists out of |length| digits. |
| // |on_input_change| will be called upon access code digit insertion, deletion |
| // or change. True will be passed if the current code is complete (all digits |
| // have input values) and false otherwise. |on_enter| will be called when code |
| // is complete and user presses enter to submit it for validation. |
| AccessCodeInput(int length, OnInputChange on_input_change, OnEnter on_enter) |
| : on_input_change_(std::move(on_input_change)), |
| on_enter_(std::move(on_enter)) { |
| DCHECK_LT(0, length); |
| DCHECK(on_input_change_); |
| |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kHorizontal, gfx::Insets(), |
| kAccessCodeBetweenInputFieldsGapDp)); |
| SetGroup(kParentAccessInputGroup); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| |
| for (int i = 0; i < length; ++i) { |
| auto* field = new AccessibleInputField(); |
| field->set_controller(this); |
| field->SetPreferredSize(gfx::Size(kAccessCodeInputFieldWidthDp, |
| kAccessCodeInputFieldHeightDp)); |
| field->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_CENTER); |
| field->SetBackgroundColor(SK_ColorTRANSPARENT); |
| field->SetTextInputType(ui::TEXT_INPUT_TYPE_NUMBER); |
| field->SetTextColor(kTextColor); |
| field->SetFontList(views::Textfield::GetDefaultFontList().Derive( |
| kDescriptionFontSizeDeltaDp, gfx::Font::FontStyle::NORMAL, |
| gfx::Font::Weight::NORMAL)); |
| field->SetBorder(views::CreateSolidSidedBorder( |
| 0, 0, kAccessCodeInputFieldUnderlineThicknessDp, 0, kTextColor)); |
| field->SetGroup(kParentAccessInputGroup); |
| if (i < length - 1) { |
| field->set_accessible_description(l10n_util::GetStringUTF16( |
| IDS_ASH_LOGIN_PARENT_ACCESS_NEXT_NUMBER_PROMPT)); |
| } |
| input_fields_.push_back(field); |
| AddChildView(field); |
| } |
| } |
| |
| ~AccessCodeInput() override = default; |
| |
| // Inserts |value| into the |active_field_| and moves focus to the next field |
| // if it exists. |
| void InsertDigit(int value) { |
| DCHECK_LE(0, value); |
| DCHECK_GE(9, value); |
| |
| ActiveField()->SetText(base::NumberToString16(value)); |
| FocusNextField(); |
| |
| on_input_change_.Run(GetCode().has_value()); |
| } |
| |
| // Clears input from the |active_field_|. If |active_field| is empty moves |
| // focus to the previous field (if exists) and clears input there. |
| void Backspace() { |
| if (ActiveInput().empty()) { |
| FocusPreviousField(); |
| } |
| |
| ActiveField()->SetText(base::string16()); |
| on_input_change_.Run(false); |
| } |
| |
| // Returns access code as string if all fields contain input. |
| base::Optional<std::string> GetCode() const { |
| std::string result; |
| size_t length; |
| for (auto* field : input_fields_) { |
| length = field->text().length(); |
| if (!length) |
| return base::nullopt; |
| |
| DCHECK_EQ(1u, length); |
| base::StrAppend(&result, {base::UTF16ToUTF8(field->text())}); |
| } |
| return result; |
| } |
| |
| // Sets the color of the input text. |
| void SetInputColor(SkColor color) { |
| for (auto* field : input_fields_) { |
| field->SetTextColor(color); |
| } |
| } |
| |
| // views::View: |
| bool IsGroupFocusTraversable() const override { return false; } |
| |
| View* GetSelectedViewForGroup(int group) override { return ActiveField(); } |
| |
| void RequestFocus() override { ActiveField()->RequestFocus(); } |
| |
| // views::TextfieldController: |
| bool HandleKeyEvent(views::Textfield* sender, |
| const ui::KeyEvent& key_event) override { |
| if (key_event.type() != ui::ET_KEY_PRESSED) |
| return false; |
| |
| // AccessCodeInput class responds to limited subset of key press events. |
| // All key pressed events not handled below are ignored. |
| const ui::KeyboardCode key_code = key_event.key_code(); |
| 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_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_LEFT) { |
| FocusPreviousField(); |
| } else if (key_code == ui::VKEY_RIGHT) { |
| // Do not allow to leave empty field when moving focus with arrow key. |
| if (!ActiveInput().empty()) |
| FocusNextField(); |
| } else if (key_code == ui::VKEY_BACK) { |
| Backspace(); |
| } else if (key_code == ui::VKEY_RETURN) { |
| if (GetCode().has_value()) |
| on_enter_.Run(); |
| } |
| |
| return true; |
| } |
| |
| bool HandleMouseEvent(views::Textfield* sender, |
| const ui::MouseEvent& mouse_event) override { |
| if (!mouse_event.IsOnlyLeftMouseButton()) |
| return false; |
| |
| // Move focus to the field that was selected with mouse input. |
| for (size_t i = 0; i < input_fields_.size(); ++i) { |
| if (input_fields_[i] == sender) { |
| active_input_index_ = i; |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool HandleGestureEvent(views::Textfield* sender, |
| const ui::GestureEvent& gesture_event) override { |
| if (gesture_event.details().type() != ui::EventType::ET_GESTURE_TAP) |
| return false; |
| |
| // Move focus to the field that was selected with gesture. |
| for (size_t i = 0; i < input_fields_.size(); ++i) { |
| if (input_fields_[i] == sender) { |
| active_input_index_ = i; |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| private: |
| // Moves focus to the previous input field if it exists. |
| void FocusPreviousField() { |
| if (active_input_index_ == 0) |
| return; |
| |
| --active_input_index_; |
| ActiveField()->RequestFocus(); |
| } |
| |
| // Moves focus to the next input field if it exists. |
| void FocusNextField() { |
| if (active_input_index_ == (static_cast<int>(input_fields_.size()) - 1)) |
| return; |
| |
| ++active_input_index_; |
| ActiveField()->RequestFocus(); |
| } |
| |
| // Returns pointer to the active input field. |
| views::Textfield* ActiveField() const { |
| return input_fields_[active_input_index_]; |
| } |
| |
| // Returns text in the active input field. |
| const base::string16& ActiveInput() const { return ActiveField()->text(); } |
| |
| // To be called when access input code changes (digit is inserted, deleted or |
| // updated). Passes true when code is complete (all digits have input value) |
| // and false otherwise. |
| OnInputChange on_input_change_; |
| |
| // To be called when user pressed enter to submit. |
| OnEnter on_enter_; |
| |
| // An active/focused input field index. Incoming digit will be inserted here. |
| int active_input_index_ = 0; |
| |
| // Unowned input textfields ordered from the first to the last digit. |
| std::vector<views::Textfield*> input_fields_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AccessCodeInput); |
| }; |
| |
| ParentAccessView::TestApi::TestApi(ParentAccessView* view) : view_(view) { |
| DCHECK(view_); |
| } |
| |
| ParentAccessView::TestApi::~TestApi() = default; |
| |
| LoginButton* ParentAccessView::TestApi::back_button() const { |
| return view_->back_button_; |
| } |
| |
| views::Label* ParentAccessView::TestApi::title_label() const { |
| return view_->title_label_; |
| } |
| |
| views::Label* ParentAccessView::TestApi::description_label() const { |
| return view_->description_label_; |
| } |
| |
| views::View* ParentAccessView::TestApi::access_code_view() const { |
| return view_->access_code_view_; |
| } |
| |
| views::LabelButton* ParentAccessView::TestApi::help_button() const { |
| return view_->help_button_; |
| } |
| |
| ArrowButtonView* ParentAccessView::TestApi::submit_button() const { |
| return view_->submit_button_; |
| } |
| |
| LoginPinView* ParentAccessView::TestApi::pin_keyboard_view() const { |
| return view_->pin_keyboard_view_; |
| } |
| |
| ParentAccessView::State ParentAccessView::TestApi::state() const { |
| return view_->state_; |
| } |
| |
| ParentAccessView::Callbacks::Callbacks() = default; |
| |
| ParentAccessView::Callbacks::Callbacks(const Callbacks& other) = default; |
| |
| ParentAccessView::Callbacks::~Callbacks() = default; |
| |
| ParentAccessView::ParentAccessView(const AccountId& account_id, |
| const Callbacks& callbacks) |
| : NonAccessibleView(kParentAccessViewClassName), |
| callbacks_(callbacks), |
| account_id_(account_id) { |
| DCHECK(callbacks.on_finished); |
| |
| // Main view contains all other views aligned vertically and centered. |
| auto layout = std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kVertical, |
| gfx::Insets(kParentAccessViewVerticalInsetDp, |
| kParentAccessViewHorizontalInsetDp), |
| 0); |
| layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); |
| layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| views::BoxLayout* main_layout = SetLayoutManager(std::move(layout)); |
| |
| SetPreferredSize(GetParentAccessViewSize()); |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| |
| const int child_view_width = |
| kParentAccessViewWidthDp - 2 * kParentAccessViewHorizontalInsetDp; |
| |
| // Header view contains back button that is aligned to its start. |
| auto header_layout = std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kHorizontal, gfx::Insets(), 0); |
| header_layout->set_main_axis_alignment( |
| views::BoxLayout::MAIN_AXIS_ALIGNMENT_START); |
| auto* header = new NonAccessibleView(); |
| header->SetPreferredSize(gfx::Size(child_view_width, 0)); |
| header->SetLayoutManager(std::move(header_layout)); |
| AddChildView(header); |
| |
| back_button_ = new LoginButton(this); |
| back_button_->SetPreferredSize(gfx::Size(kArrowSizeDp, kArrowSizeDp)); |
| back_button_->SetBackground( |
| views::CreateSolidBackground(SK_ColorTRANSPARENT)); |
| back_button_->SetImage(views::Button::STATE_NORMAL, |
| gfx::CreateVectorIcon(kLockScreenArrowBackIcon, |
| kArrowSizeDp, SK_ColorWHITE)); |
| back_button_->SetImageAlignment(views::ImageButton::ALIGN_CENTER, |
| views::ImageButton::ALIGN_MIDDLE); |
| back_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_BACK_BUTTON_ACCESSIBLE_NAME)); |
| back_button_->SetFocusBehavior(FocusBehavior::ALWAYS); |
| |
| header->AddChildView(back_button_); |
| |
| // Main view icon. |
| views::ImageView* icon = new views::ImageView(); |
| icon->SetPreferredSize(gfx::Size(kLockIconSizeDp, kLockIconSizeDp)); |
| icon->SetImage(gfx::CreateVectorIcon(kParentAccessLockIcon, SK_ColorWHITE)); |
| AddChildView(icon); |
| |
| auto add_spacer = [&](int height) { |
| auto* spacer = new NonAccessibleView(); |
| spacer->SetPreferredSize(gfx::Size(0, height)); |
| AddChildView(spacer); |
| }; |
| |
| add_spacer(kIconToTitleDistanceDp); |
| |
| auto decorate_label = [](views::Label* label) { |
| label->SetSubpixelRenderingEnabled(false); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetEnabledColor(kTextColor); |
| label->SetFocusBehavior(FocusBehavior::ALWAYS); |
| }; |
| |
| // Main view title. |
| title_label_ = new views::Label( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_PARENT_ACCESS_TITLE), |
| views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY); |
| title_label_->SetFontList(gfx::FontList().Derive( |
| kTitleFontSizeDeltaDp, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM)); |
| decorate_label(title_label_); |
| AddChildView(title_label_); |
| |
| add_spacer(kTitleToDescriptionDistanceDp); |
| |
| // Main view description. |
| description_label_ = new views::Label( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_PARENT_ACCESS_DESCRIPTION), |
| views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY); |
| description_label_->SetMultiLine(true); |
| description_label_->SetLineHeight(kDescriptionTextLineHeightDp); |
| description_label_->SetFontList( |
| gfx::FontList().Derive(kDescriptionFontSizeDeltaDp, gfx::Font::NORMAL, |
| gfx::Font::Weight::NORMAL)); |
| decorate_label(description_label_); |
| AddChildView(description_label_); |
| |
| add_spacer(kDescriptionToAccessCodeDistanceDp); |
| |
| // Access code input view. |
| access_code_view_ = |
| new AccessCodeInput(kParentAccessCodePinLength, |
| base::BindRepeating(&ParentAccessView::OnInputChange, |
| base::Unretained(this)), |
| base::BindRepeating(&ParentAccessView::SubmitCode, |
| base::Unretained(this))); |
| access_code_view_->SetFocusBehavior(FocusBehavior::ALWAYS); |
| AddChildView(access_code_view_); |
| |
| add_spacer(kAccessCodeToPinKeyboardDistanceDp); |
| |
| // Pin keyboard. |
| pin_keyboard_view_ = new LoginPinView( |
| LoginPinView::Style::kNumeric, |
| base::BindRepeating(&AccessCodeInput::InsertDigit, |
| base::Unretained(access_code_view_)), |
| base::BindRepeating(&AccessCodeInput::Backspace, |
| base::Unretained(access_code_view_))); |
| // Backspace key is always enabled and |access_code_| field handles it. |
| pin_keyboard_view_->OnPasswordTextChanged(false); |
| AddChildView(pin_keyboard_view_); |
| |
| // Vertical spacer to consume height remaining in the view after all children |
| // are accounted for. |
| pin_keyboard_to_footer_spacer_ = new NonAccessibleView(); |
| pin_keyboard_to_footer_spacer_->SetPreferredSize( |
| GetPinKeyboardToFooterSpacerSize()); |
| AddChildView(pin_keyboard_to_footer_spacer_); |
| main_layout->SetFlexForView(pin_keyboard_to_footer_spacer_, 1); |
| |
| // Footer view contains help text button aligned to its start, submit |
| // button aligned to its end and spacer view in between. |
| auto* footer = new NonAccessibleView(); |
| footer->SetPreferredSize(gfx::Size(child_view_width, kArrowButtonSizeDp)); |
| auto* bottom_layout = |
| footer->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kHorizontal, gfx::Insets(), 0)); |
| AddChildView(footer); |
| |
| help_button_ = new views::LabelButton( |
| this, l10n_util::GetStringUTF16(IDS_ASH_LOGIN_PARENT_ACCESS_HELP)); |
| help_button_->SetPaintToLayer(); |
| help_button_->layer()->SetFillsBoundsOpaquely(false); |
| help_button_->SetTextSubpixelRenderingEnabled(false); |
| help_button_->SetTextColor(views::Button::STATE_NORMAL, kTextColor); |
| help_button_->SetTextColor(views::Button::STATE_HOVERED, kTextColor); |
| help_button_->SetTextColor(views::Button::STATE_PRESSED, kTextColor); |
| help_button_->SetFocusBehavior(FocusBehavior::ALWAYS); |
| |
| footer->AddChildView(help_button_); |
| |
| auto* horizontal_spacer = new NonAccessibleView(); |
| footer->AddChildView(horizontal_spacer); |
| bottom_layout->SetFlexForView(horizontal_spacer, 1); |
| |
| submit_button_ = new ArrowButtonView(this, kArrowButtonSizeDp); |
| submit_button_->SetBackgroundColor(kArrowButtonColor); |
| submit_button_->SetPreferredSize( |
| gfx::Size(kArrowButtonSizeDp, kArrowButtonSizeDp)); |
| submit_button_->SetEnabled(false); |
| submit_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_SUBMIT_BUTTON_ACCESSIBLE_NAME)); |
| submit_button_->SetFocusBehavior(FocusBehavior::ALWAYS); |
| footer->AddChildView(submit_button_); |
| |
| add_spacer(kSubmitButtonBottomMarginDp); |
| |
| // Pin keyboard is only shown in tablet mode. |
| pin_keyboard_view_->SetVisible(IsTabletMode()); |
| |
| tablet_mode_observer_.Add(Shell::Get()->tablet_mode_controller()); |
| } |
| |
| ParentAccessView::~ParentAccessView() = default; |
| |
| void ParentAccessView::OnPaint(gfx::Canvas* canvas) { |
| views::View::OnPaint(canvas); |
| |
| cc::PaintFlags flags; |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| SkColor color = Shell::Get()->wallpaper_controller()->GetProminentColor( |
| color_utils::ColorProfile(color_utils::LumaRange::DARK, |
| color_utils::SaturationRange::MUTED)); |
| if (color == kInvalidWallpaperColor || color == SK_ColorTRANSPARENT) |
| color = gfx::kGoogleGrey900; |
| flags.setColor(color); |
| canvas->DrawRoundRect(GetContentsBounds(), |
| kParentAccessViewRoundedCornerRadiusDp, flags); |
| } |
| |
| void ParentAccessView::RequestFocus() { |
| access_code_view_->RequestFocus(); |
| } |
| |
| gfx::Size ParentAccessView::CalculatePreferredSize() const { |
| return GetParentAccessViewSize(); |
| } |
| |
| void ParentAccessView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| if (sender == back_button_) { |
| callbacks_.on_finished.Run(false); |
| } else if (sender == help_button_) { |
| // TODO(agawronska): Implement help view. |
| NOTIMPLEMENTED(); |
| } else if (sender == submit_button_) { |
| SubmitCode(); |
| } |
| } |
| |
| void ParentAccessView::OnTabletModeStarted() { |
| VLOG(1) << "Showing PIN keyboard in ParentAccessView"; |
| pin_keyboard_view_->SetVisible(true); |
| // This will trigger ChildPreferredSizeChanged in parent view and Layout() in |
| // view. As the result whole hierarchy will go through re-layout. |
| UpdatePreferredSize(); |
| } |
| |
| void ParentAccessView::OnTabletModeEnded() { |
| VLOG(1) << "Hiding PIN keyboard in ParentAccessView"; |
| DCHECK(pin_keyboard_view_); |
| pin_keyboard_view_->SetVisible(false); |
| // This will trigger ChildPreferredSizeChanged in parent view and Layout() in |
| // view. As the result whole hierarchy will go through re-layout. |
| UpdatePreferredSize(); |
| } |
| |
| void ParentAccessView::OnTabletControllerDestroyed() { |
| tablet_mode_observer_.RemoveAll(); |
| } |
| |
| void ParentAccessView::SubmitCode() { |
| base::Optional<std::string> code = access_code_view_->GetCode(); |
| DCHECK(code.has_value()); |
| |
| Shell::Get()->login_screen_controller()->ValidateParentAccessCode( |
| account_id_, *code, |
| base::BindOnce(&ParentAccessView::OnValidationResult, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ParentAccessView::UpdateState(State state) { |
| if (state_ == state) |
| return; |
| |
| state_ = state; |
| switch (state_) { |
| case State::kNormal: { |
| access_code_view_->SetInputColor(kTextColor); |
| title_label_->SetEnabledColor(kTextColor); |
| title_label_->SetText( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_PARENT_ACCESS_TITLE)); |
| return; |
| } |
| case State::kError: { |
| access_code_view_->SetInputColor(kErrorColor); |
| title_label_->SetEnabledColor(kErrorColor); |
| title_label_->SetText( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_PARENT_ACCESS_TITLE_ERROR)); |
| return; |
| } |
| } |
| } |
| |
| void ParentAccessView::UpdatePreferredSize() { |
| pin_keyboard_to_footer_spacer_->SetPreferredSize( |
| GetPinKeyboardToFooterSpacerSize()); |
| SetPreferredSize(CalculatePreferredSize()); |
| } |
| |
| void ParentAccessView::OnValidationResult(base::Optional<bool> result) { |
| if (result.has_value() && *result) { |
| VLOG(1) << "Parent access code successfully validated"; |
| callbacks_.on_finished.Run(true); |
| return; |
| } |
| |
| VLOG(1) << "Invalid parent access code entered"; |
| UpdateState(State::kError); |
| } |
| |
| void ParentAccessView::OnInputChange(bool complete) { |
| if (state_ == State::kError) |
| UpdateState(State::kNormal); |
| |
| submit_button_->SetEnabled(complete); |
| } |
| |
| void ParentAccessView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| views::View::GetAccessibleNodeData(node_data); |
| node_data->role = ax::mojom::Role::kDialog; |
| node_data->SetName( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_PARENT_ACCESS_DIALOG_NAME)); |
| } |
| |
| } // namespace ash |