| // Copyright (c) 2012 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/tray/system_tray.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <vector> |
| |
| #include "ash/accessibility/accessibility_controller.h" |
| #include "ash/login_status.h" |
| #include "ash/metrics/user_metrics_recorder.h" |
| #include "ash/public/cpp/ash_switches.h" |
| #include "ash/public/cpp/config.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/shell_port.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/audio/tray_audio.h" |
| #include "ash/system/bluetooth/tray_bluetooth.h" |
| #include "ash/system/brightness/tray_brightness.h" |
| #include "ash/system/cast/tray_cast.h" |
| #include "ash/system/date/tray_system_info.h" |
| #include "ash/system/display_scale/tray_scale.h" |
| #include "ash/system/enterprise/tray_enterprise.h" |
| #include "ash/system/ime/tray_ime_chromeos.h" |
| #include "ash/system/keyboard_brightness/tray_keyboard_brightness.h" |
| #include "ash/system/media_security/multi_profile_media_tray_item.h" |
| #include "ash/system/network/tray_network.h" |
| #include "ash/system/network/tray_vpn.h" |
| #include "ash/system/night_light/tray_night_light.h" |
| #include "ash/system/power/power_status.h" |
| #include "ash/system/power/tray_power.h" |
| #include "ash/system/rotation/tray_rotation_lock.h" |
| #include "ash/system/screen_security/screen_capture_tray_item.h" |
| #include "ash/system/screen_security/screen_share_tray_item.h" |
| #include "ash/system/screen_security/screen_tray_item.h" |
| #include "ash/system/session/tray_session_length_limit.h" |
| #include "ash/system/supervised/tray_supervised_user.h" |
| #include "ash/system/tiles/tray_tiles.h" |
| #include "ash/system/tray/system_tray_controller.h" |
| #include "ash/system/tray/system_tray_item.h" |
| #include "ash/system/tray/tray_bubble_wrapper.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_container.h" |
| #include "ash/system/tray_accessibility.h" |
| #include "ash/system/tray_caps_lock.h" |
| #include "ash/system/tray_tracing.h" |
| #include "ash/system/update/tray_update.h" |
| #include "ash/system/user/tray_user.h" |
| #include "ash/system/web_notification/web_notification_tray.h" |
| #include "ash/wm/container_finder.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/widget_finder.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/timer/timer.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/skia_util.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/message_center_constants.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/message_box_view.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/dialog_delegate.h" |
| #include "ui/wm/core/window_util.h" |
| #include "ui/wm/public/activation_change_observer.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| using views::TrayBubbleView; |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Dialog that confirms the user wants to stop screen share/cast. Calls a |
| // callback with the result. |
| class CancelCastingDialog : public views::DialogDelegateView { |
| public: |
| explicit CancelCastingDialog(base::OnceCallback<void(bool)> callback) |
| : callback_(std::move(callback)) { |
| AddChildView(new views::MessageBoxView(views::MessageBoxView::InitParams( |
| l10n_util::GetStringUTF16(IDS_DESKTOP_CASTING_ACTIVE_MESSAGE)))); |
| SetLayoutManager(std::make_unique<views::FillLayout>()); |
| } |
| ~CancelCastingDialog() override = default; |
| |
| base::string16 GetWindowTitle() const override { |
| return l10n_util::GetStringUTF16(IDS_DESKTOP_CASTING_ACTIVE_TITLE); |
| } |
| |
| int GetDialogButtons() const override { |
| return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL; |
| } |
| |
| bool Cancel() override { |
| std::move(callback_).Run(false); |
| return true; |
| } |
| |
| bool Accept() override { |
| // Stop screen sharing and capturing. |
| SystemTray* system_tray = Shell::Get()->GetPrimarySystemTray(); |
| if (system_tray->GetScreenShareItem()->is_started()) |
| system_tray->GetScreenShareItem()->Stop(); |
| if (system_tray->GetScreenCaptureItem()->is_started()) |
| system_tray->GetScreenCaptureItem()->Stop(); |
| |
| std::move(callback_).Run(true); |
| return true; |
| } |
| |
| private: |
| base::OnceCallback<void(bool)> callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CancelCastingDialog); |
| }; |
| |
| // A tray item that just reserves space in the tray. |
| class PaddingTrayItem : public SystemTrayItem { |
| public: |
| PaddingTrayItem() : SystemTrayItem(nullptr, UMA_NOT_RECORDED) {} |
| ~PaddingTrayItem() override = default; |
| |
| // SystemTrayItem: |
| views::View* CreateTrayView(LoginStatus status) override { |
| auto* padding = new views::View(); |
| // The other tray items already have some padding baked in so we have to |
| // subtract that off. |
| constexpr int side = kTrayEdgePadding - kTrayImageItemPadding; |
| padding->SetPreferredSize(gfx::Size(side, side)); |
| return padding; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(PaddingTrayItem); |
| }; |
| |
| } // namespace |
| |
| // Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper |
| // instances for a bubble. |
| |
| class SystemBubbleWrapper { |
| public: |
| // Takes ownership of |bubble|. |
| SystemBubbleWrapper() = default; |
| |
| ~SystemBubbleWrapper() { |
| if (system_tray_view()) |
| system_tray_view()->DestroyItemViews(); |
| } |
| |
| // Initializes the bubble view and creates |bubble_wrapper_|. |
| void InitView(SystemTray* tray, |
| views::View* anchor, |
| const gfx::Insets& anchor_insets, |
| const std::vector<ash::SystemTrayItem*>& items, |
| SystemTrayView::SystemTrayType system_tray_type, |
| TrayBubbleView::InitParams* init_params, |
| bool is_persistent) { |
| DCHECK(anchor); |
| |
| bubble_ = std::make_unique<SystemTrayBubble>(tray); |
| is_persistent_ = is_persistent; |
| |
| const LoginStatus login_status = |
| Shell::Get()->session_controller()->login_status(); |
| bubble_->InitView(anchor, items, system_tray_type, login_status, |
| init_params); |
| bubble_->bubble_view()->set_anchor_view_insets(anchor_insets); |
| bubble_wrapper_ = std::make_unique<TrayBubbleWrapper>( |
| tray, bubble_->bubble_view(), is_persistent); |
| } |
| |
| // Convenience accessors: |
| SystemTrayBubble* bubble() { return bubble_.get(); } |
| SystemTrayView* system_tray_view() { return bubble_->system_tray_view(); } |
| SystemTrayView::SystemTrayType system_tray_type() const { |
| return bubble_->system_tray_view()->system_tray_type(); |
| } |
| TrayBubbleView* bubble_view() { return bubble_->bubble_view(); } |
| bool is_persistent() const { return is_persistent_; } |
| |
| private: |
| std::unique_ptr<SystemTrayBubble> bubble_; |
| std::unique_ptr<TrayBubbleWrapper> bubble_wrapper_; |
| bool is_persistent_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper); |
| }; |
| |
| // SystemTray |
| |
| SystemTray::SystemTray(Shelf* shelf) : TrayBackgroundView(shelf) { |
| SetInkDropMode(InkDropMode::ON); |
| |
| // Since user avatar is on the right hand side of System tray of a |
| // horizontal shelf and that is sufficient to indicate separation, no |
| // separator is required. |
| set_separator_visibility(false); |
| } |
| |
| SystemTray::~SystemTray() { |
| // On shutdown, views in the bubble might be destroyed after this. Clear the |
| // list of SystemTrayItems in the SystemTrayView, since they are owned by |
| // this class. |
| if (system_bubble_ && system_bubble_->system_tray_view()) |
| system_bubble_->system_tray_view()->set_items( |
| std::vector<SystemTrayItem*>()); |
| |
| // Destroy any child views that might have back pointers before ~View(). |
| system_bubble_.reset(); |
| for (const auto& item : items_) |
| item->OnTrayViewDestroyed(); |
| } |
| |
| void SystemTray::InitializeTrayItems( |
| WebNotificationTray* web_notification_tray) { |
| DCHECK(web_notification_tray); |
| web_notification_tray_ = web_notification_tray; |
| TrayBackgroundView::Initialize(); |
| CreateItems(); |
| } |
| |
| void SystemTray::Shutdown() { |
| DCHECK(web_notification_tray_); |
| web_notification_tray_ = nullptr; |
| } |
| |
| void SystemTray::CreateItems() { |
| AddTrayItem(std::make_unique<TrayUser>(this)); |
| |
| // Crucially, this trailing padding has to be inside the user item(s). |
| // Otherwise it could be a main axis margin on the tray's box layout. |
| AddTrayItem(std::make_unique<PaddingTrayItem>()); |
| |
| tray_session_length_limit_ = new TraySessionLengthLimit(this); |
| AddTrayItem(base::WrapUnique(tray_session_length_limit_)); |
| tray_enterprise_ = new TrayEnterprise(this); |
| AddTrayItem(base::WrapUnique(tray_enterprise_)); |
| tray_supervised_user_ = new TraySupervisedUser(this); |
| AddTrayItem(base::WrapUnique(tray_supervised_user_)); |
| AddTrayItem(std::make_unique<TrayIME>(this)); |
| tray_accessibility_ = new TrayAccessibility(this); |
| AddTrayItem(base::WrapUnique(tray_accessibility_)); |
| tray_tracing_ = new TrayTracing(this); |
| AddTrayItem(base::WrapUnique(tray_tracing_)); |
| AddTrayItem( |
| std::make_unique<TrayPower>(this, message_center::MessageCenter::Get())); |
| tray_network_ = new TrayNetwork(this); |
| AddTrayItem(base::WrapUnique(tray_network_)); |
| AddTrayItem(std::make_unique<TrayVPN>(this)); |
| AddTrayItem(std::make_unique<TrayBluetooth>(this)); |
| tray_cast_ = new TrayCast(this); |
| AddTrayItem(base::WrapUnique(tray_cast_)); |
| screen_capture_tray_item_ = new ScreenCaptureTrayItem(this); |
| AddTrayItem(base::WrapUnique(screen_capture_tray_item_)); |
| screen_share_tray_item_ = new ScreenShareTrayItem(this); |
| AddTrayItem(base::WrapUnique(screen_share_tray_item_)); |
| AddTrayItem(std::make_unique<MultiProfileMediaTrayItem>(this)); |
| tray_audio_ = new TrayAudio(this); |
| AddTrayItem(base::WrapUnique(tray_audio_)); |
| tray_scale_ = new TrayScale(this); |
| AddTrayItem(base::WrapUnique(tray_scale_)); |
| AddTrayItem(std::make_unique<TrayBrightness>(this)); |
| AddTrayItem(std::make_unique<TrayKeyboardBrightness>(this)); |
| tray_caps_lock_ = new TrayCapsLock(this); |
| AddTrayItem(base::WrapUnique(tray_caps_lock_)); |
| if (switches::IsNightLightEnabled()) { |
| tray_night_light_ = new TrayNightLight(this); |
| AddTrayItem(base::WrapUnique(tray_night_light_)); |
| } |
| AddTrayItem(std::make_unique<TrayRotationLock>(this)); |
| tray_update_ = new TrayUpdate(this); |
| AddTrayItem(base::WrapUnique(tray_update_)); |
| tray_tiles_ = new TrayTiles(this); |
| AddTrayItem(base::WrapUnique(tray_tiles_)); |
| tray_system_info_ = new TraySystemInfo(this); |
| AddTrayItem(base::WrapUnique(tray_system_info_)); |
| // Leading padding. |
| AddTrayItem(std::make_unique<PaddingTrayItem>()); |
| } |
| |
| void SystemTray::AddTrayItem(std::unique_ptr<SystemTrayItem> item) { |
| SystemTrayItem* item_ptr = item.get(); |
| items_.push_back(std::move(item)); |
| |
| views::View* tray_item = item_ptr->CreateTrayView( |
| Shell::Get()->session_controller()->login_status()); |
| item_ptr->UpdateAfterShelfAlignmentChange(); |
| |
| if (tray_item) { |
| tray_container()->AddChildViewAt(tray_item, 0); |
| PreferredSizeChanged(); |
| } |
| } |
| |
| std::vector<SystemTrayItem*> SystemTray::GetTrayItems() const { |
| std::vector<SystemTrayItem*> result; |
| for (const auto& item : items_) |
| result.push_back(item.get()); |
| return result; |
| } |
| |
| void SystemTray::ShowDefaultView(BubbleCreationType creation_type, |
| bool show_by_click) { |
| if (creation_type != BUBBLE_USE_EXISTING) |
| Shell::Get()->metrics()->RecordUserMetricsAction( |
| UMA_STATUS_AREA_MENU_OPENED); |
| ShowItems(GetTrayItems(), false, creation_type, false, show_by_click); |
| } |
| |
| void SystemTray::ShowPersistentDefaultView() { |
| ShowItems(GetTrayItems(), false, BUBBLE_CREATE_NEW, true, false); |
| } |
| |
| void SystemTray::ShowDetailedView(SystemTrayItem* item, |
| int close_delay, |
| BubbleCreationType creation_type) { |
| std::vector<SystemTrayItem*> items; |
| // The detailed view with timeout means a UI to show the current system state, |
| // like the audio level or brightness. Such UI should behave as persistent and |
| // keep its own logic for the appearance. |
| bool persistent = (close_delay > 0 && creation_type == BUBBLE_CREATE_NEW); |
| items.push_back(item); |
| ShowItems(items, true, creation_type, persistent, false); |
| if (system_bubble_) |
| system_bubble_->bubble()->StartAutoCloseTimer(close_delay); |
| } |
| |
| void SystemTray::SetDetailedViewCloseDelay(int close_delay) { |
| if (HasSystemTrayType(SystemTrayView::SYSTEM_TRAY_TYPE_DETAILED)) |
| system_bubble_->bubble()->StartAutoCloseTimer(close_delay); |
| } |
| |
| void SystemTray::HideDetailedView(SystemTrayItem* item) { |
| if (item != detailed_item_) |
| return; |
| |
| DestroySystemBubble(); |
| } |
| |
| void SystemTray::UpdateAfterLoginStatusChange(LoginStatus login_status) { |
| DestroySystemBubble(); |
| |
| for (const auto& item : items_) |
| item->UpdateAfterLoginStatusChange(login_status); |
| |
| SetVisible(true); |
| PreferredSizeChanged(); |
| } |
| |
| void SystemTray::UpdateItemsAfterShelfAlignmentChange() { |
| for (const auto& item : items_) |
| item->UpdateAfterShelfAlignmentChange(); |
| } |
| |
| bool SystemTray::ShouldShowShelf() const { |
| return system_bubble_.get() && system_bubble_->bubble()->ShouldShowShelf(); |
| } |
| |
| bool SystemTray::HasSystemBubble() const { |
| return system_bubble_.get() != NULL; |
| } |
| |
| SystemTrayBubble* SystemTray::GetSystemBubble() { |
| if (!system_bubble_) |
| return NULL; |
| return system_bubble_->bubble(); |
| } |
| |
| bool SystemTray::IsSystemBubbleVisible() const { |
| return HasSystemBubble() && system_bubble_->bubble()->IsVisible(); |
| } |
| |
| views::View* SystemTray::GetHelpButtonView() const { |
| return tray_tiles_->GetHelpButtonView(); |
| } |
| |
| TrayAudio* SystemTray::GetTrayAudio() const { |
| return tray_audio_; |
| } |
| |
| void SystemTray::CanSwitchAwayFromActiveUser( |
| base::OnceCallback<void(bool)> callback) { |
| // If neither screen sharing nor capturing is going on we can immediately |
| // switch users. |
| if (!GetScreenShareItem()->is_started() && |
| !GetScreenCaptureItem()->is_started()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| views::DialogDelegate::CreateDialogWidget( |
| new CancelCastingDialog(std::move(callback)), |
| Shell::GetPrimaryRootWindow(), nullptr) |
| ->Show(); |
| } |
| |
| // Private methods. |
| |
| bool SystemTray::HasSystemTrayType(SystemTrayView::SystemTrayType type) { |
| return system_bubble_.get() && system_bubble_->system_tray_type() == type; |
| } |
| |
| void SystemTray::DestroySystemBubble() { |
| CloseSystemBubbleAndDeactivateSystemTray(); |
| detailed_item_ = NULL; |
| UpdateWebNotifications(); |
| } |
| |
| base::string16 SystemTray::GetAccessibleNameForTray() { |
| base::string16 time = GetAccessibleTimeString(base::Time::Now()); |
| base::string16 battery = PowerStatus::Get()->GetAccessibleNameString(false); |
| return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_DESCRIPTION, |
| time, battery); |
| } |
| |
| void SystemTray::ShowItems(const std::vector<SystemTrayItem*>& items, |
| bool detailed, |
| BubbleCreationType creation_type, |
| bool persistent, |
| bool show_by_click) { |
| // No system tray bubbles in kiosk app mode. |
| if (Shell::Get()->session_controller()->IsRunningInAppMode()) |
| return; |
| |
| // Destroy any existing bubble and create a new one. |
| SystemTrayView::SystemTrayType system_tray_type = |
| detailed ? SystemTrayView::SYSTEM_TRAY_TYPE_DETAILED |
| : SystemTrayView::SYSTEM_TRAY_TYPE_DEFAULT; |
| |
| if (system_bubble_.get() && creation_type == BUBBLE_USE_EXISTING) { |
| system_bubble_->bubble()->UpdateView(items, system_tray_type); |
| } else { |
| // Cleanup the existing bubble before showing a new one. Otherwise, it's |
| // possible to confuse the new system bubble with the old one during |
| // destruction, leading to subtle errors/crashes such as crbug.com/545166. |
| DestroySystemBubble(); |
| |
| // Remember if the menu is a single property (like e.g. volume) or the |
| // full tray menu. Note that in case of the |BUBBLE_USE_EXISTING| case |
| // above, |full_system_tray_menu_| does not get changed since the fact that |
| // the menu is full (or not) doesn't change even if a "single property" |
| // (like network) replaces most of the menu. |
| full_system_tray_menu_ = items.size() > 1; |
| |
| TrayBubbleView::InitParams init_params; |
| init_params.anchor_alignment = GetAnchorAlignment(); |
| init_params.min_width = kTrayMenuWidth; |
| init_params.max_width = kTrayMenuWidth; |
| // The bubble is not initially activatable, but will become activatable if |
| // the user presses Tab. For behavioral consistency with the non-activatable |
| // scenario, don't close on deactivation after Tab either. |
| init_params.close_on_deactivate = false; |
| init_params.show_by_click = show_by_click; |
| if (detailed) { |
| // This is the case where a volume control or brightness control bubble |
| // is created. |
| init_params.max_height = default_bubble_height_; |
| } else { |
| init_params.bg_color = kHeaderBackgroundColor; |
| } |
| |
| system_bubble_ = std::make_unique<SystemBubbleWrapper>(); |
| system_bubble_->InitView( |
| this, shelf()->GetSystemTrayAnchor()->GetBubbleAnchor(), |
| shelf()->GetSystemTrayAnchor()->GetBubbleAnchorInsets(), items, |
| system_tray_type, &init_params, persistent); |
| |
| // Record metrics for the system menu when the default view is invoked. |
| if (!detailed) |
| RecordSystemMenuMetrics(); |
| } |
| // Save height of default view for creating detailed views directly. |
| if (!detailed) |
| default_bubble_height_ = system_bubble_->bubble_view()->height(); |
| |
| if (detailed && items.size() > 0) |
| detailed_item_ = items[0]; |
| else |
| detailed_item_ = NULL; |
| |
| UpdateWebNotifications(); |
| shelf()->UpdateAutoHideState(); |
| |
| // When we show the system menu in our alternate shelf layout, we need to |
| // tint the background. |
| if (full_system_tray_menu_) |
| SetIsActive(true); |
| } |
| |
| void SystemTray::UpdateWebNotifications() { |
| TrayBubbleView* bubble_view = NULL; |
| if (system_bubble_) |
| bubble_view = system_bubble_->bubble_view(); |
| |
| int height = 0; |
| if (bubble_view) { |
| gfx::Rect work_area = display::Screen::GetScreen() |
| ->GetDisplayNearestWindow( |
| bubble_view->GetWidget()->GetNativeWindow()) |
| .work_area(); |
| height = |
| std::max(0, work_area.bottom() - bubble_view->GetBoundsInScreen().y()); |
| } |
| if (web_notification_tray_) |
| web_notification_tray_->SetTrayBubbleHeight(height); |
| } |
| |
| base::string16 SystemTray::GetAccessibleTimeString( |
| const base::Time& now) const { |
| base::HourClockType hour_type = |
| Shell::Get()->system_tray_controller()->hour_clock_type(); |
| return base::TimeFormatTimeOfDayWithHourClockType(now, hour_type, |
| base::kKeepAmPm); |
| } |
| |
| void SystemTray::UpdateAfterShelfAlignmentChange() { |
| TrayBackgroundView::UpdateAfterShelfAlignmentChange(); |
| UpdateItemsAfterShelfAlignmentChange(); |
| // Destroy any existing bubble so that it is rebuilt correctly. |
| CloseSystemBubbleAndDeactivateSystemTray(); |
| // Rebuild any notification bubble. |
| UpdateWebNotifications(); |
| } |
| |
| void SystemTray::AnchorUpdated() { |
| if (system_bubble_) { |
| UpdateClippingWindowBounds(); |
| system_bubble_->bubble_view()->UpdateBubble(); |
| // Should check |system_bubble_| again here. Since UpdateBubble above |
| // set the bounds of the bubble which will stop the current animation. |
| // If the system tray bubble is during animation to close, |
| // CloseBubbleObserver in TrayBackgroundView will close the bubble if |
| // animation finished. |
| if (system_bubble_) |
| UpdateBubbleViewArrow(system_bubble_->bubble_view()); |
| } |
| } |
| |
| void SystemTray::BubbleResized(const TrayBubbleView* bubble_view) { |
| UpdateWebNotifications(); |
| } |
| |
| void SystemTray::HideBubbleWithView(const TrayBubbleView* bubble_view) { |
| if (system_bubble_.get() && bubble_view == system_bubble_->bubble_view()) { |
| DestroySystemBubble(); |
| shelf()->UpdateAutoHideState(); |
| } |
| } |
| |
| void SystemTray::ClickedOutsideBubble() { |
| if (!system_bubble_ || system_bubble_->is_persistent()) |
| return; |
| HideBubbleWithView(system_bubble_->bubble_view()); |
| } |
| |
| bool SystemTray::PerformAction(const ui::Event& event) { |
| UserMetricsRecorder::RecordUserClick( |
| LoginMetricsRecorder::LockScreenUserClickTarget::kSystemTray); |
| |
| // If we're already showing a full system tray menu, either default or |
| // detailed menu, hide it; otherwise, show it (and hide any popup that's |
| // currently shown). |
| if (HasSystemBubble() && full_system_tray_menu_) { |
| system_bubble_->bubble()->Close(); |
| } else { |
| ShowBubble(event.IsMouseEvent() || event.IsGestureEvent()); |
| if (event.IsKeyEvent() || (event.flags() & ui::EF_TOUCH_ACCESSIBILITY)) |
| ActivateBubble(); |
| } |
| return true; |
| } |
| |
| void SystemTray::CloseBubble() { |
| if (!system_bubble_) |
| return; |
| system_bubble_->bubble()->Close(); |
| } |
| |
| void SystemTray::ShowBubble(bool show_by_click) { |
| ShowDefaultView(BUBBLE_CREATE_NEW, show_by_click); |
| } |
| |
| views::TrayBubbleView* SystemTray::GetBubbleView() { |
| // Only return the bubble view when it's showing the main system tray bubble, |
| // not the volume or brightness bubbles etc., to avoid client confusion. |
| return system_bubble_ && full_system_tray_menu_ |
| ? system_bubble_->bubble_view() |
| : nullptr; |
| } |
| |
| void SystemTray::BubbleViewDestroyed() { |
| if (system_bubble_) { |
| system_bubble_->bubble()->BubbleViewDestroyed(); |
| } |
| } |
| |
| void SystemTray::OnMouseEnteredView() { |
| if (system_bubble_) |
| system_bubble_->bubble()->StopAutoCloseTimer(); |
| } |
| |
| void SystemTray::OnMouseExitedView() { |
| if (system_bubble_) |
| system_bubble_->bubble()->RestartAutoCloseTimer(); |
| } |
| |
| base::string16 SystemTray::GetAccessibleNameForBubble() { |
| return GetAccessibleNameForTray(); |
| } |
| |
| bool SystemTray::ShouldEnableExtraKeyboardAccessibility() { |
| // Do not enable extra keyboard accessibility for persistent system bubble. |
| // e.g. volume slider. Persistent system bubble is a bubble which is not |
| // closed even if user clicks outside of the bubble. |
| return system_bubble_ && !system_bubble_->is_persistent() && |
| Shell::Get()->accessibility_controller()->IsSpokenFeedbackEnabled(); |
| } |
| |
| void SystemTray::HideBubble(const TrayBubbleView* bubble_view) { |
| HideBubbleWithView(bubble_view); |
| } |
| |
| void SystemTray::ActivateAndStartNavigation(const ui::KeyEvent& key_event) { |
| if (!system_bubble_) |
| return; |
| ActivateBubble(); |
| |
| views::Widget* widget = GetSystemBubble()->bubble_view()->GetWidget(); |
| widget->GetFocusManager()->OnKeyEvent(key_event); |
| } |
| |
| void SystemTray::ActivateBubble() { |
| TrayBubbleView* bubble_view = GetSystemBubble()->bubble_view(); |
| // If system tray bubble is in the process of closing, do not try to activate |
| // bubble. |
| if (bubble_view->GetWidget()->IsClosed()) |
| return; |
| bubble_view->set_can_activate(true); |
| bubble_view->GetWidget()->Activate(); |
| } |
| |
| void SystemTray::CloseSystemBubbleAndDeactivateSystemTray() { |
| system_bubble_.reset(); |
| // When closing a system bubble with the alternate shelf layout, we need to |
| // turn off the active tinting of the shelf. |
| if (full_system_tray_menu_) { |
| SetIsActive(false); |
| full_system_tray_menu_ = false; |
| } |
| } |
| |
| void SystemTray::RecordSystemMenuMetrics() { |
| DCHECK(system_bubble_); |
| |
| if (system_bubble_->system_tray_view()) |
| system_bubble_->system_tray_view()->RecordVisibleRowMetrics(); |
| |
| TrayBubbleView* bubble_view = system_bubble_->bubble_view(); |
| int num_rows = 0; |
| for (int i = 0; i < bubble_view->child_count(); i++) { |
| // Certain menu rows are attached by default but can set themselves as |
| // invisible (IME is one such example). Count only user-visible rows. |
| if (bubble_view->child_at(i)->visible()) |
| num_rows++; |
| } |
| UMA_HISTOGRAM_COUNTS_100("Ash.SystemMenu.Rows", num_rows); |
| |
| int work_area_height = |
| display::Screen::GetScreen() |
| ->GetDisplayNearestWindow(bubble_view->GetWidget()->GetNativeWindow()) |
| .work_area() |
| .height(); |
| if (work_area_height > 0) { |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Ash.SystemMenu.PercentageOfWorkAreaHeightCoveredByMenu", |
| 100 * bubble_view->height() / work_area_height, 1, 300, 100); |
| } |
| } |
| |
| } // namespace ash |