| // 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_bubble.h" |
| |
| #include "ash/bubble/bubble_constants.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/system/message_center/unified_message_center_bubble.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/tray/tray_background_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_event_filter.h" |
| #include "ash/system/tray/tray_utils.h" |
| #include "ash/system/unified/unified_system_tray.h" |
| #include "ash/system/unified/unified_system_tray_controller.h" |
| #include "ash/system/unified/unified_system_tray_view.h" |
| #include "ash/wm/container_finder.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/work_area_insets.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/wm/core/window_util.h" |
| #include "ui/wm/public/activation_client.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Container view of UnifiedSystemTrayView to return fake preferred size for |
| // animation optimization. See UnifiedSystemTrayBubble::UpdateTransform(). |
| // The fake size is specific to the structure of TrayBubbleView, so it is better |
| // to keep it separate from UnifiedSystemTrayView. |
| class ContainerView : public views::View { |
| public: |
| explicit ContainerView(UnifiedSystemTrayView* unified_view) |
| : unified_view_(unified_view) { |
| AddChildView(unified_view); |
| } |
| |
| ContainerView(const ContainerView&) = delete; |
| ContainerView& operator=(const ContainerView&) = delete; |
| |
| ~ContainerView() override = default; |
| |
| // views::View: |
| void Layout() override { unified_view_->SetBoundsRect(GetContentsBounds()); } |
| const char* GetClassName() const override { return "ContainerView"; } |
| |
| gfx::Size CalculatePreferredSize() const override { |
| // If transform is used, always return the maximum expanded height. |
| // Otherwise, return the actual height. |
| // Note that transforms are currently only supported when there are not |
| // notifications, so we only consider the system tray height (excluding the |
| // message center) for now. |
| return gfx::Size(kTrayMenuWidth, unified_view_->GetCurrentHeight()); |
| } |
| |
| void ChildPreferredSizeChanged(views::View* child) override { |
| PreferredSizeChanged(); |
| } |
| |
| private: |
| UnifiedSystemTrayView* const unified_view_; |
| }; |
| |
| } // namespace |
| |
| UnifiedSystemTrayBubble::UnifiedSystemTrayBubble(UnifiedSystemTray* tray) |
| : controller_(std::make_unique<UnifiedSystemTrayController>(tray->model(), |
| this, |
| tray)), |
| tray_(tray) { |
| time_opened_ = base::TimeTicks::Now(); |
| |
| TrayBubbleView::InitParams init_params; |
| init_params.shelf_alignment = tray_->shelf()->alignment(); |
| init_params.preferred_width = kTrayMenuWidth; |
| init_params.delegate = tray; |
| init_params.parent_window = tray->GetBubbleWindowContainer(); |
| init_params.anchor_view = nullptr; |
| init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect; |
| init_params.anchor_rect = tray->shelf()->GetSystemTrayAnchorRect(); |
| init_params.insets = GetTrayBubbleInsets(); |
| init_params.corner_radius = kBubbleCornerRadius; |
| init_params.has_shadow = false; |
| init_params.close_on_deactivate = false; |
| init_params.reroute_event_handler = true; |
| init_params.translucent = true; |
| |
| bubble_view_ = new TrayBubbleView(init_params); |
| |
| unified_view_ = controller_->CreateView(); |
| time_to_click_recorder_ = |
| std::make_unique<TimeToClickRecorder>(this, unified_view_); |
| int max_height = CalculateMaxHeight(); |
| unified_view_->SetMaxHeight(max_height); |
| bubble_view_->SetMaxHeight(max_height); |
| controller_->ResetToCollapsedIfRequired(); |
| bubble_view_->AddChildView(unified_view_); |
| |
| bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_); |
| bubble_widget_->AddObserver(this); |
| |
| TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_); |
| bubble_view_->InitializeAndShowBubble(); |
| |
| // Notify accessibility features that the status tray has opened. |
| NotifyAccessibilityEvent(ax::mojom::Event::kShow, true); |
| |
| tray->tray_event_filter()->AddBubble(this); |
| tray->shelf()->AddObserver(this); |
| Shell::Get()->tablet_mode_controller()->AddObserver(this); |
| Shell::Get()->activation_client()->AddObserver(this); |
| } |
| |
| UnifiedSystemTrayBubble::~UnifiedSystemTrayBubble() { |
| Shell::Get()->activation_client()->RemoveObserver(this); |
| if (Shell::Get()->tablet_mode_controller()) |
| Shell::Get()->tablet_mode_controller()->RemoveObserver(this); |
| tray_->tray_event_filter()->RemoveBubble(this); |
| tray_->shelf()->RemoveObserver(this); |
| |
| // Unified view children depend on `controller_` which is about to go away. |
| // Remove child views synchronously to ensure they don't try to access |
| // `controller_` after `this` goes out of scope. |
| bubble_view_->RemoveAllChildViews(); |
| bubble_view_->ResetDelegate(); |
| |
| if (bubble_widget_) { |
| bubble_widget_->RemoveObserver(this); |
| bubble_widget_->Close(); |
| } |
| |
| CHECK(!IsInObserverList()); |
| } |
| |
| gfx::Rect UnifiedSystemTrayBubble::GetBoundsInScreen() const { |
| DCHECK(bubble_view_); |
| return bubble_view_->GetBoundsInScreen(); |
| } |
| |
| bool UnifiedSystemTrayBubble::IsBubbleActive() const { |
| return bubble_widget_ && bubble_widget_->IsActive(); |
| } |
| |
| void UnifiedSystemTrayBubble::EnsureCollapsed() { |
| if (!bubble_widget_) |
| return; |
| |
| DCHECK(unified_view_); |
| DCHECK(controller_); |
| controller_->EnsureCollapsed(); |
| } |
| |
| void UnifiedSystemTrayBubble::EnsureExpanded() { |
| if (!bubble_widget_) |
| return; |
| |
| DCHECK(unified_view_); |
| DCHECK(controller_); |
| controller_->EnsureExpanded(); |
| } |
| |
| void UnifiedSystemTrayBubble::CollapseWithoutAnimating() { |
| if (!bubble_widget_) |
| return; |
| |
| DCHECK(unified_view_); |
| DCHECK(controller_); |
| |
| controller_->CollapseWithoutAnimating(); |
| } |
| |
| void UnifiedSystemTrayBubble::CollapseMessageCenter() { |
| tray_->CollapseMessageCenter(); |
| } |
| |
| void UnifiedSystemTrayBubble::ExpandMessageCenter() { |
| tray_->ExpandMessageCenter(); |
| } |
| |
| void UnifiedSystemTrayBubble::ShowAudioDetailedView() { |
| if (!bubble_widget_) |
| return; |
| |
| DCHECK(unified_view_); |
| DCHECK(controller_); |
| controller_->ShowAudioDetailedView(); |
| } |
| |
| void UnifiedSystemTrayBubble::ShowCalendarView() { |
| if (!bubble_widget_) |
| return; |
| |
| DCHECK(unified_view_); |
| DCHECK(controller_); |
| controller_->ShowCalendarView(); |
| } |
| |
| void UnifiedSystemTrayBubble::ShowNetworkDetailedView(bool force) { |
| if (!bubble_widget_) |
| return; |
| |
| DCHECK(unified_view_); |
| DCHECK(controller_); |
| controller_->ShowNetworkDetailedView(force); |
| } |
| |
| void UnifiedSystemTrayBubble::UpdateBubble() { |
| if (!bubble_widget_) |
| return; |
| DCHECK(bubble_view_); |
| |
| bubble_view_->UpdateBubble(); |
| } |
| |
| TrayBackgroundView* UnifiedSystemTrayBubble::GetTray() const { |
| return tray_; |
| } |
| |
| TrayBubbleView* UnifiedSystemTrayBubble::GetBubbleView() const { |
| return bubble_view_; |
| } |
| |
| views::Widget* UnifiedSystemTrayBubble::GetBubbleWidget() const { |
| return bubble_widget_; |
| } |
| |
| int UnifiedSystemTrayBubble::GetCurrentTrayHeight() const { |
| return unified_view_->GetCurrentHeight(); |
| } |
| |
| int UnifiedSystemTrayBubble::CalculateMaxHeight() const { |
| // We use the system tray anchor rect's bottom position to calculate the free |
| // space height. Here 'GetSystemTrayAnchorRect' gets the rect that those |
| // bubble views will be anchored. The calculation of this rect has considered |
| // the position of the tray (bottom, left, right), the status of the tray |
| // (tray_->is_active()), etc. |
| int bottom = tray_->shelf()->GetSystemTrayAnchorRect().bottom(); |
| WorkAreaInsets* work_area = |
| WorkAreaInsets::ForWindow(tray_->shelf()->GetWindow()->GetRootWindow()); |
| int free_space_height_above_anchor = |
| bottom - work_area->user_work_area_bounds().y(); |
| return free_space_height_above_anchor - kBubbleMenuPadding * 2; |
| } |
| |
| bool UnifiedSystemTrayBubble::FocusOut(bool reverse) { |
| return tray_->FocusMessageCenter(reverse); |
| } |
| |
| void UnifiedSystemTrayBubble::FocusEntered(bool reverse) { |
| unified_view_->FocusEntered(reverse); |
| } |
| |
| void UnifiedSystemTrayBubble::OnMessageCenterActivated() { |
| // When the message center is activated, we no longer need to reroute key |
| // events to this bubble. Otherwise, we interfere with notifications that may |
| // require key input like inline replies. See crbug.com/1040738. |
| bubble_view_->StopReroutingEvents(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnDisplayConfigurationChanged() { |
| UpdateBubbleBounds(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnWidgetDestroying(views::Widget* widget) { |
| CHECK_EQ(bubble_widget_, widget); |
| bubble_widget_->RemoveObserver(this); |
| bubble_widget_ = nullptr; |
| |
| // `tray_->CloseBubble()` will delete `this`. |
| tray_->CloseBubble(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnWindowActivated(ActivationReason reason, |
| aura::Window* gained_active, |
| aura::Window* lost_active) { |
| if (!gained_active || !bubble_widget_) |
| return; |
| |
| // Check for the CloseBubble() lock. |
| if (!TrayBackgroundView::ShouldCloseBubbleOnWindowActivated()) |
| return; |
| |
| // Don't close the bubble if a transient child is gaining or losing |
| // activation. |
| if (bubble_widget_ == views::Widget::GetWidgetForNativeView(gained_active) || |
| ::wm::HasTransientAncestor(gained_active, |
| bubble_widget_->GetNativeWindow()) || |
| (lost_active && ::wm::HasTransientAncestor( |
| lost_active, bubble_widget_->GetNativeWindow()))) { |
| return; |
| } |
| |
| // Don't close the bubble if the message center is gaining activation. |
| if (tray_->IsMessageCenterBubbleShown()) { |
| views::Widget* message_center_widget = |
| tray_->message_center_bubble()->GetBubbleWidget(); |
| if (message_center_widget == |
| views::Widget::GetWidgetForNativeView(gained_active)) { |
| return; |
| } |
| |
| // If the message center is not visible, ignore activation changes. |
| // Otherwise, this may cause a crash when closing the dialog via |
| // accelerator. See crbug.com/1041174. |
| if (!message_center_widget->IsVisible()) |
| return; |
| } |
| |
| tray_->CloseBubble(); |
| } |
| |
| void UnifiedSystemTrayBubble::RecordTimeToClick() { |
| if (!time_opened_) |
| return; |
| |
| tray_->MaybeRecordFirstInteraction( |
| UnifiedSystemTray::FirstInteractionType::kQuickSettings); |
| |
| UMA_HISTOGRAM_TIMES("ChromeOS.SystemTray.TimeToClick2", |
| base::TimeTicks::Now() - time_opened_.value()); |
| |
| time_opened_.reset(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnTabletModeStarted() { |
| UpdateBubbleBounds(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnTabletModeEnded() { |
| UpdateBubbleBounds(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnAutoHideStateChanged( |
| ShelfAutoHideState new_state) { |
| UpdateBubbleBounds(); |
| } |
| |
| void UnifiedSystemTrayBubble::UpdateBubbleBounds() { |
| int max_height = CalculateMaxHeight(); |
| unified_view_->SetMaxHeight(max_height); |
| bubble_view_->SetMaxHeight(max_height); |
| bubble_view_->ChangeAnchorAlignment(tray_->shelf()->alignment()); |
| bubble_view_->ChangeAnchorRect(tray_->shelf()->GetSystemTrayAnchorRect()); |
| |
| if (tray_->IsMessageCenterBubbleShown()) |
| tray_->message_center_bubble()->UpdatePosition(); |
| } |
| |
| void UnifiedSystemTrayBubble::OnAnimationFinished() { |
| bubble_widget_->GetNativeWindow()->layer()->SetClipRect(gfx::Rect()); |
| } |
| |
| void UnifiedSystemTrayBubble::SetFrameVisible(bool visible) { |
| DCHECK(bubble_widget_); |
| bubble_widget_->non_client_view()->frame_view()->SetVisible(visible); |
| } |
| |
| void UnifiedSystemTrayBubble::NotifyAccessibilityEvent(ax::mojom::Event event, |
| bool send_native_event) { |
| bubble_view_->NotifyAccessibilityEvent(event, send_native_event); |
| } |
| |
| bool UnifiedSystemTrayBubble::ShowingAudioDetailedView() const { |
| return bubble_widget_ && controller_->showing_audio_detailed_view(); |
| } |
| |
| } // namespace ash |