| // 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/unified_system_tray.h" |
| |
| #include "ash/accessibility/accessibility_controller_impl.h" |
| #include "ash/focus_cycler.h" |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/message_center/ash_message_popup_collection.h" |
| #include "ash/system/message_center/message_center_ui_controller.h" |
| #include "ash/system/message_center/message_center_ui_delegate.h" |
| #include "ash/system/message_center/unified_message_center_bubble.h" |
| #include "ash/system/model/clock_model.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/network/network_tray_view.h" |
| #include "ash/system/power/tray_power.h" |
| #include "ash/system/privacy_screen/privacy_screen_toast_controller.h" |
| #include "ash/system/time/time_tray_item_view.h" |
| #include "ash/system/time/time_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_container.h" |
| #include "ash/system/unified/camera_mic_tray_item_view.h" |
| #include "ash/system/unified/current_locale_view.h" |
| #include "ash/system/unified/ime_mode_view.h" |
| #include "ash/system/unified/managed_device_tray_item_view.h" |
| #include "ash/system/unified/notification_counter_view.h" |
| #include "ash/system/unified/notification_icons_controller.h" |
| #include "ash/system/unified/unified_slider_bubble_controller.h" |
| #include "ash/system/unified/unified_system_tray_bubble.h" |
| #include "ash/system/unified/unified_system_tray_model.h" |
| #include "ash/system/unified/unified_system_tray_view.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/message_center/message_center.h" |
| |
| namespace ash { |
| |
| class UnifiedSystemTray::UiDelegate : public MessageCenterUiDelegate { |
| public: |
| explicit UiDelegate(UnifiedSystemTray* owner); |
| ~UiDelegate() override; |
| |
| // MessageCenterUiDelegate: |
| void OnMessageCenterContentsChanged() override; |
| bool ShowPopups() override; |
| void HidePopups() override; |
| bool ShowMessageCenter() override; |
| void HideMessageCenter() override; |
| |
| MessageCenterUiController* ui_controller() { return ui_controller_.get(); } |
| |
| void SetTrayBubbleHeight(int height) { |
| message_popup_collection_->SetTrayBubbleHeight(height); |
| } |
| message_center::MessagePopupView* GetPopupViewForNotificationID( |
| const std::string& notification_id) { |
| return message_popup_collection_->GetPopupViewForNotificationID( |
| notification_id); |
| } |
| |
| private: |
| std::unique_ptr<MessageCenterUiController> ui_controller_; |
| std::unique_ptr<AshMessagePopupCollection> message_popup_collection_; |
| |
| UnifiedSystemTray* const owner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UiDelegate); |
| }; |
| |
| const base::TimeDelta UnifiedSystemTray::kNotificationCountUpdateDelay = |
| base::TimeDelta::FromMilliseconds(100); |
| |
| UnifiedSystemTray::UiDelegate::UiDelegate(UnifiedSystemTray* owner) |
| : owner_(owner) { |
| ui_controller_ = std::make_unique<MessageCenterUiController>(this); |
| ui_controller_->set_hide_on_last_notification(false); |
| message_popup_collection_ = |
| std::make_unique<AshMessagePopupCollection>(owner->shelf()); |
| display::Screen* screen = display::Screen::GetScreen(); |
| message_popup_collection_->StartObserving( |
| screen, screen->GetDisplayNearestWindow( |
| owner->shelf()->GetStatusAreaWidget()->GetNativeWindow())); |
| } |
| |
| UnifiedSystemTray::UiDelegate::~UiDelegate() = default; |
| |
| void UnifiedSystemTray::UiDelegate::OnMessageCenterContentsChanged() { |
| owner_->UpdateNotificationInternal(); |
| } |
| |
| bool UnifiedSystemTray::UiDelegate::ShowPopups() { |
| if (owner_->IsBubbleShown()) |
| return false; |
| return true; |
| } |
| |
| void UnifiedSystemTray::UiDelegate::HidePopups() { |
| message_popup_collection_->SetTrayBubbleHeight(0); |
| } |
| |
| bool UnifiedSystemTray::UiDelegate::ShowMessageCenter() { |
| if (owner_->IsBubbleShown()) |
| return false; |
| |
| owner_->ShowBubbleInternal(); |
| return true; |
| } |
| |
| void UnifiedSystemTray::UiDelegate::HideMessageCenter() {} |
| |
| UnifiedSystemTray::UnifiedSystemTray(Shelf* shelf) |
| : TrayBackgroundView(shelf), |
| ui_delegate_(std::make_unique<UiDelegate>(this)), |
| model_(std::make_unique<UnifiedSystemTrayModel>(shelf)), |
| slider_bubble_controller_( |
| std::make_unique<UnifiedSliderBubbleController>(this)), |
| privacy_screen_toast_controller_( |
| std::make_unique<PrivacyScreenToastController>(this)), |
| notification_icons_controller_( |
| std::make_unique<NotificationIconsController>(this)), |
| current_locale_view_(new CurrentLocaleView(shelf)), |
| ime_mode_view_(new ImeModeView(shelf)), |
| managed_device_view_(new ManagedDeviceTrayItemView(shelf)), |
| camera_view_( |
| new CameraMicTrayItemView(shelf, |
| CameraMicTrayItemView::Type::kCamera)), |
| mic_view_( |
| new CameraMicTrayItemView(shelf, CameraMicTrayItemView::Type::kMic)), |
| time_view_(new tray::TimeTrayItemView(shelf, model())) { |
| tray_container()->SetMargin( |
| kUnifiedTrayContentPadding - |
| ShelfConfig::Get()->status_area_hit_region_padding(), |
| 0); |
| |
| notification_icons_controller_->AddNotificationTrayItems(tray_container()); |
| for (TrayItemView* tray_item : notification_icons_controller_->tray_items()) |
| tray_items_.push_back(tray_item); |
| tray_items_.push_back( |
| notification_icons_controller_->notification_counter_view()); |
| tray_items_.push_back(notification_icons_controller_->quiet_mode_view()); |
| AddTrayItemToContainer(current_locale_view_); |
| AddTrayItemToContainer(ime_mode_view_); |
| AddTrayItemToContainer(managed_device_view_); |
| AddTrayItemToContainer(camera_view_); |
| AddTrayItemToContainer(mic_view_); |
| |
| if (features::IsSeparateNetworkIconsEnabled()) { |
| network_tray_view_ = |
| new tray::NetworkTrayView(shelf, ActiveNetworkIcon::Type::kPrimary); |
| AddTrayItemToContainer( |
| new tray::NetworkTrayView(shelf, ActiveNetworkIcon::Type::kCellular)); |
| } else { |
| network_tray_view_ = |
| new tray::NetworkTrayView(shelf, ActiveNetworkIcon::Type::kSingle); |
| } |
| AddTrayItemToContainer(network_tray_view_); |
| AddTrayItemToContainer(new tray::PowerTrayView(shelf)); |
| AddTrayItemToContainer(time_view_); |
| |
| set_separator_visibility(false); |
| |
| ShelfConfig::Get()->AddObserver(this); |
| } |
| |
| UnifiedSystemTray::~UnifiedSystemTray() { |
| ShelfConfig::Get()->RemoveObserver(this); |
| |
| message_center_bubble_.reset(); |
| // Close bubble immediately when the bubble is closed on dtor. |
| if (bubble_) |
| bubble_->CloseNow(); |
| bubble_.reset(); |
| |
| // Reset the view to remove its dependency from |model_|, since this view is |
| // destructed after |model_|. |
| time_view_->Reset(); |
| } |
| |
| bool UnifiedSystemTray::IsBubbleShown() const { |
| return !!bubble_; |
| } |
| |
| bool UnifiedSystemTray::IsSliderBubbleShown() const { |
| return slider_bubble_controller_->IsBubbleShown(); |
| } |
| |
| bool UnifiedSystemTray::IsMessageCenterBubbleShown() const { |
| if (message_center_bubble_) |
| return message_center_bubble_->IsMessageCenterVisible(); |
| |
| return false; |
| } |
| |
| bool UnifiedSystemTray::IsBubbleActive() const { |
| return bubble_ && bubble_->IsBubbleActive(); |
| } |
| |
| void UnifiedSystemTray::ActivateBubble() { |
| if (bubble_) |
| bubble_->GetBubbleWidget()->Activate(); |
| } |
| |
| void UnifiedSystemTray::CloseSecondaryBubbles() { |
| slider_bubble_controller_->CloseBubble(); |
| privacy_screen_toast_controller_->HideToast(); |
| } |
| |
| void UnifiedSystemTray::CollapseMessageCenter() { |
| if (message_center_bubble_) |
| message_center_bubble_->CollapseMessageCenter(); |
| } |
| |
| void UnifiedSystemTray::ExpandMessageCenter() { |
| if (message_center_bubble_) |
| message_center_bubble_->ExpandMessageCenter(); |
| } |
| |
| void UnifiedSystemTray::EnsureQuickSettingsCollapsed(bool animate) { |
| if (!bubble_) |
| return; |
| |
| if (animate) |
| bubble_->EnsureCollapsed(); |
| else |
| bubble_->CollapseWithoutAnimating(); |
| } |
| |
| void UnifiedSystemTray::EnsureBubbleExpanded() { |
| if (bubble_) |
| bubble_->EnsureExpanded(); |
| } |
| |
| void UnifiedSystemTray::ShowVolumeSliderBubble() { |
| slider_bubble_controller_->ShowBubble( |
| UnifiedSliderBubbleController::SLIDER_TYPE_VOLUME); |
| } |
| |
| void UnifiedSystemTray::ShowAudioDetailedViewBubble() { |
| // The settings menu bubble gains focus when |show_by_click| is true. |
| ShowBubble(); |
| bubble_->ShowAudioDetailedView(); |
| } |
| |
| void UnifiedSystemTray::ShowNetworkDetailedViewBubble() { |
| ShowBubble(); |
| bubble_->ShowNetworkDetailedView(true /* force */); |
| } |
| |
| void UnifiedSystemTray::SetTrayBubbleHeight(int height) { |
| ui_delegate_->SetTrayBubbleHeight(height); |
| } |
| |
| void UnifiedSystemTray::FocusFirstNotification() { |
| FocusMessageCenter(false /*reverse*/); |
| |
| // Do not focus an individual element in quick settings if chrome vox is |
| // enabled |
| if (!ShouldEnableExtraKeyboardAccessibility()) |
| message_center_bubble()->FocusFirstNotification(); |
| } |
| |
| bool UnifiedSystemTray::FocusMessageCenter(bool reverse) { |
| if (!IsMessageCenterBubbleShown()) |
| return false; |
| |
| views::Widget* message_center_widget = |
| message_center_bubble_->GetBubbleWidget(); |
| message_center_widget->widget_delegate()->SetCanActivate(true); |
| |
| Shell::Get()->focus_cycler()->FocusWidget(message_center_widget); |
| |
| // Focus an individual element in the message center if chrome vox is |
| // disabled. Otherwise, ensure the message center is expanded. |
| if (!ShouldEnableExtraKeyboardAccessibility()) { |
| message_center_bubble_->FocusEntered(reverse); |
| } else if (message_center_bubble_->IsMessageCenterCollapsed()) { |
| ExpandMessageCenter(); |
| EnsureQuickSettingsCollapsed(true /*animate*/); |
| } |
| return true; |
| } |
| |
| bool UnifiedSystemTray::FocusQuickSettings(bool reverse) { |
| if (!IsBubbleShown()) |
| return false; |
| |
| views::Widget* quick_settings_widget = bubble_->GetBubbleWidget(); |
| quick_settings_widget->widget_delegate()->SetCanActivate(true); |
| |
| Shell::Get()->focus_cycler()->FocusWidget(quick_settings_widget); |
| |
| // Focus an individual element in quick settings if chrome vox is |
| // disabled. Otherwise, ensure quick settings is expanded. |
| if (!ShouldEnableExtraKeyboardAccessibility()) |
| bubble_->FocusEntered(reverse); |
| else |
| EnsureBubbleExpanded(); |
| |
| return true; |
| } |
| |
| bool UnifiedSystemTray::IsQuickSettingsExplicitlyExpanded() const { |
| return model_->IsExplicitlyExpanded(); |
| } |
| |
| gfx::Rect UnifiedSystemTray::GetBubbleBoundsInScreen() const { |
| return bubble_ ? bubble_->GetBoundsInScreen() : gfx::Rect(); |
| } |
| |
| void UnifiedSystemTray::MaybeRecordFirstInteraction(FirstInteractionType type) { |
| if (first_interaction_recorded_) |
| return; |
| first_interaction_recorded_ = true; |
| |
| UMA_HISTOGRAM_ENUMERATION("ChromeOS.SystemTray.FirstInteraction", type, |
| FirstInteractionType::kMaxValue); |
| } |
| |
| void UnifiedSystemTray::UpdateAfterLoginStatusChange() { |
| SetVisiblePreferred(true); |
| PreferredSizeChanged(); |
| } |
| |
| bool UnifiedSystemTray::ShouldEnableExtraKeyboardAccessibility() { |
| return Shell::Get()->accessibility_controller()->spoken_feedback().enabled(); |
| } |
| |
| views::Widget* UnifiedSystemTray::GetBubbleWidget() const { |
| return bubble_ ? bubble_->GetBubbleWidget() : nullptr; |
| } |
| |
| const char* UnifiedSystemTray::GetClassName() const { |
| return "UnifiedSystemTray"; |
| } |
| |
| void UnifiedSystemTray::OnShelfConfigUpdated() { |
| // Ensure the margin is updated correctly depending on whether dense shelf |
| // is currently shown or not. |
| tray_container()->SetMargin( |
| kUnifiedTrayContentPadding - |
| ShelfConfig::Get()->status_area_hit_region_padding(), |
| 0); |
| } |
| |
| void UnifiedSystemTray::SetTrayEnabled(bool enabled) { |
| // We should close bubble at this point. If it remains opened and interactive, |
| // it can be dangerous (http://crbug.com/497080). |
| if (!enabled && bubble_) |
| CloseBubble(); |
| |
| SetEnabled(enabled); |
| } |
| |
| void UnifiedSystemTray::SetTargetNotification( |
| const std::string& notification_id) { |
| model_->SetTargetNotification(notification_id); |
| } |
| |
| void UnifiedSystemTray::ShowBubble() { |
| // ShowBubbleInternal will be called from UiDelegate. |
| if (!bubble_) |
| ui_delegate_->ui_controller()->ShowMessageCenterBubble(); |
| } |
| |
| void UnifiedSystemTray::CloseBubble() { |
| // HideMessageCenterBubbleInternal will be called from UiDelegate. |
| ui_delegate_->ui_controller()->HideMessageCenterBubble(); |
| |
| HideBubbleInternal(); |
| } |
| |
| std::u16string UnifiedSystemTray::GetAccessibleNameForBubble() { |
| if (IsBubbleShown()) |
| return GetAccessibleNameForQuickSettingsBubble(); |
| else |
| return GetAccessibleNameForTray(); |
| } |
| |
| std::u16string UnifiedSystemTray::GetAccessibleNameForQuickSettingsBubble() { |
| if (bubble_->unified_view()->IsDetailedViewShown()) |
| return bubble_->unified_view()->GetDetailedViewAccessibleName(); |
| |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_QUICK_SETTINGS_BUBBLE_ACCESSIBLE_DESCRIPTION); |
| } |
| |
| void UnifiedSystemTray::HandleLocaleChange() { |
| for (TrayItemView* item : tray_items_) |
| item->HandleLocaleChange(); |
| } |
| |
| std::u16string UnifiedSystemTray::GetAccessibleNameForTray() { |
| std::u16string time = base::TimeFormatTimeOfDayWithHourClockType( |
| base::Time::Now(), |
| Shell::Get()->system_tray_model()->clock()->hour_clock_type(), |
| base::kKeepAmPm); |
| std::u16string battery = PowerStatus::Get()->GetAccessibleNameString(false); |
| std::vector<std::u16string> status = {time, battery}; |
| |
| status.push_back(network_tray_view_->GetVisible() |
| ? network_tray_view_->GetAccessibleNameString() |
| : base::EmptyString16()); |
| status.push_back(mic_view_->GetVisible() |
| ? mic_view_->GetAccessibleNameString() |
| : base::EmptyString16()); |
| status.push_back(camera_view_->GetVisible() |
| ? camera_view_->GetAccessibleNameString() |
| : base::EmptyString16()); |
| status.push_back(notification_icons_controller_->GetAccessibleNameString()); |
| status.push_back(ime_mode_view_->GetVisible() |
| ? ime_mode_view_->label()->GetAccessibleNameString() |
| : base::EmptyString16()); |
| status.push_back( |
| current_locale_view_->GetVisible() |
| ? current_locale_view_->label()->GetAccessibleNameString() |
| : base::EmptyString16()); |
| |
| return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_DESCRIPTION, |
| status, nullptr); |
| } |
| |
| void UnifiedSystemTray::HideBubble(const TrayBubbleView* bubble_view) { |
| CloseBubble(); |
| } |
| |
| void UnifiedSystemTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {} |
| |
| void UnifiedSystemTray::ClickedOutsideBubble() { |
| CloseBubble(); |
| } |
| |
| void UnifiedSystemTray::UpdateLayout() { |
| TrayBackgroundView::UpdateLayout(); |
| time_view_->UpdateAlignmentForShelf(shelf()); |
| } |
| |
| void UnifiedSystemTray::ShowBubbleInternal() { |
| // Never show System Tray bubble in kiosk app mode. |
| if (Shell::Get()->session_controller()->IsRunningInAppMode()) |
| return; |
| |
| CloseSecondaryBubbles(); |
| |
| bubble_ = std::make_unique<UnifiedSystemTrayBubble>(this); |
| |
| message_center_bubble_ = std::make_unique<UnifiedMessageCenterBubble>(this); |
| message_center_bubble_->ShowBubble(); |
| |
| if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) |
| ActivateBubble(); |
| |
| first_interaction_recorded_ = false; |
| |
| SetIsActive(true); |
| } |
| |
| void UnifiedSystemTray::HideBubbleInternal() { |
| message_center_bubble_.reset(); |
| bubble_.reset(); |
| SetIsActive(false); |
| } |
| |
| void UnifiedSystemTray::UpdateNotificationInternal() { |
| // Limit update frequency in order to avoid flashing when 2 updates are |
| // incoming in a very short period of time. It happens when ARC++ apps |
| // creating bundled notifications. |
| if (!timer_.IsRunning()) { |
| timer_.Start(FROM_HERE, kNotificationCountUpdateDelay, this, |
| &UnifiedSystemTray::UpdateNotificationAfterDelay); |
| } |
| } |
| |
| void UnifiedSystemTray::UpdateNotificationAfterDelay() { |
| notification_icons_controller_->UpdateNotificationIndicators(); |
| } |
| |
| message_center::MessagePopupView* |
| UnifiedSystemTray::GetPopupViewForNotificationID( |
| const std::string& notification_id) { |
| return ui_delegate_->GetPopupViewForNotificationID(notification_id); |
| } |
| |
| void UnifiedSystemTray::AddTrayItemToContainer(TrayItemView* tray_item) { |
| tray_items_.push_back(tray_item); |
| tray_container()->AddChildView(tray_item); |
| } |
| |
| } // namespace ash |