| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/unified/quick_settings_header.h" |
| |
| #include <memory> |
| |
| #include "ash/ash_element_identifiers.h" |
| #include "ash/constants/quick_settings_catalogs.h" |
| #include "ash/login/ui/lock_screen.h" |
| #include "ash/public/cpp/ash_view_ids.h" |
| #include "ash/public/cpp/session/session_observer.h" |
| #include "ash/public/cpp/system_tray_client.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/shell_delegate.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/ash_color_id.h" |
| #include "ash/style/ash_color_provider.h" |
| #include "ash/style/typography.h" |
| #include "ash/system/channel_indicator/channel_indicator_quick_settings_view.h" |
| #include "ash/system/channel_indicator/channel_indicator_utils.h" |
| #include "ash/system/enterprise/enterprise_domain_observer.h" |
| #include "ash/system/model/enterprise_domain_model.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/model/update_model.h" |
| #include "ash/system/supervised/supervised_icon_string.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/unified/quick_settings_metrics_util.h" |
| #include "ash/system/unified/unified_system_tray_controller.h" |
| #include "ash/system/update/eol_notice_quick_settings_view.h" |
| #include "ash/system/update/extended_updates_notice_quick_settings_view.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "components/session_manager/session_manager_types.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/chromeos/devicetype_utils.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/views/animation/ink_drop.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/view.h" |
| #include "ui/views/view_class_properties.h" |
| |
| namespace ash { |
| namespace { |
| |
| // The bottom padding is 0 so this view is flush with the feature tiles. |
| constexpr auto kHeaderPadding = gfx::Insets::TLBR(16, 16, 0, 16); |
| |
| // Horizontal space between header buttons. |
| constexpr int kButtonSpacing = 8; |
| |
| // Header button size when the button is narrow (e.g. two column layout). |
| constexpr gfx::Size kNarrowButtonSize(180, 32); |
| |
| // Header button size when the button is wide (e.g. one column layout). |
| constexpr gfx::Size kWideButtonSize(408, 32); |
| |
| constexpr int kManagedStateCornerRadius = 16; |
| constexpr float kManagedStateStrokeWidth = 1.0f; |
| constexpr auto kManagedStateBorderInsets = gfx::Insets::TLBR(0, 12, 0, 12); |
| constexpr gfx::Size kManagedStateImageSize(20, 20); |
| |
| // Shows account settings in OS settings, which includes a link to install or |
| // open the Family Link app to see supervision settings. |
| void ShowAccountSettings() { |
| quick_settings_metrics_util::RecordQsButtonActivated( |
| QsButtonCatalogName::kSupervisedButton); |
| Shell::Get()->system_tray_model()->client()->ShowAccountSettings(); |
| } |
| |
| } // namespace |
| |
| class QuickSettingsHeader::ManagedStateView : public views::Button { |
| METADATA_HEADER(ManagedStateView, views::Button) |
| |
| public: |
| ManagedStateView(base::OnceClosure callback, |
| int label_id, |
| const gfx::VectorIcon& icon) |
| : views::Button(std::move(callback)), icon_(icon) { |
| auto* layout_manager = SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), |
| kUnifiedSystemInfoSpacing)); |
| |
| // Image goes first. |
| image_ = AddChildView(std::make_unique<views::ImageView>()); |
| label_ = AddChildView(std::make_unique<views::Label>()); |
| |
| // Inset the icon and label so they aren't too close to the rounded corners. |
| layout_manager->set_inside_border_insets(kManagedStateBorderInsets); |
| layout_manager->set_main_axis_alignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| label_->SetAutoColorReadabilityEnabled(false); |
| label_->SetSubpixelRenderingEnabled(false); |
| label_->SetText(l10n_util::GetStringUTF16(label_id)); |
| |
| image_->SetPreferredSize(kManagedStateImageSize); |
| label_->SetEnabledColor(cros_tokens::kCrosSysOnSurfaceVariant); |
| TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosBody2, |
| *label_); |
| SetInstallFocusRingOnFocus(true); |
| views::FocusRing::Get(this)->SetColorId(cros_tokens::kCrosSysFocusRing); |
| views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON); |
| views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(), |
| kManagedStateCornerRadius); |
| } |
| |
| ManagedStateView(const ManagedStateView&) = delete; |
| |
| ManagedStateView& operator=(const ManagedStateView&) = delete; |
| |
| ~ManagedStateView() override = default; |
| |
| views::Label* label() { return label_; } |
| |
| private: |
| // views::Button: |
| views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override { |
| // Tooltip events should be handled by this top-level view. |
| return HitTestPoint(point) ? this : nullptr; |
| } |
| |
| // views::Button: |
| // TODO(b/311234537): consider to remove this override and set color ids. |
| void OnThemeChanged() override { |
| views::Button::OnThemeChanged(); |
| const std::pair<SkColor, float> base_color_and_opacity = |
| AshColorProvider::Get()->GetInkDropBaseColorAndOpacity(); |
| views::InkDrop::Get(this)->SetBaseColor(base_color_and_opacity.first); |
| image_->SetImage(ui::ImageModel::FromVectorIcon( |
| *icon_, |
| GetColorProvider()->GetColor(cros_tokens::kCrosSysOnSurfaceVariant))); |
| } |
| |
| // views::Button: |
| void PaintButtonContents(gfx::Canvas* canvas) override { |
| // Draw a button outline similar to ChannelIndicatorQuickSettingsView's |
| // VersionButton outline. |
| cc::PaintFlags flags; |
| flags.setColor( |
| GetColorProvider()->GetColor(cros_tokens::kCrosSysSeparator)); |
| flags.setStyle(cc::PaintFlags::kStroke_Style); |
| flags.setStrokeWidth(kManagedStateStrokeWidth); |
| flags.setAntiAlias(true); |
| const float half_stroke_width = kManagedStateStrokeWidth / 2.0f; |
| gfx::RectF bounds(GetLocalBounds()); |
| bounds.Inset(half_stroke_width); |
| canvas->DrawRoundRect(bounds, kManagedStateCornerRadius, flags); |
| } |
| |
| // Owned by views hierarchy. |
| raw_ptr<views::Label> label_ = nullptr; |
| raw_ptr<views::ImageView> image_ = nullptr; |
| |
| const raw_ref<const gfx::VectorIcon> icon_; |
| }; |
| |
| BEGIN_METADATA(QuickSettingsHeader, ManagedStateView) |
| END_METADATA |
| |
| class QuickSettingsHeader::EnterpriseManagedView |
| : public ManagedStateView, |
| public EnterpriseDomainObserver, |
| public SessionObserver { |
| METADATA_HEADER(EnterpriseManagedView, ManagedStateView) |
| |
| public: |
| explicit EnterpriseManagedView(UnifiedSystemTrayController* controller) |
| : ManagedStateView( |
| base::BindRepeating( |
| &QuickSettingsHeader::ShowEnterpriseInfo, |
| base::Unretained(controller), |
| base::FeatureList::IsEnabled( |
| ash::features::kImprovedManagementDisclosure), |
| Shell::Get()->session_controller()->IsUserSessionBlocked(), |
| !Shell::Get() |
| ->system_tray_model() |
| ->enterprise_domain() |
| ->enterprise_domain_manager() |
| .empty()), |
| IDS_ASH_ENTERPRISE_DEVICE_MANAGED_SHORT, |
| kQuickSettingsManagedIcon) { |
| DCHECK(Shell::Get()); |
| SetID(VIEW_ID_QS_MANAGED_BUTTON); |
| SetProperty(views::kElementIdentifierKey, kEnterpriseManagedView); |
| Shell::Get()->system_tray_model()->enterprise_domain()->AddObserver(this); |
| Shell::Get()->session_controller()->AddObserver(this); |
| Update(); |
| } |
| |
| EnterpriseManagedView(const EnterpriseManagedView&) = delete; |
| EnterpriseManagedView& operator=(const EnterpriseManagedView&) = delete; |
| |
| ~EnterpriseManagedView() override { |
| Shell::Get()->system_tray_model()->enterprise_domain()->RemoveObserver( |
| this); |
| Shell::Get()->session_controller()->RemoveObserver(this); |
| } |
| |
| // Adjusts the layout for a narrower appearance, using a shorter label for |
| // the button. |
| void SetNarrowLayout(bool narrow) { |
| narrow_layout_ = narrow; |
| Update(); |
| } |
| |
| private: |
| // EnterpriseDomainObserver: |
| void OnDeviceEnterpriseInfoChanged() override { Update(); } |
| void OnEnterpriseAccountDomainChanged() override { Update(); } |
| |
| // SessionObserver: |
| void OnLoginStatusChanged(LoginStatus status) override { Update(); } |
| |
| // Updates the view visibility and displayed string. |
| void Update() { |
| EnterpriseDomainModel* model = |
| Shell::Get()->system_tray_model()->enterprise_domain(); |
| SessionControllerImpl* session_controller = |
| Shell::Get()->session_controller(); |
| const std::string enterprise_domain_manager = |
| model->enterprise_domain_manager(); |
| const std::string account_domain_manager = model->account_domain_manager(); |
| |
| const bool visible = session_controller->ShouldDisplayManagedUI() || |
| !enterprise_domain_manager.empty() || |
| !account_domain_manager.empty(); |
| SetVisible(visible); |
| |
| if (!visible) { |
| return; |
| } |
| |
| // Display device and user management based on the enterprise enrollment |
| // state. |
| std::u16string managed_string; |
| if (enterprise_domain_manager.empty() && account_domain_manager.empty()) { |
| managed_string = l10n_util::GetStringFUTF16( |
| IDS_ASH_ENTERPRISE_DEVICE_MANAGED, ui::GetChromeOSDeviceName()); |
| } else if (!enterprise_domain_manager.empty() && |
| !account_domain_manager.empty() && |
| enterprise_domain_manager != account_domain_manager) { |
| managed_string = l10n_util::GetStringFUTF16( |
| IDS_ASH_SHORT_MANAGED_BY_MULTIPLE, |
| base::UTF8ToUTF16(enterprise_domain_manager), |
| base::UTF8ToUTF16(account_domain_manager)); |
| } else { |
| const std::u16string display_domain_manager = |
| enterprise_domain_manager.empty() |
| ? base::UTF8ToUTF16(account_domain_manager) |
| : base::UTF8ToUTF16(enterprise_domain_manager); |
| managed_string = l10n_util::GetStringFUTF16(IDS_ASH_SHORT_MANAGED_BY, |
| display_domain_manager); |
| // Narrow layout uses the string "Managed" and wide layout uses the full |
| // string "Managed by example.com". |
| label()->SetText(narrow_layout_ |
| ? l10n_util::GetStringUTF16( |
| IDS_ASH_ENTERPRISE_DEVICE_MANAGED_SHORT) |
| : managed_string); |
| } |
| SetTooltipText(managed_string); |
| } |
| |
| // See SetNarrowLayout(). |
| bool narrow_layout_ = false; |
| }; |
| |
| BEGIN_METADATA(QuickSettingsHeader, EnterpriseManagedView) |
| END_METADATA |
| |
| QuickSettingsHeader::QuickSettingsHeader( |
| UnifiedSystemTrayController* controller) { |
| SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, kHeaderPadding, |
| kButtonSpacing)); |
| |
| enterprise_managed_view_ = |
| AddChildView(std::make_unique<EnterpriseManagedView>(controller)); |
| |
| // A view that shows whether the user is supervised or a child. |
| supervised_view_ = AddChildView(std::make_unique<ManagedStateView>( |
| base::BindRepeating(&ShowAccountSettings), |
| IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL, GetSupervisedUserIcon())); |
| supervised_view_->SetID(VIEW_ID_QS_SUPERVISED_BUTTON); |
| const bool visible = |
| Shell::Get()->system_tray_model()->IsInUserChildSession(); |
| supervised_view_->SetVisible(visible); |
| if (visible) { |
| supervised_view_->SetTooltipText(GetSupervisedUserMessage()); |
| } |
| |
| const bool is_active_state = |
| Shell::Get()->session_controller()->GetSessionState() == |
| session_manager::SessionState::ACTIVE; |
| if (is_active_state) { |
| if (Shell::Get()->system_tray_model()->update_model()->show_eol_notice()) { |
| eol_notice_ = |
| AddChildView(std::make_unique<EolNoticeQuickSettingsView>()); |
| } else if (Shell::Get() |
| ->system_tray_model() |
| ->update_model() |
| ->show_extended_updates_notice()) { |
| extended_updates_notice_ = AddChildView( |
| std::make_unique<ExtendedUpdatesNoticeQuickSettingsView>()); |
| } |
| } |
| |
| // If the release track is not "stable" then show the channel indicator UI. |
| auto channel = Shell::Get()->shell_delegate()->GetChannel(); |
| if (channel_indicator_utils::IsDisplayableChannel(channel) && !eol_notice_ && |
| !extended_updates_notice_) { |
| channel_view_ = |
| AddChildView(std::make_unique<ChannelIndicatorQuickSettingsView>( |
| channel, is_active_state && Shell::Get() |
| ->system_tray_model() |
| ->client() |
| ->IsUserFeedbackEnabled())); |
| } |
| |
| UpdateVisibilityAndLayout(); |
| } |
| |
| QuickSettingsHeader::~QuickSettingsHeader() = default; |
| |
| void QuickSettingsHeader::ChildVisibilityChanged(views::View* child) { |
| UpdateVisibilityAndLayout(); |
| } |
| |
| views::View* QuickSettingsHeader::GetManagedButtonForTest() { |
| return enterprise_managed_view_; |
| } |
| |
| views::View* QuickSettingsHeader::GetSupervisedButtonForTest() { |
| return supervised_view_; |
| } |
| |
| views::Label* QuickSettingsHeader::GetManagedButtonLabelForTest() { |
| return enterprise_managed_view_->label(); |
| } |
| |
| views::Label* QuickSettingsHeader::GetSupervisedButtonLabelForTest() { |
| return supervised_view_->label(); |
| } |
| |
| views::View* QuickSettingsHeader::GetExtendedUpdatesViewForTest() { |
| return extended_updates_notice_; |
| } |
| |
| void QuickSettingsHeader::UpdateVisibilityAndLayout() { |
| // The managed view and the supervised view are never shown together. |
| DCHECK(!enterprise_managed_view_->GetVisible() || |
| !supervised_view_->GetVisible()); |
| |
| // The notice views are never shown together. |
| DCHECK(!!channel_view_ + !!eol_notice_ + !!extended_updates_notice_ <= 1); |
| |
| // Make `this` view visible if a child is visible. |
| bool managed_view_visible = |
| enterprise_managed_view_->GetVisible() || supervised_view_->GetVisible(); |
| bool notice_view_visible = |
| channel_view_ || eol_notice_ || extended_updates_notice_; |
| |
| SetVisible(managed_view_visible || notice_view_visible); |
| |
| // Update button sizes for one column vs. two columns. |
| bool two_columns = managed_view_visible && notice_view_visible; |
| gfx::Size size = two_columns ? kNarrowButtonSize : kWideButtonSize; |
| enterprise_managed_view_->SetPreferredSize(size); |
| supervised_view_->SetPreferredSize(size); |
| if (channel_view_) { |
| channel_view_->SetPreferredSize(size); |
| } |
| if (eol_notice_) { |
| eol_notice_->SetPreferredSize(size); |
| } |
| if (extended_updates_notice_) { |
| extended_updates_notice_->SetPreferredSize(size); |
| } |
| |
| // Use custom narrow layouts when two columns are showing. |
| enterprise_managed_view_->SetNarrowLayout(two_columns); |
| if (channel_view_) { |
| channel_view_->SetNarrowLayout(two_columns); |
| } |
| if (eol_notice_) { |
| eol_notice_->SetNarrowLayout(two_columns); |
| } |
| if (extended_updates_notice_) { |
| extended_updates_notice_->SetNarrowLayout(two_columns); |
| } |
| } |
| |
| // static |
| void QuickSettingsHeader::ShowEnterpriseInfo( |
| UnifiedSystemTrayController* controller, |
| bool show_management_disclosure_dialog, |
| bool is_user_session_blocked, |
| bool has_enterprise_domain_manager) { |
| quick_settings_metrics_util::RecordQsButtonActivated( |
| QsButtonCatalogName::kManagedButton); |
| // Show the new disclosure when on the login/lock screen and feature is |
| // enabled. |
| if (show_management_disclosure_dialog && is_user_session_blocked && |
| has_enterprise_domain_manager) { |
| LockScreen::Get()->ShowManagementDisclosureDialog(); |
| } else { |
| controller->HandleEnterpriseInfoAction(); |
| } |
| } |
| |
| BEGIN_METADATA(QuickSettingsHeader) |
| END_METADATA |
| |
| } // namespace ash |