| // Copyright 2017 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/login_password_view.h" |
| |
| #include "ash/accessibility/accessibility_controller_impl.h" |
| #include "ash/login/ui/arrow_button_view.h" |
| #include "ash/login/ui/horizontal_image_sequence_animation_decoder.h" |
| #include "ash/login/ui/hover_notifier.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/login/ui/non_accessible_view.h" |
| #include "ash/public/cpp/login_constants.h" |
| #include "ash/public/cpp/login_types.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/ash_color_provider.h" |
| #include "base/bind.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/constants/chromeos_features.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/events/event_constants.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/types/event_type.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/text_constants.h" |
| #include "ui/resources/grit/ui_resources.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/widget/widget.h" |
| |
| // TODO(jdufault): On two user view the password prompt is visible to |
| // accessibility using special navigation keys even though it is invisible. We |
| // probably need to customize the text box quite a bit, we may want to do |
| // something similar to SearchBoxView. |
| |
| namespace ash { |
| namespace { |
| |
| // How long the user must hover over the easy unlock icon before showing the |
| // tooltip. |
| const int kDelayBeforeShowingTooltipMs = 500; |
| |
| // Margin above/below the password view. |
| constexpr const int kMarginAboveBelowPasswordIconsDp = 8; |
| |
| // Spacing between the password textfield and the submit button. |
| constexpr int kSpacingBetweenPasswordTextFieldAndSubmitButtonDp = 8; |
| |
| // Size (width/height) of the submit button. |
| constexpr int kSubmitButtonSizeDp = 32; |
| |
| // Left padding of the password view allowing the view to have its center |
| // aligned with the one of the user pod. |
| constexpr int kLeftPaddingPasswordView = |
| kSubmitButtonSizeDp + kSpacingBetweenPasswordTextFieldAndSubmitButtonDp; |
| |
| // Width of the password textfield, placed at the center of the password view. |
| constexpr int kPasswordTextfieldWidthDp = 204; |
| |
| // Total width of the password view (left margin + password textfield + spacing |
| // + submit button). |
| constexpr int kPasswordTotalWidthDp = |
| kLeftPaddingPasswordView + kPasswordTextfieldWidthDp + kSubmitButtonSizeDp + |
| kSpacingBetweenPasswordTextFieldAndSubmitButtonDp; |
| |
| // Delta between normal font and font of the typed text. |
| constexpr int kPasswordFontDeltaSize = 5; |
| |
| // Spacing between glyphs. |
| constexpr int kPasswordGlyphSpacing = 6; |
| |
| // Size (width/height) of the display password button. |
| constexpr int kDisplayPasswordButtonSizeDp = 20; |
| |
| // Size (width/height) of the caps lock hint icon. |
| constexpr int kCapsLockIconSizeDp = 20; |
| |
| // Width and height of the easy unlock icon. |
| constexpr const int kEasyUnlockIconSizeDp = 20; |
| |
| // Horizontal distance/margin between the easy unlock icon and the start of |
| // the password view. |
| constexpr const int kHorizontalDistanceBetweenEasyUnlockAndPasswordDp = 12; |
| |
| // Non-empty height, useful for debugging/visualization. |
| constexpr const int kNonEmptyHeight = 1; |
| |
| // Clears the password after some time if no action has been done and the |
| // display password feature is enabled, for security reasons. |
| constexpr base::TimeDelta kClearPasswordAfterDelay = |
| base::TimeDelta::FromSeconds(30); |
| |
| // Hides the password after a short delay for security reasons. |
| constexpr base::TimeDelta kHidePasswordAfterDelay = |
| base::TimeDelta::FromSeconds(3); |
| |
| constexpr const char kLoginPasswordViewName[] = "LoginPasswordView"; |
| |
| class NonAccessibleSeparator : public views::Separator { |
| public: |
| NonAccessibleSeparator() = default; |
| ~NonAccessibleSeparator() override = default; |
| |
| // views::Separator: |
| void GetAccessibleNodeData(ui::AXNodeData* node_data) override { |
| views::Separator::GetAccessibleNodeData(node_data); |
| node_data->AddState(ax::mojom::State::kInvisible); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(NonAccessibleSeparator); |
| }; |
| |
| // Set of resources for an easy unlock icon. |
| struct IconBundle { |
| // Creates an IconBundle for a static image. |
| IconBundle(int normal, int hover, int pressed) |
| : normal(normal), hover(hover), pressed(pressed) {} |
| // Creates an IconBundle instance for an animation. |
| IconBundle(int resource, base::TimeDelta duration, int num_frames) |
| : normal(resource), |
| hover(resource), |
| pressed(resource), |
| duration(duration), |
| num_frames(num_frames) {} |
| |
| // Icons for different button states. |
| const int normal; |
| const int hover; |
| const int pressed; |
| |
| // Animation metadata. If these are set then |normal| == |hover| == |pressed|. |
| const base::TimeDelta duration; |
| const int num_frames = 0; |
| }; |
| |
| // Construct an IconBundle instance for a given EasyUnlockIconId value. |
| IconBundle GetEasyUnlockResources(EasyUnlockIconId id) { |
| switch (id) { |
| case EasyUnlockIconId::NONE: |
| break; |
| case EasyUnlockIconId::HARDLOCKED: |
| return IconBundle(IDR_EASY_UNLOCK_HARDLOCKED, |
| IDR_EASY_UNLOCK_HARDLOCKED_HOVER, |
| IDR_EASY_UNLOCK_HARDLOCKED_PRESSED); |
| case EasyUnlockIconId::LOCKED: |
| return IconBundle(IDR_EASY_UNLOCK_LOCKED, IDR_EASY_UNLOCK_LOCKED_HOVER, |
| IDR_EASY_UNLOCK_LOCKED_PRESSED); |
| case EasyUnlockIconId::LOCKED_TO_BE_ACTIVATED: |
| return IconBundle(IDR_EASY_UNLOCK_LOCKED_TO_BE_ACTIVATED, |
| IDR_EASY_UNLOCK_LOCKED_TO_BE_ACTIVATED_HOVER, |
| IDR_EASY_UNLOCK_LOCKED_TO_BE_ACTIVATED_PRESSED); |
| case EasyUnlockIconId::LOCKED_WITH_PROXIMITY_HINT: |
| return IconBundle(IDR_EASY_UNLOCK_LOCKED_WITH_PROXIMITY_HINT, |
| IDR_EASY_UNLOCK_LOCKED_WITH_PROXIMITY_HINT_HOVER, |
| IDR_EASY_UNLOCK_LOCKED_WITH_PROXIMITY_HINT_PRESSED); |
| case EasyUnlockIconId::UNLOCKED: |
| return IconBundle(IDR_EASY_UNLOCK_UNLOCKED, |
| IDR_EASY_UNLOCK_UNLOCKED_HOVER, |
| IDR_EASY_UNLOCK_UNLOCKED_PRESSED); |
| case EasyUnlockIconId::SPINNER: |
| return IconBundle(IDR_EASY_UNLOCK_SPINNER, |
| base::TimeDelta::FromSeconds(2), /*num_frames=*/45); |
| } |
| |
| NOTREACHED(); |
| return IconBundle(IDR_EASY_UNLOCK_LOCKED, IDR_EASY_UNLOCK_LOCKED_HOVER, |
| IDR_EASY_UNLOCK_LOCKED_PRESSED); |
| } |
| |
| } // namespace |
| |
| // A textfield that selects all text on focus and allows to switch between |
| // show/hide password modes. |
| class LoginPasswordView::LoginTextfield : public views::Textfield { |
| public: |
| LoginTextfield(base::RepeatingClosure on_focus_closure, |
| base::RepeatingClosure on_blur_closure) |
| : on_focus_closure_(std::move(on_focus_closure)), |
| on_blur_closure_(std::move(on_blur_closure)) { |
| SetTextColor(SK_ColorWHITE); |
| SetFontList(views::Textfield::GetDefaultFontList().Derive( |
| kPasswordFontDeltaSize, gfx::Font::FontStyle::NORMAL, |
| gfx::Font::Weight::NORMAL)); |
| SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| set_placeholder_font_list(views::Textfield::GetDefaultFontList()); |
| set_placeholder_text_color(login_constants::kAuthMethodsTextColor); |
| SetObscuredGlyphSpacing(kPasswordGlyphSpacing); |
| SetBorder(nullptr); |
| SetBackgroundColor(SK_ColorTRANSPARENT); |
| } |
| LoginTextfield(const LoginTextfield&) = delete; |
| LoginTextfield& operator=(const LoginTextfield&) = delete; |
| ~LoginTextfield() override = default; |
| |
| // views::Textfield: |
| void OnBlur() override { |
| if (on_blur_closure_) |
| on_blur_closure_.Run(); |
| views::Textfield::OnBlur(); |
| } |
| |
| void OnFocus() override { |
| if (on_focus_closure_) |
| on_focus_closure_.Run(); |
| views::Textfield::OnFocus(); |
| } |
| |
| void AboutToRequestFocusFromTabTraversal(bool reverse) override { |
| SelectAll(/*reversed=*/false); |
| } |
| |
| // Switches between normal input and password input when the user hits the |
| // display password button. |
| void InvertTextInputType() { |
| if (GetTextInputType() == ui::TEXT_INPUT_TYPE_NULL) |
| SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); |
| else |
| SetTextInputType(ui::TEXT_INPUT_TYPE_NULL); |
| } |
| |
| // This is useful when the display password button is not shown. In such a |
| // case, the login text field needs to define its size. |
| gfx::Size CalculatePreferredSize() const override { |
| return gfx::Size(kPasswordTotalWidthDp, kDisplayPasswordButtonSizeDp); |
| } |
| |
| private: |
| // Closures that will be called when the element receives and loses focus. |
| base::RepeatingClosure on_focus_closure_; |
| base::RepeatingClosure on_blur_closure_; |
| }; |
| |
| class LoginPasswordView::EasyUnlockIcon : public views::Button, |
| public views::ButtonListener { |
| public: |
| EasyUnlockIcon(const gfx::Size& size, int corner_radius) |
| : views::Button(this) { |
| SetPreferredSize(size); |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| icon_ = AddChildView( |
| std::make_unique<AnimatedRoundedImageView>(size, corner_radius)); |
| } |
| ~EasyUnlockIcon() override = default; |
| |
| void Init(const OnEasyUnlockIconHovered& on_hovered, |
| const OnEasyUnlockIconTapped& on_tapped) { |
| DCHECK(on_hovered); |
| DCHECK(on_tapped); |
| |
| on_hovered_ = on_hovered; |
| on_tapped_ = on_tapped; |
| |
| hover_notifier_ = std::make_unique<HoverNotifier>( |
| this, base::BindRepeating( |
| &LoginPasswordView::EasyUnlockIcon::OnHoverStateChanged, |
| base::Unretained(this))); |
| } |
| |
| void SetEasyUnlockIcon(EasyUnlockIconId icon_id, |
| const base::string16& accessibility_label) { |
| bool changed_states = icon_id != icon_id_; |
| icon_id_ = icon_id; |
| UpdateImage(changed_states); |
| SetAccessibleName(accessibility_label); |
| } |
| |
| void set_immediately_hover_for_test() { immediately_hover_for_test_ = true; } |
| |
| // views::Button: |
| void StateChanged(ButtonState old_state) override { |
| Button::StateChanged(old_state); |
| |
| // Stop showing tooltip, as we most likely exited hover state. |
| invoke_hover_.Stop(); |
| |
| if (GetState() == ButtonState::STATE_DISABLED) |
| return; |
| |
| UpdateImage(false /*changed_states*/); |
| |
| if (GetState() == ButtonState::STATE_HOVERED) { |
| if (immediately_hover_for_test_) { |
| on_hovered_.Run(); |
| } else { |
| invoke_hover_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kDelayBeforeShowingTooltipMs), |
| on_hovered_); |
| } |
| } |
| } |
| |
| // views::ButtonListener: |
| void ButtonPressed(Button* sender, const ui::Event& event) override { |
| on_tapped_.Run(); |
| } |
| |
| private: |
| void OnHoverStateChanged(bool has_hover) { |
| SetState(has_hover ? ButtonState::STATE_HOVERED |
| : ButtonState::STATE_NORMAL); |
| } |
| |
| void UpdateImage(bool changed_states) { |
| // Ignore any calls happening while the view is not attached; |
| // IsMouseHovered() will CHECK(false) in that scenario. GetWidget() may be |
| // null during construction and GetWidget()->GetRootView() may be null |
| // during destruction. Both scenarios only happen in tests. |
| if (!GetWidget() || !GetWidget()->GetRootView()) |
| return; |
| |
| if (icon_id_ == EasyUnlockIconId::NONE) |
| return; |
| |
| IconBundle resources = GetEasyUnlockResources(icon_id_); |
| |
| int active_resource = resources.normal; |
| if (IsMouseHovered()) |
| active_resource = resources.hover; |
| if (GetState() == ButtonState::STATE_PRESSED) |
| active_resource = resources.pressed; |
| |
| // Image to show. It may or may not be an animation, depending on |
| // |resources.duration|. |
| gfx::ImageSkia* image = |
| ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| active_resource); |
| |
| if (!resources.duration.is_zero()) { |
| // Only change the animation if the state itself has changed, otherwise |
| // the active animation frame is reset and there is a lot of unecessary |
| // decoding/image resizing work. This optimization only is valid only if |
| // all three resource assets are the same. |
| DCHECK_EQ(resources.normal, resources.hover); |
| DCHECK_EQ(resources.normal, resources.pressed); |
| if (changed_states) { |
| icon_->SetAnimationDecoder( |
| std::make_unique<HorizontalImageSequenceAnimationDecoder>( |
| *image, resources.duration, resources.num_frames), |
| AnimatedRoundedImageView::Playback::kRepeat); |
| } |
| } else { |
| icon_->SetImage(*image); |
| } |
| } |
| |
| // Icon we are currently displaying. |
| EasyUnlockIconId icon_id_ = EasyUnlockIconId::NONE; |
| |
| // View which renders the icon. |
| AnimatedRoundedImageView* icon_; |
| |
| // Callbacks run when icon is hovered or tapped. |
| OnEasyUnlockIconHovered on_hovered_; |
| OnEasyUnlockIconTapped on_tapped_; |
| |
| std::unique_ptr<HoverNotifier> hover_notifier_; |
| |
| // Timer used to control when we invoke |on_hover_|. |
| base::OneShotTimer invoke_hover_; |
| |
| // If true, the tooltip/hover timer will be skipped and |on_hover_| will be |
| // run immediately. |
| bool immediately_hover_for_test_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(EasyUnlockIcon); |
| }; |
| |
| class LoginPasswordView::DisplayPasswordButton |
| : public views::ToggleImageButton { |
| public: |
| explicit DisplayPasswordButton(views::ButtonListener* listener) |
| : ToggleImageButton(listener) { |
| const gfx::ImageSkia invisible_icon = gfx::CreateVectorIcon( |
| kLockScreenPasswordInvisibleIcon, kDisplayPasswordButtonSizeDp, |
| login_constants::kButtonEnabledColor); |
| const gfx::ImageSkia visible_icon = gfx::CreateVectorIcon( |
| kLockScreenPasswordVisibleIcon, kDisplayPasswordButtonSizeDp, |
| login_constants::kButtonEnabledColor); |
| const gfx::ImageSkia visible_icon_disabled = gfx::CreateVectorIcon( |
| kLockScreenPasswordVisibleIcon, kDisplayPasswordButtonSizeDp, |
| SkColorSetA(login_constants::kButtonEnabledColor, |
| login_constants::kButtonDisabledAlpha)); |
| SetImage(views::Button::STATE_NORMAL, visible_icon); |
| SetImage(views::Button::STATE_DISABLED, visible_icon_disabled); |
| SetToggledImage(views::Button::STATE_NORMAL, &invisible_icon); |
| |
| SetTooltipText(l10n_util::GetStringUTF16( |
| IDS_ASH_LOGIN_DISPLAY_PASSWORD_BUTTON_ACCESSIBLE_NAME_SHOW)); |
| SetToggledTooltipText(l10n_util::GetStringUTF16( |
| IDS_ASH_LOGIN_DISPLAY_PASSWORD_BUTTON_ACCESSIBLE_NAME_HIDE)); |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| SetInstallFocusRingOnFocus(true); |
| focus_ring()->SetColor(ShelfConfig::Get()->shelf_focus_border_color()); |
| |
| SetEnabled(false); |
| } |
| |
| DisplayPasswordButton(const DisplayPasswordButton&) = delete; |
| DisplayPasswordButton& operator=(const DisplayPasswordButton&) = delete; |
| ~DisplayPasswordButton() override = default; |
| |
| // This should be done automatically per ToggleImageButton. |
| void InvertToggled() { |
| toggled_ = !toggled_; |
| SetToggled(toggled_); |
| } |
| |
| private: |
| bool toggled_ = false; |
| }; |
| |
| LoginPasswordView::TestApi::TestApi(LoginPasswordView* view) : view_(view) {} |
| |
| LoginPasswordView::TestApi::~TestApi() = default; |
| |
| void LoginPasswordView::TestApi::SubmitPassword(const std::string& password) { |
| view_->textfield_->SetText(base::ASCIIToUTF16(password)); |
| view_->UpdateUiState(); |
| view_->SubmitPassword(); |
| } |
| |
| views::Textfield* LoginPasswordView::TestApi::textfield() const { |
| return view_->textfield_; |
| } |
| |
| views::View* LoginPasswordView::TestApi::submit_button() const { |
| return view_->submit_button_; |
| } |
| |
| views::ToggleImageButton* LoginPasswordView::TestApi::display_password_button() |
| const { |
| return view_->display_password_button_; |
| } |
| |
| views::View* LoginPasswordView::TestApi::easy_unlock_icon() const { |
| return view_->easy_unlock_icon_; |
| } |
| |
| void LoginPasswordView::TestApi::set_immediately_hover_easy_unlock_icon() { |
| view_->easy_unlock_icon_->set_immediately_hover_for_test(); |
| } |
| |
| void LoginPasswordView::TestApi::SetTimers( |
| std::unique_ptr<base::RetainingOneShotTimer> clear_timer, |
| std::unique_ptr<base::RetainingOneShotTimer> hide_timer) { |
| view_->clear_password_timer_ = std::move(clear_timer); |
| view_->hide_password_timer_ = std::move(hide_timer); |
| // Starts the clearing timer. |
| view_->SetDisplayPasswordButtonVisible(true); |
| } |
| |
| LoginPasswordView::LoginPasswordView() |
| : is_display_password_feature_enabled_( |
| chromeos::features::IsLoginDisplayPasswordButtonEnabled()), |
| clear_password_timer_(std::make_unique<base::RetainingOneShotTimer>()), |
| hide_password_timer_(std::make_unique<base::RetainingOneShotTimer>()) { |
| Shell::Get()->ime_controller()->AddObserver(this); |
| |
| // Contains the password layout on the left and the submit button on the |
| // right. |
| auto* root_layout = SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets(0, kLeftPaddingPasswordView, 0, 0), |
| kSpacingBetweenPasswordTextFieldAndSubmitButtonDp)); |
| root_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kEnd); |
| |
| // Contains the password row along with the separator. |
| auto* password = AddChildView(std::make_unique<views::View>()); |
| std::unique_ptr<views::BoxLayout> password_layout = |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical); |
| password_layout->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| password->SetLayoutManager(std::move(password_layout)); |
| |
| password_row_ = password->AddChildView(std::make_unique<NonAccessibleView>()); |
| auto layout = std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets(kMarginAboveBelowPasswordIconsDp, 0)); |
| layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter); |
| layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| auto* layout_ptr = password_row_->SetLayoutManager(std::move(layout)); |
| |
| // Add easy unlock icon. |
| easy_unlock_icon_ = |
| password_row_->AddChildView(std::make_unique<EasyUnlockIcon>( |
| gfx::Size(kEasyUnlockIconSizeDp, kEasyUnlockIconSizeDp), |
| /*corner_radius=*/0)); |
| |
| easy_unlock_right_margin_ = |
| password_row_->AddChildView(std::make_unique<NonAccessibleView>()); |
| easy_unlock_right_margin_->SetPreferredSize(gfx::Size( |
| kHorizontalDistanceBetweenEasyUnlockAndPasswordDp, kNonEmptyHeight)); |
| |
| // Easy unlock starts invisible. There will be an event later to show it if |
| // needed. |
| easy_unlock_icon_->SetVisible(false); |
| easy_unlock_right_margin_->SetVisible(false); |
| |
| // Password textfield. We control the textfield size by sizing the parent |
| // view, as the textfield will expand to fill it. |
| auto textfield = std::make_unique<LoginTextfield>( |
| // Highlight on focus. Remove highlight on blur. |
| base::BindRepeating( |
| &LoginPasswordView::SetSeparatorAndCapsLockHighlighted, |
| base::Unretained(this), /*highlight=*/true), |
| base::BindRepeating( |
| &LoginPasswordView::SetSeparatorAndCapsLockHighlighted, |
| base::Unretained(this), /*highlight=*/false)); |
| textfield_ = password_row_->AddChildView(std::move(textfield)); |
| textfield_->set_controller(this); |
| |
| layout_ptr->SetFlexForView(textfield_, 1); |
| |
| // Caps lock hint icon. |
| capslock_icon_ = |
| password_row_->AddChildView(std::make_unique<views::ImageView>()); |
| // Caps lock hint starts invisible. This constructor will call |
| // OnCapsLockChanged with the actual caps lock state. |
| capslock_icon_->SetVisible(false); |
| |
| if (is_display_password_feature_enabled_) { |
| display_password_button_ = password_row_->AddChildView( |
| std::make_unique<DisplayPasswordButton>(this)); |
| } |
| |
| // Separator on bottom. |
| separator_ = |
| password->AddChildView(std::make_unique<NonAccessibleSeparator>()); |
| |
| submit_button_ = AddChildView(std::make_unique<ArrowButtonView>( |
| /*listener=*/this, kSubmitButtonSizeDp)); |
| const AshColorProvider* color_provider = AshColorProvider::Get(); |
| SkColor color = color_provider->GetControlsLayerColor( |
| AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive, |
| AshColorProvider::AshColorMode::kDark); |
| submit_button_->SetBackgroundColor(color); |
| submit_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_ASH_LOGIN_SUBMIT_BUTTON_ACCESSIBLE_NAME)); |
| submit_button_->SetEnabled(false); |
| |
| // Initialize the capslock icon and the separator without a highlight. |
| SetSeparatorAndCapsLockHighlighted(/*highlight=*/false); |
| |
| // Make sure the textfield always starts with focus. |
| textfield_->RequestFocus(); |
| |
| // Initialize with the initial state of caps lock. |
| OnCapsLockChanged(Shell::Get()->ime_controller()->IsCapsLockEnabled()); |
| |
| // Make sure the UI start with the correct states. |
| UpdateUiState(); |
| } |
| |
| LoginPasswordView::~LoginPasswordView() { |
| Shell::Get()->ime_controller()->RemoveObserver(this); |
| } |
| |
| void LoginPasswordView::Init( |
| const OnPasswordSubmit& on_submit, |
| const OnPasswordTextChanged& on_password_text_changed, |
| const OnEasyUnlockIconHovered& on_easy_unlock_icon_hovered, |
| const OnEasyUnlockIconTapped& on_easy_unlock_icon_tapped) { |
| DCHECK(on_submit); |
| DCHECK(on_password_text_changed); |
| on_submit_ = on_submit; |
| on_password_text_changed_ = on_password_text_changed; |
| easy_unlock_icon_->Init(on_easy_unlock_icon_hovered, |
| on_easy_unlock_icon_tapped); |
| } |
| |
| void LoginPasswordView::SetEnabledOnEmptyPassword(bool enabled) { |
| enabled_on_empty_password_ = enabled; |
| UpdateUiState(); |
| } |
| |
| void LoginPasswordView::SetEasyUnlockIcon( |
| EasyUnlockIconId id, |
| const base::string16& accessibility_label) { |
| // Update icon. |
| easy_unlock_icon_->SetEasyUnlockIcon(id, accessibility_label); |
| |
| // Update icon visiblity. |
| bool has_icon = id != EasyUnlockIconId::NONE; |
| easy_unlock_icon_->SetVisible(has_icon); |
| easy_unlock_right_margin_->SetVisible(has_icon); |
| password_row_->Layout(); |
| } |
| |
| void LoginPasswordView::SetAccessibleName(const base::string16& name) { |
| textfield_->SetAccessibleName(name); |
| } |
| |
| void LoginPasswordView::SetFocusEnabledForChildViews(bool enable) { |
| auto behavior = enable ? FocusBehavior::ALWAYS : FocusBehavior::NEVER; |
| textfield_->SetFocusBehavior(behavior); |
| } |
| |
| void LoginPasswordView::SetDisplayPasswordButtonVisible(bool visible) { |
| if (!is_display_password_feature_enabled_) |
| return; |
| display_password_button_->SetVisible(visible); |
| // Only start the timer if the display password button is enabled. |
| if (visible) { |
| clear_password_timer_->Start( |
| FROM_HERE, kClearPasswordAfterDelay, |
| base::BindRepeating(&LoginPasswordView::Clear, base::Unretained(this))); |
| } |
| } |
| |
| void LoginPasswordView::Reset() { |
| Clear(); |
| |
| if (is_display_password_feature_enabled_) { |
| // A user could hit the display button, then quickly switch account and |
| // type; we want the password to be hidden in such a case. |
| HidePassword(false /*chromevox_exception*/); |
| } |
| } |
| |
| void LoginPasswordView::Clear() { |
| textfield_->SetText(base::string16()); |
| // For security reasons, we also want to clear the edit history if the Clear |
| // function is invoked by the clear password timer. |
| textfield_->ClearEditHistory(); |
| // |ContentsChanged| won't be called by |Textfield| if the text is changed |
| // by |Textfield::SetText()|. |
| ContentsChanged(textfield_, textfield_->GetText()); |
| } |
| |
| void LoginPasswordView::InsertNumber(int value) { |
| if (!textfield_->HasFocus()) { |
| // RequestFocus on textfield to activate cursor. |
| textfield_->RequestFocus(); |
| } |
| textfield_->InsertOrReplaceText(base::NumberToString16(value)); |
| } |
| |
| void LoginPasswordView::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. |
| auto* view = static_cast<views::View*>(textfield_); |
| view->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_BACK, |
| ui::DomCode::BACKSPACE, ui::EF_NONE)); |
| view->OnKeyPressed(ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_BACK, |
| ui::DomCode::BACKSPACE, ui::EF_NONE)); |
| } |
| |
| void LoginPasswordView::SetPlaceholderText( |
| const base::string16& placeholder_text) { |
| textfield_->SetPlaceholderText(placeholder_text); |
| SchedulePaint(); |
| } |
| |
| void LoginPasswordView::SetReadOnly(bool read_only) { |
| textfield_->SetReadOnly(read_only); |
| textfield_->SetCursorEnabled(!read_only); |
| UpdateUiState(); |
| } |
| |
| const char* LoginPasswordView::GetClassName() const { |
| return kLoginPasswordViewName; |
| } |
| |
| gfx::Size LoginPasswordView::CalculatePreferredSize() const { |
| gfx::Size size = views::View::CalculatePreferredSize(); |
| size.set_width(kPasswordTotalWidthDp); |
| return size; |
| } |
| |
| void LoginPasswordView::RequestFocus() { |
| textfield_->RequestFocus(); |
| } |
| |
| bool LoginPasswordView::OnKeyPressed(const ui::KeyEvent& event) { |
| if (event.key_code() == ui::KeyboardCode::VKEY_RETURN && |
| IsPasswordSubmittable()) { |
| SubmitPassword(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void LoginPasswordView::InvertPasswordDisplayingState() { |
| display_password_button_->InvertToggled(); |
| textfield_->InvertTextInputType(); |
| hide_password_timer_->Start( |
| FROM_HERE, kHidePasswordAfterDelay, |
| base::BindRepeating(&LoginPasswordView::HidePassword, |
| base::Unretained(this), |
| true /*chromevox_exception*/)); |
| } |
| |
| void LoginPasswordView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| if (sender == submit_button_) { |
| SubmitPassword(); |
| } else if (is_display_password_feature_enabled_) { |
| DCHECK_EQ(sender, display_password_button_); |
| InvertPasswordDisplayingState(); |
| } |
| } |
| |
| void LoginPasswordView::HidePassword(bool chromevox_exception) { |
| if (chromevox_exception && |
| Shell::Get()->accessibility_controller()->spoken_feedback_enabled()) { |
| return; |
| } |
| if (textfield_->GetTextInputType() == ui::TEXT_INPUT_TYPE_NULL) |
| InvertPasswordDisplayingState(); |
| } |
| |
| void LoginPasswordView::ContentsChanged(views::Textfield* sender, |
| const base::string16& new_contents) { |
| DCHECK_EQ(sender, textfield_); |
| UpdateUiState(); |
| on_password_text_changed_.Run(new_contents.empty() /*is_empty*/); |
| |
| if (!is_display_password_feature_enabled_) |
| return; |
| |
| // Only reset the timer if the display password feature is enabled. |
| if (display_password_button_->GetVisible()) |
| clear_password_timer_->Reset(); |
| // For UX purposes, hide back the password when the user is typing. |
| HidePassword(false /*chromevox_exception*/); |
| display_password_button_->SetEnabled(!new_contents.empty()); |
| } |
| |
| // Implements swapping active user with arrow keys |
| bool LoginPasswordView::HandleKeyEvent(views::Textfield* sender, |
| const ui::KeyEvent& key_event) { |
| // Treat the password field as normal if it has text |
| if (!textfield_->GetText().empty()) |
| return false; |
| |
| if (key_event.type() != ui::ET_KEY_PRESSED) |
| return false; |
| |
| if (key_event.is_repeat()) |
| return false; |
| |
| switch (key_event.key_code()) { |
| case ui::VKEY_LEFT: |
| LockScreen::Get()->FocusPreviousUser(); |
| break; |
| case ui::VKEY_RIGHT: |
| LockScreen::Get()->FocusNextUser(); |
| break; |
| default: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void LoginPasswordView::UpdateUiState() { |
| submit_button_->SetEnabled(IsPasswordSubmittable()); |
| } |
| |
| void LoginPasswordView::OnCapsLockChanged(bool enabled) { |
| capslock_icon_->SetVisible(enabled); |
| password_row_->Layout(); |
| } |
| |
| bool LoginPasswordView::IsPasswordSubmittable() { |
| return !textfield_->GetReadOnly() && |
| (enabled_on_empty_password_ || !textfield_->GetText().empty()); |
| } |
| |
| void LoginPasswordView::SubmitPassword() { |
| DCHECK(IsPasswordSubmittable()); |
| if (textfield_->GetReadOnly()) |
| return; |
| on_submit_.Run(textfield_->GetText()); |
| } |
| |
| void LoginPasswordView::SetSeparatorAndCapsLockHighlighted(bool highlight) { |
| SkColor color = login_constants::kButtonEnabledColor; |
| if (!highlight) |
| color = SkColorSetA(color, login_constants::kButtonDisabledAlpha); |
| separator_->SetColor(color); |
| capslock_icon_->SetImage(gfx::CreateVectorIcon(kLockScreenCapsLockIcon, |
| kCapsLockIconSizeDp, color)); |
| } |
| |
| } // namespace ash |