| // 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/shelf/login_shelf_view.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/ash_constants.h" |
| #include "ash/focus_cycler.h" |
| #include "ash/lock_screen_action/lock_screen_action_background_controller.h" |
| #include "ash/lock_screen_action/lock_screen_action_background_state.h" |
| #include "ash/login/login_screen_controller.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/login/ui/lock_window.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_constants.h" |
| #include "ash/shelf/shelf_widget.h" |
| #include "ash/shell.h" |
| #include "ash/shutdown_controller.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/status_area_widget_delegate.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/system/tray/tray_popup_utils.h" |
| #include "ash/tray_action/tray_action.h" |
| #include "ash/wm/lock_state_controller.h" |
| #include "base/metrics/user_metrics.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/accessibility/ax_aura_obj_cache.h" |
| #include "ui/views/animation/ink_drop_impl.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/focus/focus_search.h" |
| #include "ui/views/layout/box_layout.h" |
| |
| using session_manager::SessionState; |
| |
| namespace ash { |
| namespace { |
| |
| LoginMetricsRecorder::LockScreenUserClickTarget GetUserClickTarget( |
| int button_id) { |
| // TODO(agawronska): Add metrics for login screen only buttons. |
| // https://crbug.com/798848 |
| switch (button_id) { |
| case LoginShelfView::kShutdown: |
| return LoginMetricsRecorder::LockScreenUserClickTarget::kShutDownButton; |
| case LoginShelfView::kRestart: |
| return LoginMetricsRecorder::LockScreenUserClickTarget::kRestartButton; |
| case LoginShelfView::kSignOut: |
| return LoginMetricsRecorder::LockScreenUserClickTarget::kSignOutButton; |
| case LoginShelfView::kCloseNote: |
| return LoginMetricsRecorder::LockScreenUserClickTarget::kCloseNoteButton; |
| case LoginShelfView::kCancel: |
| return LoginMetricsRecorder::LockScreenUserClickTarget::kTargetCount; |
| } |
| return LoginMetricsRecorder::LockScreenUserClickTarget::kTargetCount; |
| } |
| |
| // Spacing between the button image and label. |
| constexpr int kImageLabelSpacingDp = 8; |
| |
| // The width of the four margins of each button. |
| constexpr int kButtonMarginDp = 13; |
| |
| // The color of the button image and label. |
| constexpr SkColor kButtonColor = SK_ColorWHITE; |
| |
| class LoginShelfButton : public views::LabelButton { |
| public: |
| LoginShelfButton(views::ButtonListener* listener, |
| const base::string16& text, |
| const gfx::ImageSkia& image) |
| : LabelButton(listener, text) { |
| SetAccessibleName(text); |
| SetImage(views::Button::STATE_NORMAL, image); |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| SetFocusPainter(views::Painter::CreateSolidFocusPainter( |
| kFocusBorderColor, kFocusBorderThickness, gfx::InsetsF())); |
| SetInkDropMode(views::InkDropHostView::InkDropMode::ON); |
| set_ink_drop_base_color(kShelfInkDropBaseColor); |
| set_ink_drop_visible_opacity(kShelfInkDropVisibleOpacity); |
| SetTextSubpixelRenderingEnabled(false); |
| |
| SetImageLabelSpacing(kImageLabelSpacingDp); |
| SetTextColor(views::Button::STATE_NORMAL, kButtonColor); |
| SetTextColor(views::Button::STATE_HOVERED, kButtonColor); |
| SetTextColor(views::Button::STATE_PRESSED, kButtonColor); |
| label()->SetFontList(views::Label::GetDefaultFontList().Derive( |
| 1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL)); |
| } |
| |
| ~LoginShelfButton() override = default; |
| |
| // views::View: |
| gfx::Insets GetInsets() const override { |
| return gfx::Insets(kButtonMarginDp); |
| } |
| |
| // views::InkDropHostView: |
| std::unique_ptr<views::InkDrop> CreateInkDrop() override { |
| std::unique_ptr<views::InkDropImpl> ink_drop = |
| std::make_unique<views::InkDropImpl>(this, size()); |
| ink_drop->SetShowHighlightOnHover(false); |
| ink_drop->SetShowHighlightOnFocus(false); |
| return std::move(ink_drop); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(LoginShelfButton); |
| }; |
| |
| } // namespace |
| |
| LoginShelfView::LoginShelfView( |
| LockScreenActionBackgroundController* lock_screen_action_background) |
| : lock_screen_action_background_(lock_screen_action_background), |
| tray_action_observer_(this), |
| lock_screen_action_background_observer_(this), |
| shutdown_controller_observer_(this) { |
| // We reuse the focusable state on this view as a signal that focus should |
| // switch to the lock screen or status area. This view should otherwise not |
| // be focusable. |
| SetFocusBehavior(FocusBehavior::ALWAYS); |
| SetLayoutManager( |
| std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal)); |
| |
| auto add_button = [this](ButtonId id, int text_resource_id, |
| const gfx::VectorIcon& icon) { |
| const base::string16 text = l10n_util::GetStringUTF16(text_resource_id); |
| gfx::ImageSkia image = CreateVectorIcon(icon, kButtonColor); |
| LoginShelfButton* button = new LoginShelfButton(this, text, image); |
| button->set_id(id); |
| AddChildView(button); |
| }; |
| add_button(kShutdown, IDS_ASH_SHELF_SHUTDOWN_BUTTON, |
| kShelfShutdownButtonIcon); |
| add_button(kRestart, IDS_ASH_SHELF_RESTART_BUTTON, kShelfShutdownButtonIcon); |
| add_button(kSignOut, IDS_ASH_SHELF_SIGN_OUT_BUTTON, kShelfSignOutButtonIcon); |
| add_button(kCloseNote, IDS_ASH_SHELF_UNLOCK_BUTTON, kShelfUnlockButtonIcon); |
| add_button(kCancel, IDS_ASH_SHELF_CANCEL_BUTTON, kShelfCancelButtonIcon); |
| add_button(kBrowseAsGuest, IDS_ASH_BROWSE_AS_GUEST_BUTTON, |
| kShelfBrowseAsGuestButtonIcon); |
| add_button(kAddUser, IDS_ASH_ADD_USER_BUTTON, kShelfAddPersonButtonIcon); |
| |
| // Adds observers for states that affect the visiblity of different buttons. |
| tray_action_observer_.Add(Shell::Get()->tray_action()); |
| shutdown_controller_observer_.Add(Shell::Get()->shutdown_controller()); |
| lock_screen_action_background_observer_.Add(lock_screen_action_background); |
| UpdateUi(); |
| } |
| |
| LoginShelfView::~LoginShelfView() = default; |
| |
| void LoginShelfView::UpdateAfterSessionStateChange(SessionState state) { |
| UpdateUi(); |
| } |
| |
| const char* LoginShelfView::GetClassName() const { |
| return "LoginShelfView"; |
| } |
| |
| void LoginShelfView::OnFocus() { |
| LOG(WARNING) << "LoginShelfView was focused, but this should never happen. " |
| "Forwarded focus to shelf widget with an unknown direction."; |
| Shell::Get()->focus_cycler()->FocusWidget( |
| Shelf::ForWindow(GetWidget()->GetNativeWindow())->shelf_widget()); |
| } |
| |
| void LoginShelfView::AboutToRequestFocusFromTabTraversal(bool reverse) { |
| if (reverse) { |
| // Focus should leave the system tray. |
| Shell::Get()->system_tray_notifier()->NotifyFocusOut(reverse); |
| } else { |
| // Focus goes to status area. |
| Shelf::ForWindow(GetWidget()->GetNativeWindow()) |
| ->GetStatusAreaWidget() |
| ->status_area_widget_delegate() |
| ->set_default_last_focusable_child(reverse); |
| Shell::Get()->focus_cycler()->FocusWidget( |
| Shelf::ForWindow(GetWidget()->GetNativeWindow()) |
| ->GetStatusAreaWidget()); |
| } |
| } |
| |
| void LoginShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| if (LockScreen::IsShown()) { |
| int previous_id = views::AXAuraObjCache::GetInstance()->GetID( |
| static_cast<views::Widget*>(LockScreen::Get()->window())); |
| node_data->AddIntAttribute(ui::AX_ATTR_PREVIOUS_FOCUS_ID, previous_id); |
| } |
| |
| Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow()); |
| int next_id = |
| views::AXAuraObjCache::GetInstance()->GetID(shelf->GetStatusAreaWidget()); |
| node_data->AddIntAttribute(ui::AX_ATTR_NEXT_FOCUS_ID, next_id); |
| } |
| |
| void LoginShelfView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| UserMetricsRecorder::RecordUserClick(GetUserClickTarget(sender->id())); |
| switch (sender->id()) { |
| case kShutdown: |
| case kRestart: |
| // |ShutdownController| will further distinguish the two cases based on |
| // shutdown policy. |
| Shell::Get()->lock_state_controller()->RequestShutdown( |
| ShutdownReason::LOGIN_SHUT_DOWN_BUTTON); |
| break; |
| case kSignOut: |
| base::RecordAction(base::UserMetricsAction("ScreenLocker_Signout")); |
| Shell::Get()->session_controller()->RequestSignOut(); |
| break; |
| case kCloseNote: |
| Shell::Get()->tray_action()->CloseLockScreenNote( |
| mojom::CloseLockScreenNoteReason::kUnlockButtonPressed); |
| break; |
| case kCancel: |
| Shell::Get()->login_screen_controller()->CancelAddUser(); |
| break; |
| case kBrowseAsGuest: |
| Shell::Get()->login_screen_controller()->LoginAsGuest(); |
| break; |
| case kAddUser: |
| NOTIMPLEMENTED(); |
| break; |
| } |
| } |
| |
| void LoginShelfView::OnLockScreenNoteStateChanged( |
| mojom::TrayActionState state) { |
| UpdateUi(); |
| } |
| |
| void LoginShelfView::OnLockScreenActionBackgroundStateChanged( |
| LockScreenActionBackgroundState state) { |
| UpdateUi(); |
| } |
| |
| void LoginShelfView::OnShutdownPolicyChanged(bool reboot_on_shutdown) { |
| UpdateUi(); |
| } |
| |
| bool LoginShelfView::LockScreenActionBackgroundAnimating() const { |
| return lock_screen_action_background_->state() == |
| LockScreenActionBackgroundState::kShowing || |
| lock_screen_action_background_->state() == |
| LockScreenActionBackgroundState::kHiding; |
| } |
| |
| void LoginShelfView::UpdateUi() { |
| SessionState session_state = |
| Shell::Get()->session_controller()->GetSessionState(); |
| if (session_state == SessionState::ACTIVE) { |
| // The entire view was set invisible. The buttons are also set invisible |
| // to avoid affecting calculation of the shelf size. |
| for (int i = 0; i < child_count(); ++i) |
| child_at(i)->SetVisible(false); |
| return; |
| } |
| bool show_reboot = Shell::Get()->shutdown_controller()->reboot_on_shutdown(); |
| mojom::TrayActionState tray_action_state = |
| Shell::Get()->tray_action()->GetLockScreenNoteState(); |
| bool is_lock_screen_note_in_foreground = |
| (tray_action_state == mojom::TrayActionState::kActive || |
| tray_action_state == mojom::TrayActionState::kLaunching) && |
| !LockScreenActionBackgroundAnimating(); |
| |
| // The following should be kept in sync with |updateUI_| in md_header_bar.js. |
| GetViewByID(kShutdown)->SetVisible(!show_reboot && |
| !is_lock_screen_note_in_foreground); |
| GetViewByID(kRestart)->SetVisible(show_reboot && |
| !is_lock_screen_note_in_foreground); |
| GetViewByID(kSignOut)->SetVisible(session_state == SessionState::LOCKED && |
| !is_lock_screen_note_in_foreground); |
| GetViewByID(kCloseNote) |
| ->SetVisible(session_state == SessionState::LOCKED && |
| is_lock_screen_note_in_foreground); |
| GetViewByID(kCancel)->SetVisible(session_state == |
| SessionState::LOGIN_SECONDARY); |
| // TODO(agawronska): Implement full list of conditions for buttons visibility, |
| // when views based shelf if enabled during OOBE. https://crbug.com/798869 |
| bool is_login_primary = (session_state == SessionState::LOGIN_PRIMARY); |
| GetViewByID(kBrowseAsGuest)->SetVisible(is_login_primary); |
| GetViewByID(kAddUser)->SetVisible(is_login_primary); |
| Layout(); |
| } |
| |
| } // namespace ash |