#include "ash/wm/workspace/backdrop_controller.h"
#include <algorithm>
#include <utility>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/accessibility_delegate.h"
#include "ash/animation/animation_change_type.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_animation_types.h"
#include "ash/screen_util.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wm/always_on_top_controller.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_types.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/auto_reset.h"
#include "base/containers/adapters.h"
#include "base/functional/bind.h"
#include "chromeos/ash/components/audio/sounds.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/display/tablet_state.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
class BackdropEventHandler : public ui::EventHandler {
public:
BackdropEventHandler() = default;
BackdropEventHandler(const BackdropEventHandler&) = delete;
BackdropEventHandler& operator=(const BackdropEventHandler&) = delete;
~BackdropEventHandler() override = default;
void OnEvent(ui::Event* event) override {
if (event->IsLocatedEvent()) {
switch (event->type()) {
case ui::EventType::kMousePressed:
case ui::EventType::kMousewheel:
case ui::EventType::kTouchPressed:
case ui::EventType::kGestureBegin:
case ui::EventType::kScroll:
case ui::EventType::kScrollFlingStart:
Shell::Get()->accessibility_controller()->PlayEarcon(
Sound::kVolumeAdjust);
break;
default:
break;
}
event->SetHandled();
}
}
};
class ScopedWindowVisibilityAnimationTypeResetter {
public:
ScopedWindowVisibilityAnimationTypeResetter(aura::Window* window,
int new_type)
: window_(window) {
DCHECK(window);
if (::wm::GetWindowVisibilityAnimationType(window) == new_type) {
window_ = nullptr;
return;
}
::wm::SetWindowVisibilityAnimationType(window_, new_type);
}
~ScopedWindowVisibilityAnimationTypeResetter() {
if (window_) {
::wm::SetWindowVisibilityAnimationType(
window_, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}
}
ScopedWindowVisibilityAnimationTypeResetter(
const ScopedWindowVisibilityAnimationTypeResetter&) = delete;
ScopedWindowVisibilityAnimationTypeResetter& operator=(
const ScopedWindowVisibilityAnimationTypeResetter&) = delete;
private:
raw_ptr<aura::Window> window_;
};
bool InOverviewSession() {
OverviewController* overview_controller = Shell::Get()->overview_controller();
return overview_controller && overview_controller->InOverviewSession();
}
aura::Window* GetBottomMostSnappedWindowForDeskContainer(
aura::Window* desk_container) {
DCHECK(desks_util::IsDeskContainer(desk_container));
DCHECK(display::Screen::Get()->InTabletMode());
SplitViewController* split_view_controller =
SplitViewController::Get(desk_container);
if (desks_util::IsActiveDeskContainer(desk_container) &&
split_view_controller->InSplitViewMode()) {
aura::Window* left_window = split_view_controller->primary_window();
aura::Window* right_window = split_view_controller->secondary_window();
for (aura::Window* child : desk_container->children()) {
if (child == left_window || child == right_window)
return child;
}
return nullptr;
}
for (aura::Window* child : desk_container->children()) {
if (WindowState::Get(child)->IsSnapped())
return child;
}
return nullptr;
}
}
class BackdropController::WindowAnimationWaiter
: public ui::ImplicitAnimationObserver {
public:
WindowAnimationWaiter(BackdropController* owner,
aura::Window* animating_window)
: owner_(owner), animating_window_(animating_window) {
auto* animator = animating_window_->layer()->GetAnimator();
DCHECK(animator->is_animating());
const auto original_transition_duration = animator->GetTransitionDuration();
ui::ScopedLayerAnimationSettings settings{animator};
settings.SetTransitionDuration(original_transition_duration);
settings.AddObserver(this);
}
~WindowAnimationWaiter() override { StopObservingImplicitAnimations(); }
WindowAnimationWaiter(const WindowAnimationWaiter&) = delete;
WindowAnimationWaiter& operator=(const WindowAnimationWaiter&) = delete;
aura::Window* animating_window() { return animating_window_; }
void OnImplicitAnimationsCompleted() override {
auto to_destroy = std::move(owner_->window_animation_waiter_);
DCHECK_EQ(this, to_destroy.get());
owner_->UpdateBackdrop();
}
private:
raw_ptr<BackdropController> owner_;
raw_ptr<aura::Window> animating_window_;
};
BackdropController::BackdropController(aura::Window* container)
: root_window_(container->GetRootWindow()), container_(container) {
DCHECK(container_);
auto* shell = Shell::Get();
SplitViewController::Get(root_window_)->AddObserver(this);
shell->overview_controller()->AddObserver(this);
shell->accessibility_controller()->AddObserver(this);
shell->wallpaper_controller()->AddObserver(this);
}
BackdropController::~BackdropController() {
window_backdrop_observations_.RemoveAllObservations();
auto* shell = Shell::Get();
shell->accessibility_controller()->RemoveObserver(this);
shell->wallpaper_controller()->RemoveObserver(this);
if (shell->overview_controller())
shell->overview_controller()->RemoveObserver(this);
SplitViewController::Get(root_window_)->RemoveObserver(this);
Hide(true);
}
void BackdropController::OnWindowAddedToLayout(aura::Window* window) {
if (DoesWindowCauseBackdropUpdates(window)) {
window_backdrop_observations_.AddObservation(WindowBackdrop::Get(window));
UpdateBackdrop();
}
}
void BackdropController::OnWindowRemovedFromLayout(aura::Window* window) {
WindowBackdrop* window_backdrop = WindowBackdrop::Get(window);
if (window_backdrop_observations_.IsObservingSource(window_backdrop))
window_backdrop_observations_.RemoveObservation(window_backdrop);
if (DoesWindowCauseBackdropUpdates(window))
UpdateBackdrop();
}
void BackdropController::OnChildWindowVisibilityChanged(aura::Window* window) {
if (DoesWindowCauseBackdropUpdates(window))
UpdateBackdrop();
}
void BackdropController::OnWindowStackingChanged(aura::Window* window) {
if (DoesWindowCauseBackdropUpdates(window))
UpdateBackdrop();
}
void BackdropController::OnPostWindowStateTypeChange(aura::Window* window) {
if (backdrop_ && backdrop_->IsVisible() &&
WindowState::Get(window)->IsSnapped() &&
SplitViewController::Get(window->GetRootWindow())
->WillStartPartialOverview(window)) {
Hide(false, false);
return;
}
if (DoesWindowCauseBackdropUpdates(window))
UpdateBackdrop();
}
void BackdropController::OnDisplayMetricsChanged() {
MaybeUpdateLayout();
}
void BackdropController::OnTabletModeChanged() {
UpdateBackdrop();
}
void BackdropController::OnDeskContentChanged() {
DCHECK(InOverviewSession());
UpdateBackdropInternal();
}
void BackdropController::UpdateBackdrop() {
if (pause_update_ || InOverviewSession())
return;
UpdateBackdropInternal();
}
aura::Window* BackdropController::GetTopmostWindowWithBackdrop() {
const aura::Window::Windows windows = container_->children();
for (aura::Window* window : base::Reversed(windows)) {
if (window == backdrop_window_)
continue;
if (window->GetType() != aura::client::WINDOW_TYPE_NORMAL)
continue;
auto* window_state = WindowState::Get(window);
if (window_state->IsMinimized())
continue;
if (desks_util::IsDeskContainer(container_)) {
if (!window->layer()->GetTargetVisibility())
continue;
if (!wm::CanActivateWindow(window))
continue;
}
if (!WindowShouldHaveBackdrop(window))
continue;
if (!window_util::ShouldShowForCurrentUser(window))
continue;
return window;
}
return nullptr;
}
void BackdropController::HideOnTakingInformedRestoreScreenshot() {
Hide(false, false);
}
base::ScopedClosureRunner BackdropController::PauseUpdates() {
DCHECK(!pause_update_);
pause_update_ = true;
return base::ScopedClosureRunner(base::BindOnce(
&BackdropController::RestoreUpdates, weak_ptr_factory_.GetWeakPtr()));
}
void BackdropController::OnOverviewModeStarting() {
Hide(false, false);
}
void BackdropController::OnOverviewModeEnding(
OverviewSession* overview_session) {
pause_update_ = true;
}
void BackdropController::OnOverviewModeEndingAnimationComplete(bool canceled) {
pause_update_ = false;
UpdateBackdrop();
}
void BackdropController::OnAccessibilityStatusChanged() {
UpdateAccessibilityMode();
}
void BackdropController::OnSplitViewStateChanged(
SplitViewController::State previous_state,
SplitViewController::State state) {
UpdateBackdropInternal();
}
void BackdropController::OnSplitViewDividerPositionChanged() {
MaybeUpdateLayout();
}
void BackdropController::OnWallpaperPreviewStarted() {
aura::Window* active_window = window_util::GetActiveWindow();
if (active_window) {
WindowBackdrop::Get(active_window)
->SetBackdropMode(WindowBackdrop::BackdropMode::kDisabled);
}
}
void BackdropController::OnWindowBackdropPropertyChanged(aura::Window* window) {
if (DoesWindowCauseBackdropUpdates(window))
UpdateBackdrop();
}
void BackdropController::RestoreUpdates() {
pause_update_ = false;
UpdateBackdrop();
}
void BackdropController::UpdateBackdropInternal() {
if (pause_update_)
return;
Shelf::ScopedAutoHideLock auto_hide_lock(ash::Shelf::ForWindow(container_));
base::AutoReset<bool> lock(&pause_update_, true);
aura::Window* window = GetTopmostWindowWithBackdrop();
if (window == window_having_backdrop_) {
if (window)
Show();
return;
}
window_having_backdrop_ = window;
if (!window_having_backdrop_) {
Hide(true);
return;
}
DCHECK_EQ(window_having_backdrop_->GetRootWindow(), root_window_);
DCHECK_NE(window_having_backdrop_, backdrop_window_);
EnsureBackdropWidget();
Show();
}
void BackdropController::EnsureBackdropWidget() {
DCHECK(window_having_backdrop_);
if (backdrop_)
return;
backdrop_ = std::make_unique<views::Widget>();
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = container_->GetBoundsInScreen();
params.layer_type = ui::LAYER_SOLID_COLOR;
params.name = "Backdrop";
params.activatable = views::Widget::InitParams::Activatable::kNo;
DCHECK_NE(kShellWindowId_Invalid, container_->GetId());
params.parent = container_;
params.init_properties_container.SetProperty(kHideInOverviewKey, true);
params.init_properties_container.SetProperty(kForceVisibleInMiniViewKey,
true);
backdrop_->Init(std::move(params));
backdrop_window_ = backdrop_->GetNativeWindow();
AlwaysOnTopController::SetDisallowReparent(backdrop_window_);
backdrop_window_->layer()->SetColor(
WindowBackdrop::Get(window_having_backdrop_)->GetBackdropColor());
WindowState::Get(backdrop_window_)->set_allow_set_bounds_direct(true);
UpdateAccessibilityMode();
}
void BackdropController::UpdateAccessibilityMode() {
if (!backdrop_)
return;
const bool enabled =
Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
if (enabled) {
if (!backdrop_event_handler_) {
backdrop_event_handler_ = std::make_unique<BackdropEventHandler>();
original_event_handler_ =
backdrop_window_->SetTargetHandler(backdrop_event_handler_.get());
}
} else if (backdrop_event_handler_) {
backdrop_window_->SetTargetHandler(original_event_handler_);
backdrop_event_handler_.reset();
}
}
bool BackdropController::WindowShouldHaveBackdrop(aura::Window* window) {
WindowBackdrop* window_backdrop = WindowBackdrop::Get(window);
if (window_backdrop->temporarily_disabled()) {
return false;
}
WindowBackdrop::BackdropMode backdrop_mode = window_backdrop->mode();
if (backdrop_mode == WindowBackdrop::BackdropMode::kEnabled) {
return true;
}
if (backdrop_mode == WindowBackdrop::BackdropMode::kDisabled) {
return false;
}
if (!desks_util::IsDeskContainer(container_)) {
return false;
}
if (!display::Screen::Get()->InTabletMode()) {
return false;
}
auto* state = WindowState::Get(window);
if (state->IsPip()) {
return false;
}
if (!state->IsSnapped()) {
return true;
}
auto* bottom_most_snapped_window =
GetBottomMostSnappedWindowForDeskContainer(container_);
if (!bottom_most_snapped_window) {
return true;
}
return window == bottom_most_snapped_window;
}
void BackdropController::Show() {
DCHECK(backdrop_);
DCHECK(backdrop_window_);
DCHECK(window_having_backdrop_);
const bool in_overview = InOverviewSession();
if (!in_overview && MaybeWaitForWindowAnimation())
return;
Layout();
const SkColor backdrop_color =
WindowBackdrop::Get(window_having_backdrop_)->GetBackdropColor();
if (backdrop_window_->layer()->GetTargetColor() != backdrop_color)
backdrop_window_->layer()->SetColor(backdrop_color);
container_->StackChildBelow(backdrop_window_, window_having_backdrop_);
if (backdrop_->IsVisible() || in_overview)
return;
ScopedWindowVisibilityAnimationTypeResetter resetter{
backdrop_window_,
WindowState::Get(window_having_backdrop_)->CanMaximize()
? static_cast<int>(WINDOW_VISIBILITY_ANIMATION_TYPE_STEP_END)
: static_cast<int>(::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE)};
backdrop_->Show();
}
void BackdropController::Hide(bool destroy, bool animate) {
if (!backdrop_ || is_hiding_backdrop_)
return;
DCHECK(backdrop_window_);
base::AutoReset<bool> lock(&is_hiding_backdrop_, true);
const aura::Window::Windows windows = container_->children();
auto window_iter = std::ranges::find(windows, backdrop_window_);
++window_iter;
if (window_iter != windows.end()) {
aura::Window* window_above_backdrop = *window_iter;
WindowState* window_state = WindowState::Get(window_above_backdrop);
if (!animate || (window_state && window_state->CanMaximize()))
backdrop_window_->SetProperty(aura::client::kAnimationsDisabledKey, true);
} else {
backdrop_window_->SetProperty(aura::client::kAnimationsDisabledKey, true);
}
if (destroy) {
backdrop_.reset();
backdrop_window_ = nullptr;
original_event_handler_ = nullptr;
backdrop_event_handler_.reset();
} else {
backdrop_->Hide();
}
}
bool BackdropController::BackdropShouldFullscreen() {
SplitViewController* split_view_controller =
SplitViewController::Get(root_window_);
SplitViewController::State state = split_view_controller->state();
if ((state == SplitViewController::State::kPrimarySnapped &&
window_having_backdrop_ == split_view_controller->primary_window()) ||
(state == SplitViewController::State::kSecondarySnapped &&
window_having_backdrop_ == split_view_controller->secondary_window())) {
return false;
}
return true;
}
gfx::Rect BackdropController::GetBackdropBounds() {
DCHECK(!BackdropShouldFullscreen());
SplitViewController* split_view_controller =
SplitViewController::Get(root_window_);
SplitViewController::State state = split_view_controller->state();
DCHECK(state == SplitViewController::State::kPrimarySnapped ||
state == SplitViewController::State::kSecondarySnapped);
SnapPosition snap_position =
(state == SplitViewController::State::kPrimarySnapped)
? SnapPosition::kPrimary
: SnapPosition::kSecondary;
return split_view_controller->GetSnappedWindowBoundsInScreen(
snap_position, nullptr,
chromeos::kDefaultSnapRatio, true);
}
void BackdropController::Layout() {
DCHECK(backdrop_);
backdrop_->SetFullscreen(BackdropShouldFullscreen());
if (backdrop_->IsFullscreen()) {
const gfx::Rect bounds =
screen_util::GetDisplayBoundsInParent(backdrop_window_);
backdrop_window_->SetBounds(
screen_util::SnapBoundsToDisplayEdge(bounds, backdrop_window_));
} else {
backdrop_->SetBounds(GetBackdropBounds());
}
}
bool BackdropController::MaybeWaitForWindowAnimation() {
DCHECK(window_having_backdrop_);
auto* animator = window_having_backdrop_->layer()->GetAnimator();
if (!animator->is_animating())
return false;
if (window_animation_waiter_ &&
window_animation_waiter_->animating_window() == window_having_backdrop_) {
return true;
}
window_animation_waiter_.reset();
constexpr int kCheckedAnimations = ui::LayerAnimationElement::BOUNDS |
ui::LayerAnimationElement::TRANSFORM |
ui::LayerAnimationElement::OPACITY |
ui::LayerAnimationElement::VISIBILITY;
if (!animator->IsAnimatingOnePropertyOf(kCheckedAnimations))
return false;
window_animation_waiter_ =
std::make_unique<WindowAnimationWaiter>(this, window_having_backdrop_);
return true;
}
void BackdropController::MaybeUpdateLayout() {
if (backdrop_ && backdrop_->IsVisible())
Layout();
}
bool BackdropController::DoesWindowCauseBackdropUpdates(
aura::Window* window) const {
return window->GetType() != aura::client::WINDOW_TYPE_POPUP &&
(!backdrop_ || window != backdrop_->GetNativeWindow());
}
}