blob: 473ab33b79ec8df0868923ab516199ea745cb5fd [file] [log] [blame]
// Copyright 2019 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/message_center/unified_message_center_bubble.h"
#include <memory>
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/bubble/bubble_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/system_shadow.h"
#include "ash/system/message_center/message_center_style.h"
#include "ash/system/message_center/unified_message_center_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_bubble.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ash/system/unified/unified_system_tray_view.h"
#include "base/i18n/rtl.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/views/focus/focus_search.h"
#include "ui/views/widget/widget.h"
namespace ash {
// We need to draw a custom inner border for the message center in a separate
// layer so we can properly clip ARC notifications. Each ARC notification is
// contained in its own Window with its own layer, and the border needs to be
// drawn on top of them all.
class UnifiedMessageCenterBubble::Border : public ui::LayerDelegate {
public:
Border() : layer_(ui::LAYER_TEXTURED) {
layer_.set_delegate(this);
layer_.SetFillsBoundsOpaquely(false);
}
Border(const Border&) = delete;
Border& operator=(const Border&) = delete;
~Border() override = default;
ui::Layer* layer() { return &layer_; }
private:
// ui::LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override {
gfx::Rect bounds = layer()->bounds();
ui::PaintRecorder recorder(context, bounds.size());
gfx::Canvas* canvas = recorder.canvas();
// Draw a solid rounded rect as the inner border.
cc::PaintFlags flags;
flags.setColor(message_center_style::kSeperatorColor);
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(canvas->image_scale());
flags.setAntiAlias(true);
canvas->DrawRoundRect(bounds, kBubbleCornerRadius, flags);
}
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
ui::Layer layer_;
};
UnifiedMessageCenterBubble::UnifiedMessageCenterBubble(UnifiedSystemTray* tray)
: tray_(tray), border_(std::make_unique<Border>()) {
TrayBubbleView::InitParams init_params;
init_params.delegate = GetWeakPtr();
// Anchor within the overlay container.
init_params.parent_window = tray->GetBubbleWindowContainer();
init_params.anchor_mode = TrayBubbleView::AnchorMode::kRect;
init_params.preferred_width = kTrayMenuWidth;
init_params.has_shadow = false;
init_params.close_on_deactivate = false;
if (features::IsNotificationsRefreshEnabled())
init_params.translucent = true;
bubble_view_ = new TrayBubbleView(init_params);
message_center_view_ =
bubble_view_->AddChildView(std::make_unique<UnifiedMessageCenterView>(
nullptr /* parent */, tray->model(), this));
time_to_click_recorder_ =
std::make_unique<TimeToClickRecorder>(this, message_center_view_);
message_center_view_->AddObserver(this);
}
void UnifiedMessageCenterBubble::ShowBubble() {
bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
bubble_widget_->AddObserver(this);
TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
// Stack system tray bubble's window above message center's window, such that
// message center's shadow will not cover on system tray.
tray_->GetBubbleWindowContainer()->StackChildAbove(
tray_->bubble()->GetBubbleWidget()->GetNativeWindow(),
bubble_widget_->GetNativeWindow());
if (!features::IsNotificationsRefreshEnabled()) {
ui::Layer* content_layer = bubble_view_->layer();
float radius = kBubbleCornerRadius;
content_layer->SetRoundedCornerRadius({radius, radius, radius, radius});
content_layer->SetIsFastRoundedCorner(true);
content_layer->Add(border_->layer());
}
// Create a shadow for bubble widget.
shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForWindow(
bubble_widget_->GetNativeWindow(), SystemShadow::Type::kElevation12);
shadow_->SetRoundedCornerRadius(kBubbleCornerRadius);
bubble_view_->InitializeAndShowBubble();
message_center_view_->Init();
UpdateBubbleState();
tray_->tray_event_filter()->AddBubble(this);
tray_->bubble()->unified_view()->AddObserver(this);
}
UnifiedMessageCenterBubble::~UnifiedMessageCenterBubble() {
if (bubble_widget_) {
tray_->tray_event_filter()->RemoveBubble(this);
tray_->bubble()->unified_view()->RemoveObserver(this);
CHECK(message_center_view_);
message_center_view_->RemoveObserver(this);
bubble_view_->ResetDelegate();
bubble_widget_->RemoveObserver(this);
bubble_widget_->Close();
}
CHECK(!views::WidgetObserver::IsInObserverList());
}
gfx::Rect UnifiedMessageCenterBubble::GetBoundsInScreen() const {
DCHECK(bubble_view_);
return bubble_view_->GetBoundsInScreen();
}
void UnifiedMessageCenterBubble::CollapseMessageCenter() {
if (message_center_view_->collapsed())
return;
message_center_view_->SetCollapsed(true /*animate*/);
}
void UnifiedMessageCenterBubble::ExpandMessageCenter() {
if (!message_center_view_->collapsed())
return;
if (tray_->IsShowingCalendarView())
tray_->bubble()->unified_system_tray_controller()->TransitionToMainView(
/*restore_focus=*/true);
message_center_view_->SetExpanded();
UpdatePosition();
tray_->EnsureQuickSettingsCollapsed(true /*animate*/);
}
void UnifiedMessageCenterBubble::UpdatePosition() {
int available_height = CalculateAvailableHeight();
message_center_view_->SetMaxHeight(available_height);
message_center_view_->SetAvailableHeight(available_height);
if (!tray_->bubble())
return;
// Shelf bubbles need to be offset from the shelf, otherwise they will be
// flush with the shelf. The bounds can't be shifted via insets because this
// enlarges the layer bounds and this can break ARC notification rounded
// corners. Apply the offset to the anchor rect.
gfx::Rect anchor_rect = tray_->shelf()->GetSystemTrayAnchorRect();
gfx::Insets tray_bubble_insets = GetTrayBubbleInsets();
int offset;
switch (tray_->shelf()->alignment()) {
case ShelfAlignment::kLeft:
offset = tray_bubble_insets.left();
break;
case ShelfAlignment::kRight:
offset = -tray_bubble_insets.right();
break;
case ShelfAlignment::kBottom:
case ShelfAlignment::kBottomLocked:
if (base::i18n::IsRTL()) {
offset = tray_bubble_insets.left();
break;
}
offset = -tray_bubble_insets.right();
break;
}
anchor_rect.set_x(anchor_rect.x() + offset);
anchor_rect.set_y(anchor_rect.y() - tray_->bubble()->GetCurrentTrayHeight() -
tray_bubble_insets.bottom() -
kUnifiedMessageCenterBubbleSpacing);
bubble_view_->ChangeAnchorRect(anchor_rect);
if (!features::IsNotificationsRefreshEnabled()) {
bubble_view_->layer()->StackAtTop(border_->layer());
border_->layer()->SetBounds(message_center_view_->GetContentsBounds());
}
// When the last notification is removed, the content bounds of message center
// may become too small such which makes the shadow's bounds smaller than its
// blur region. To avoid this, we hide the shadow when the message center has
// no notifications.
shadow_->GetLayer()->SetVisible(
message_center_view_->message_list_view()->GetTotalNotificationCount());
}
void UnifiedMessageCenterBubble::FocusEntered(bool reverse) {
message_center_view_->FocusEntered(reverse);
}
bool UnifiedMessageCenterBubble::FocusOut(bool reverse) {
return tray_->FocusQuickSettings(reverse);
}
void UnifiedMessageCenterBubble::ActivateQuickSettingsBubble() {
tray_->ActivateBubble();
}
bool UnifiedMessageCenterBubble::IsMessageCenterVisible() {
return !!bubble_widget_ && message_center_view_ &&
message_center_view_->GetVisible();
}
bool UnifiedMessageCenterBubble::IsMessageCenterCollapsed() {
return message_center_view_->collapsed();
}
TrayBackgroundView* UnifiedMessageCenterBubble::GetTray() const {
return tray_;
}
TrayBubbleView* UnifiedMessageCenterBubble::GetBubbleView() const {
return bubble_view_;
}
views::Widget* UnifiedMessageCenterBubble::GetBubbleWidget() const {
return bubble_widget_;
}
std::u16string UnifiedMessageCenterBubble::GetAccessibleNameForBubble() {
return l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_ACCESSIBLE_NAME);
}
bool UnifiedMessageCenterBubble::ShouldEnableExtraKeyboardAccessibility() {
return Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
}
void UnifiedMessageCenterBubble::OnViewPreferredSizeChanged(
views::View* observed_view) {
UpdatePosition();
bubble_view_->Layout();
}
void UnifiedMessageCenterBubble::OnViewVisibilityChanged(
views::View* observed_view,
views::View* starting_view) {
// Hide the message center widget if the message center is not
// visible. This is to ensure we do not see an empty bubble.
if (observed_view != message_center_view_)
return;
bubble_view_->UpdateBubble();
}
void UnifiedMessageCenterBubble::OnWidgetDestroying(views::Widget* widget) {
CHECK_EQ(bubble_widget_, widget);
tray_->tray_event_filter()->RemoveBubble(this);
tray_->bubble()->unified_view()->RemoveObserver(this);
message_center_view_->RemoveObserver(this);
bubble_widget_->RemoveObserver(this);
bubble_widget_ = nullptr;
shadow_.reset();
bubble_view_->ResetDelegate();
// Close the quick settings bubble as well, which may not automatically happen
// when dismissing the message center bubble by pressing ESC.
tray_->CloseBubble();
}
void UnifiedMessageCenterBubble::OnWidgetActivationChanged(
views::Widget* widget,
bool active) {
if (active)
tray_->bubble()->OnMessageCenterActivated();
}
void UnifiedMessageCenterBubble::OnDisplayConfigurationChanged() {
UpdateBubbleState();
}
void UnifiedMessageCenterBubble::UpdateBubbleState() {
if (CalculateAvailableHeight() < kMessageCenterCollapseThreshold &&
message_center_view_->message_list_view()->GetTotalNotificationCount()) {
if (tray_->IsQuickSettingsExplicitlyExpanded()) {
message_center_view_->SetCollapsed(false /*animate*/);
} else {
message_center_view_->SetExpanded();
tray_->EnsureQuickSettingsCollapsed(false /*animate*/);
}
} else if (message_center_view_->collapsed()) {
message_center_view_->SetExpanded();
}
UpdatePosition();
}
int UnifiedMessageCenterBubble::CalculateAvailableHeight() {
// TODO(crbug/1311738): Temporary fix to prevent crashes in case the quick
// settings bubble is destroyed before the message center bubble. In the long
// term we should remove this code altogether and calculate the max height for
// the message center bubble separately.
if (!tray_->bubble())
return 0;
return tray_->bubble()->CalculateMaxHeight() -
tray_->bubble()->GetCurrentTrayHeight() -
GetBubbleInsetHotseatCompensation() -
kUnifiedMessageCenterBubbleSpacing;
}
void UnifiedMessageCenterBubble::RecordTimeToClick() {
// TODO(tengs): We are currently only using this handler to record the first
// interaction (i.e. whether the message center or quick settings was clicked
// first). Maybe log the time to click if it is useful in the future.
tray_->MaybeRecordFirstInteraction(
UnifiedSystemTray::FirstInteractionType::kMessageCenter);
}
} // namespace ash