| // Copyright 2018 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/system/unified/user_chooser_view.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "ash/public/cpp/ash_view_ids.h" |
| #include "ash/public/cpp/rounded_image_view.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/ash_color_provider.h" |
| #include "ash/system/model/enterprise_domain_model.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_popup_utils.h" |
| #include "ash/system/tray/tri_view.h" |
| #include "ash/system/unified/top_shortcut_button.h" |
| #include "ash/system/unified/top_shortcuts_view.h" |
| #include "ash/system/unified/user_chooser_detailed_view_controller.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/vector_icons.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash { |
| |
| using ContentLayerType = AshColorProvider::ContentLayerType; |
| |
| namespace { |
| |
| // A button that will transition to multi profile login UI. |
| class AddUserButton : public views::Button { |
| public: |
| explicit AddUserButton(UserChooserDetailedViewController* controller); |
| ~AddUserButton() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AddUserButton); |
| }; |
| |
| AddUserButton::AddUserButton(UserChooserDetailedViewController* controller) |
| : Button(base::BindRepeating( |
| &UserChooserDetailedViewController::HandleAddUserAction, |
| base::Unretained(controller))) { |
| SetID(VIEW_ID_ADD_USER_BUTTON); |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets(kUnifiedTopShortcutSpacing), kUnifiedTopShortcutSpacing)); |
| SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT)); |
| SetFocusPainter(TrayPopupUtils::CreateFocusPainter()); |
| |
| auto* icon = AddChildView(std::make_unique<views::ImageView>()); |
| icon->SetImage(gfx::CreateVectorIcon( |
| kSystemMenuNewUserIcon, AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kIconColorPrimary))); |
| |
| auto* label = AddChildView(std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT))); |
| label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kTextColorPrimary)); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetSubpixelRenderingEnabled(false); |
| } |
| |
| class Separator : public views::View { |
| public: |
| explicit Separator(bool between_user) { |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| SetBorder(views::CreateEmptyBorder( |
| between_user |
| ? gfx::Insets(0, kUnifiedUserChooserSeparatorSideMargin) |
| : gfx::Insets(kUnifiedUserChooserLargeSeparatorVerticalSpacing, |
| 0))); |
| views::View* child = new views::View(); |
| // make sure that the view is displayed by setting non-zero size |
| child->SetPreferredSize(gfx::Size(1, 1)); |
| AddChildView(child); |
| child->SetBorder(views::CreateSolidSidedBorder( |
| 0, 0, kUnifiedNotificationSeparatorThickness, 0, |
| AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kSeparatorColor))); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(Separator); |
| }; |
| |
| views::View* CreateAddUserErrorView(const std::u16string& message) { |
| auto* label = new views::Label(message); |
| label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kTextColorPrimary)); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetSubpixelRenderingEnabled(false); |
| label->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets(kUnifiedTopShortcutSpacing))); |
| label->SetMultiLine(true); |
| label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT); |
| return label; |
| } |
| |
| } // namespace |
| |
| views::View* CreateUserAvatarView(int user_index) { |
| DCHECK(Shell::Get()); |
| const UserSession* const user_session = |
| Shell::Get()->session_controller()->GetUserSession(user_index); |
| DCHECK(user_session); |
| |
| if (user_session->user_info.type == user_manager::USER_TYPE_GUEST) { |
| // In guest mode, the user avatar is just a disabled button pod. |
| auto* image_view = new TopShortcutButton(views::Button::PressedCallback(), |
| kSystemMenuGuestIcon, |
| IDS_ASH_STATUS_TRAY_GUEST_LABEL); |
| image_view->SetEnabled(false); |
| return image_view; |
| } |
| auto* image_view = new RoundedImageView( |
| kTrayItemSize / 2, RoundedImageView::Alignment::kLeading); |
| image_view->SetCanProcessEventsWithinSubtree(false); |
| image_view->SetImage(user_session->user_info.avatar.image, |
| gfx::Size(kTrayItemSize, kTrayItemSize)); |
| return image_view; |
| } |
| |
| std::u16string GetUserItemAccessibleString(int user_index) { |
| DCHECK(Shell::Get()); |
| const UserSession* const user_session = |
| Shell::Get()->session_controller()->GetUserSession(user_index); |
| DCHECK(user_session); |
| |
| if (user_session->user_info.type == user_manager::USER_TYPE_GUEST) |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_GUEST_LABEL); |
| |
| if (user_session->user_info.type == user_manager::USER_TYPE_PUBLIC_ACCOUNT) { |
| std::string domain_manager = Shell::Get() |
| ->system_tray_model() |
| ->enterprise_domain() |
| ->enterprise_domain_manager(); |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_PUBLIC_LABEL, |
| base::UTF8ToUTF16(user_session->user_info.display_name), |
| base::UTF8ToUTF16(domain_manager)); |
| } |
| |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_USER_INFO_ACCESSIBILITY, |
| base::UTF8ToUTF16(user_session->user_info.display_name), |
| base::UTF8ToUTF16(user_session->user_info.display_email)); |
| } |
| |
| UserItemButton::UserItemButton(PressedCallback callback, |
| UserChooserDetailedViewController* controller, |
| int user_index, |
| ax::mojom::Role role, |
| bool has_close_button) |
| : Button(user_index == 0 |
| ? views::Button::PressedCallback() |
| : base::BindRepeating( |
| &UserChooserDetailedViewController::HandleUserSwitch, |
| base::Unretained(controller), |
| user_index)), |
| // The button for the currently active user is not clickable. |
| role_(user_index == 0 ? ax::mojom::Role::kLabelText |
| : ax::mojom::Role::kButton), |
| capture_icon_(new views::ImageView), |
| name_(new views::Label), |
| email_(new views::Label) { |
| DCHECK_GT(VIEW_ID_USER_ITEM_BUTTON_END, |
| VIEW_ID_USER_ITEM_BUTTON_START + user_index); |
| SetID(VIEW_ID_USER_ITEM_BUTTON_START + user_index); |
| auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| gfx::Insets(0, kUnifiedTopShortcutSpacing), kUnifiedTopShortcutSpacing)); |
| layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| layout->set_minimum_cross_axis_size(kUnifiedUserChooserRowHeight); |
| AddChildView(CreateUserAvatarView(user_index)); |
| |
| views::View* vertical_labels = new views::View; |
| vertical_labels->SetCanProcessEventsWithinSubtree(false); |
| auto* vertical_layout = |
| vertical_labels->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)); |
| vertical_layout->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStart); |
| |
| const UserSession* const user_session = |
| Shell::Get()->session_controller()->GetUserSession(user_index); |
| |
| name_->SetText(base::UTF8ToUTF16(user_session->user_info.display_name)); |
| name_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kTextColorPrimary)); |
| name_->SetAutoColorReadabilityEnabled(false); |
| name_->SetSubpixelRenderingEnabled(false); |
| vertical_labels->AddChildView(name_); |
| |
| email_->SetText(base::UTF8ToUTF16(user_session->user_info.display_email)); |
| email_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kTextColorSecondary)); |
| email_->SetAutoColorReadabilityEnabled(false); |
| email_->SetSubpixelRenderingEnabled(false); |
| vertical_labels->AddChildView(email_); |
| |
| AddChildView(vertical_labels); |
| layout->SetFlexForView(vertical_labels, 1); |
| |
| capture_icon_->SetImage(gfx::CreateVectorIcon( |
| kSystemTrayRecordingIcon, AshColorProvider::Get()->GetContentLayerColor( |
| ContentLayerType::kIconColorAlert))); |
| if (!has_close_button) { |
| // Add a padding with the same size as the close button, |
| // so as to align all media indicators in a column. |
| capture_icon_->SetBorder(views::CreateEmptyBorder( |
| gfx::Insets(0, 0, 0, kTrayItemSize + kUnifiedTopShortcutSpacing))); |
| } |
| capture_icon_->SetVisible(false); |
| AddChildView(capture_icon_); |
| |
| if (has_close_button) { |
| AddChildView(std::make_unique<TopShortcutButton>( |
| base::BindRepeating( |
| &UserChooserDetailedViewController::TransitionToMainView, |
| base::Unretained(controller)), |
| views::kIcCloseIcon, IDS_APP_ACCNAME_CLOSE)); |
| } |
| |
| SetTooltipText(GetUserItemAccessibleString(user_index)); |
| SetFocusPainter(TrayPopupUtils::CreateFocusPainter()); |
| } |
| |
| void UserItemButton::SetCaptureState(MediaCaptureState capture_state) { |
| capture_icon_->SetVisible(capture_state != MediaCaptureState::kNone); |
| Layout(); |
| |
| int res_id = 0; |
| switch (capture_state) { |
| case MediaCaptureState::kAudioVideo: |
| res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO_VIDEO; |
| break; |
| case MediaCaptureState::kAudio: |
| res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_AUDIO; |
| break; |
| case MediaCaptureState::kVideo: |
| res_id = IDS_ASH_STATUS_TRAY_MEDIA_RECORDING_VIDEO; |
| break; |
| case MediaCaptureState::kNone: |
| break; |
| } |
| if (res_id) |
| capture_icon_->SetTooltipText(l10n_util::GetStringUTF16(res_id)); |
| } |
| |
| std::u16string UserItemButton::GetTooltipText(const gfx::Point& p) const { |
| // If both of them are full shown, hide the tooltip. |
| if (name_->GetPreferredSize().width() <= name_->width() && |
| email_->GetPreferredSize().width() <= email_->width()) { |
| return std::u16string(); |
| } |
| return views::Button::GetTooltipText(p); |
| } |
| |
| void UserItemButton::GetAccessibleNodeData(ui::AXNodeData* node_data) { |
| node_data->role = role_; |
| } |
| |
| UserChooserView::UserChooserView( |
| UserChooserDetailedViewController* controller) { |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical)); |
| const int num_users = |
| Shell::Get()->session_controller()->NumberOfLoggedInUsers(); |
| for (int i = 0; i < num_users; ++i) { |
| std::unique_ptr<UserItemButton> button; |
| if (i == 0) { |
| button = std::make_unique<UserItemButton>( |
| views::Button::PressedCallback(), controller, 0, |
| // The button for the currently active user is not clickable. |
| ax::mojom::Role::kLabelText, true); |
| } else { |
| button = std::make_unique<UserItemButton>( |
| base::BindRepeating( |
| &UserChooserDetailedViewController::HandleUserSwitch, |
| base::Unretained(controller), i), |
| controller, i, ax::mojom::Role::kButton, false); |
| } |
| user_item_buttons_.push_back(AddChildView(std::move(button))); |
| AddChildView(std::make_unique<Separator>(i < num_users - 1)); |
| } |
| |
| switch (Shell::Get()->session_controller()->GetAddUserPolicy()) { |
| case AddUserSessionPolicy::ALLOWED: |
| AddChildView(std::make_unique<AddUserButton>(controller)); |
| break; |
| case AddUserSessionPolicy::ERROR_NOT_ALLOWED_PRIMARY_USER: |
| AddChildView(CreateAddUserErrorView(l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_MESSAGE_NOT_ALLOWED_PRIMARY_USER))); |
| break; |
| case AddUserSessionPolicy::ERROR_MAXIMUM_USERS_REACHED: |
| AddChildView(CreateAddUserErrorView(l10n_util::GetStringFUTF16Int( |
| IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER, |
| session_manager::kMaximumNumberOfUserSessions))); |
| break; |
| case AddUserSessionPolicy::ERROR_NO_ELIGIBLE_USERS: |
| AddChildView(CreateAddUserErrorView( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_MESSAGE_OUT_OF_USERS))); |
| break; |
| case AddUserSessionPolicy::ERROR_LOCKED_TO_SINGLE_USER: |
| AddChildView(CreateAddUserErrorView(l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_MESSAGE_NOT_ALLOWED_PRIMARY_USER))); |
| break; |
| } |
| |
| Shell::Get()->media_controller()->AddObserver(this); |
| Shell::Get()->media_controller()->RequestCaptureState(); |
| } |
| |
| UserChooserView::~UserChooserView() { |
| Shell::Get()->media_controller()->RemoveObserver(this); |
| } |
| |
| void UserChooserView::OnMediaCaptureChanged( |
| const base::flat_map<AccountId, MediaCaptureState>& capture_states) { |
| if (user_item_buttons_.size() != capture_states.size()) |
| return; |
| |
| for (size_t i = 0; i < user_item_buttons_.size(); ++i) { |
| const UserSession* const user_session = |
| Shell::Get()->session_controller()->GetUserSession(i); |
| auto matched = capture_states.find(user_session->user_info.account_id); |
| if (matched != capture_states.end()) { |
| user_item_buttons_[i]->SetCaptureState(matched->second); |
| } |
| } |
| } |
| |
| const char* UserChooserView::GetClassName() const { |
| return "UserChooserView"; |
| } |
| |
| } // namespace ash |