#include "ash/wm/overview/overview_controller.h"
#include <utility>
#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/delayed_animation_observer_impl.h"
#include "ash/wm/overview/overview_constants.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/overview_wallpaper_controller.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/containers/cxx20_erase.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
constexpr base::TimeDelta kOcclusionPauseDurationForStart =
base::Milliseconds(50);
constexpr base::TimeDelta kOcclusionPauseDurationForEnd =
base::Milliseconds(500);
bool IsSplitViewDividerDraggedOrAnimated() {
SplitViewController* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
return split_view_controller->is_resizing_with_divider() ||
split_view_controller->IsDividerAnimating();
}
OverviewEnterExitType MaybeOverrideEnterExitTypeForHomeScreen(
OverviewEnterExitType original_type,
bool enter,
const std::vector<aura::Window*>& windows) {
if (original_type != OverviewEnterExitType::kNormal)
return original_type;
if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
return original_type;
for (const aura::Window* window : windows) {
if (!WindowState::Get(window)->IsMinimized()) {
return original_type;
}
}
return enter ? OverviewEnterExitType::kFadeInEnter
: OverviewEnterExitType::kFadeOutExit;
}
}
OverviewController::OverviewController()
: occlusion_pause_duration_for_end_(kOcclusionPauseDurationForEnd),
delayed_animation_task_delay_(kTransition) {
if (!chromeos::features::IsJellyrollEnabled()) {
overview_wallpaper_controller_ =
std::make_unique<OverviewWallpaperController>();
}
Shell::Get()->activation_client()->AddObserver(this);
}
OverviewController::~OverviewController() {
Shell::Get()->activation_client()->RemoveObserver(this);
overview_wallpaper_controller_.reset();
for (auto& animation_observer : delayed_animations_)
animation_observer->Shutdown();
for (auto& animation_observer : start_animations_)
animation_observer->Shutdown();
if (overview_session_) {
overview_session_->Shutdown();
overview_session_.reset();
}
}
bool OverviewController::StartOverview(OverviewStartAction action,
OverviewEnterExitType type) {
if (InOverviewSession())
return true;
if (!CanEnterOverview())
return false;
ToggleOverview(type);
RecordOverviewStartAction(action);
return true;
}
bool OverviewController::EndOverview(OverviewEndAction action,
OverviewEnterExitType type) {
if (!InOverviewSession())
return true;
if (!CanEndOverview(type))
return false;
ToggleOverview(type);
RecordOverviewEndAction(action);
DesksController::Get()->MaybeDismissPersistentDeskRemovalToast();
return true;
}
bool OverviewController::InOverviewSession() const {
return overview_session_ && !overview_session_->is_shutting_down();
}
void OverviewController::IncrementSelection(bool forward) {
DCHECK(InOverviewSession());
overview_session_->IncrementSelection(forward);
}
bool OverviewController::AcceptSelection() {
DCHECK(InOverviewSession());
return overview_session_->AcceptSelection();
}
bool OverviewController::IsInStartAnimation() {
return !start_animations_.empty();
}
bool OverviewController::IsCompletingShutdownAnimations() const {
return !delayed_animations_.empty();
}
void OverviewController::PauseOcclusionTracker() {
if (occlusion_tracker_pauser_)
return;
reset_pauser_task_.Cancel();
occlusion_tracker_pauser_ =
std::make_unique<aura::WindowOcclusionTracker::ScopedPause>();
}
void OverviewController::UnpauseOcclusionTracker(base::TimeDelta delay) {
reset_pauser_task_.Reset(base::BindOnce(&OverviewController::ResetPauser,
weak_ptr_factory_.GetWeakPtr()));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, reset_pauser_task_.callback(), delay);
}
void OverviewController::AddObserver(OverviewObserver* observer) {
observers_.AddObserver(observer);
}
void OverviewController::RemoveObserver(OverviewObserver* observer) {
observers_.RemoveObserver(observer);
}
void OverviewController::DelayedUpdateRoundedCornersAndShadow() {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&OverviewController::UpdateRoundedCornersAndShadow,
weak_ptr_factory_.GetWeakPtr()));
}
void OverviewController::AddExitAnimationObserver(
std::unique_ptr<DelayedAnimationObserver> animation_observer) {
DCHECK(IsCompletingShutdownAnimations() ||
overview_session_->enter_exit_overview_type() !=
OverviewEnterExitType::kImmediateExit);
animation_observer->SetOwner(this);
delayed_animations_.push_back(std::move(animation_observer));
}
void OverviewController::RemoveAndDestroyExitAnimationObserver(
DelayedAnimationObserver* animation_observer) {
const bool previous_empty = delayed_animations_.empty();
base::EraseIf(delayed_animations_,
base::MatchesUniquePtr(animation_observer));
if (!overview_session_ && !previous_empty && delayed_animations_.empty())
OnEndingAnimationComplete(false);
}
void OverviewController::AddEnterAnimationObserver(
std::unique_ptr<DelayedAnimationObserver> animation_observer) {
animation_observer->SetOwner(this);
start_animations_.push_back(std::move(animation_observer));
}
void OverviewController::RemoveAndDestroyEnterAnimationObserver(
DelayedAnimationObserver* animation_observer) {
const bool previous_empty = start_animations_.empty();
base::EraseIf(start_animations_, base::MatchesUniquePtr(animation_observer));
if (!previous_empty && start_animations_.empty())
OnStartingAnimationComplete(false);
}
void OverviewController::OnWindowActivating(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (InOverviewSession())
overview_session_->OnWindowActivating(reason, gained_active, lost_active);
}
std::vector<aura::Window*>
OverviewController::GetWindowsListInOverviewGridsForTest() {
std::vector<aura::Window*> windows;
for (const std::unique_ptr<OverviewGrid>& grid :
overview_session_->grid_list()) {
for (const auto& overview_item : grid->window_list())
windows.push_back(overview_item->GetWindow());
}
return windows;
}
void OverviewController::ToggleOverview(OverviewEnterExitType type) {
keyboard::KeyboardUIController::Get()->HideKeyboardImplicitlyByUser();
auto windows =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
auto should_hide_for_overview = [](aura::Window* w) -> bool {
if (w->GetProperty(kHideInOverviewKey))
return true;
return w == wm::GetTransientRoot(w) &&
!WindowState::Get(w)->IsUserPositionable();
};
std::vector<aura::Window*> hide_windows(windows.size());
auto end = base::ranges::copy_if(windows, hide_windows.begin(),
should_hide_for_overview);
hide_windows.resize(end - hide_windows.begin());
base::EraseIf(windows, window_util::ShouldExcludeForOverview);
window_util::EnsureTransientRoots(&windows);
if (InOverviewSession()) {
DCHECK(CanEndOverview(type));
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ui", "OverviewController::ExitOverview",
this);
PauseOcclusionTracker();
OverviewEnterExitType new_type =
MaybeOverrideEnterExitTypeForHomeScreen(type, false, windows);
overview_session_->set_enter_exit_overview_type(new_type);
overview_session_->set_is_shutting_down(true);
if (!start_animations_.empty())
OnStartingAnimationComplete(true);
start_animations_.clear();
if (type == OverviewEnterExitType::kFadeOutExit) {
std::vector<aura::Window*> windows_to_minimize(windows.size());
auto it = base::ranges::copy_if(
windows, windows_to_minimize.begin(), [](aura::Window* window) {
return !WindowState::Get(window)->IsMinimized();
});
windows_to_minimize.resize(
std::distance(windows_to_minimize.begin(), it));
window_util::MinimizeAndHideWithoutAnimation(windows_to_minimize);
}
overview_session_->UpdateRoundedCornersAndShadow();
for (auto& observer : observers_)
observer.OnOverviewModeEnding(overview_session_.get());
overview_session_->Shutdown();
const bool should_end_immediately =
overview_session_->enter_exit_overview_type() ==
OverviewEnterExitType::kImmediateExit;
if (should_end_immediately) {
for (const auto& animation : delayed_animations_)
animation->Shutdown();
delayed_animations_.clear();
OnEndingAnimationComplete(false);
}
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, overview_session_.release());
last_overview_session_time_ = base::Time::Now();
for (auto& observer : observers_)
observer.OnOverviewModeEnded();
if (!should_end_immediately && delayed_animations_.empty())
OnEndingAnimationComplete(false);
} else {
DCHECK(CanEnterOverview());
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ui", "OverviewController::EnterOverview",
this);
if (auto* active_window = window_util::GetActiveWindow(); active_window) {
auto* active_widget =
views::Widget::GetWidgetForNativeView(active_window);
if (active_widget)
paint_as_active_lock_ = active_widget->LockPaintAsActive();
}
Shell::Get()->frame_throttling_controller()->StartThrottling(windows);
for (const auto& animation : delayed_animations_)
animation->Shutdown();
if (!delayed_animations_.empty())
OnEndingAnimationComplete(true);
delayed_animations_.clear();
for (auto& observer : observers_)
observer.OnOverviewModeWillStart();
should_focus_overview_ = true;
const SplitViewController::State split_view_state =
SplitViewController::Get(Shell::GetPrimaryRootWindow())->state();
if (split_view_state == SplitViewController::State::kPrimarySnapped ||
split_view_state == SplitViewController::State::kSecondarySnapped) {
should_focus_overview_ = false;
} else {
if (auto* active_window = window_util::GetActiveWindow();
active_window && WindowState::Get(active_window)->is_dragged()) {
DCHECK(window_util::ShouldExcludeForOverview(active_window));
should_focus_overview_ = false;
}
}
PauseOcclusionTracker();
overview_session_ = std::make_unique<OverviewSession>(this);
OverviewEnterExitType new_type =
MaybeOverrideEnterExitTypeForHomeScreen(type, true, windows);
overview_session_->set_enter_exit_overview_type(new_type);
for (auto& observer : observers_)
observer.OnOverviewModeStarting();
overview_session_->Init(windows, hide_windows);
if (!chromeos::features::IsJellyrollEnabled()) {
overview_wallpaper_controller_->Blur(
new_type == OverviewEnterExitType::kFadeInEnter);
}
if (new_type == OverviewEnterExitType::kImmediateEnter &&
!delayed_animation_task_delay_.is_zero()) {
auto force_delay_observer =
std::make_unique<ForceDelayObserver>(delayed_animation_task_delay_);
AddEnterAnimationObserver(std::move(force_delay_observer));
}
if (start_animations_.empty())
OnStartingAnimationComplete(false);
if (!last_overview_session_time_.is_null()) {
UMA_HISTOGRAM_LONG_TIMES("Ash.Overview.TimeBetweenUse",
base::Time::Now() - last_overview_session_time_);
}
}
}
bool OverviewController::CanEnterOverview() {
if (IsSplitViewDividerDraggedOrAnimated())
return false;
if (!DesksController::Get()->CanEnterOverview())
return false;
SessionControllerImpl* session_controller =
Shell::Get()->session_controller();
return session_controller->GetSessionState() ==
session_manager::SessionState::ACTIVE &&
!Shell::IsSystemModalWindowOpen() &&
!Shell::Get()->screen_pinning_controller()->IsPinned() &&
!session_controller->IsRunningInAppMode();
}
bool OverviewController::CanEndOverview(OverviewEnterExitType type) {
if (IsSplitViewDividerDraggedOrAnimated())
return false;
SplitViewController* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
if (split_view_controller->InTabletSplitViewMode() &&
split_view_controller->state() !=
SplitViewController::State::kBothSnapped &&
InOverviewSession() && overview_session_->IsEmpty() &&
type != OverviewEnterExitType::kImmediateExit) {
return false;
}
return DesksController::Get()->CanEndOverview();
}
void OverviewController::OnStartingAnimationComplete(bool canceled) {
DCHECK(overview_session_);
if (!chromeos::features::IsJellyrollEnabled() && !canceled &&
overview_session_->enter_exit_overview_type() !=
OverviewEnterExitType::kFadeInEnter) {
overview_wallpaper_controller_->Blur(true);
}
for (auto& observer : observers_)
observer.OnOverviewModeStartingAnimationComplete(canceled);
DCHECK(overview_session_);
overview_session_->OnStartingAnimationComplete(canceled,
should_focus_overview_);
UnpauseOcclusionTracker(kOcclusionPauseDurationForStart);
TRACE_EVENT_NESTABLE_ASYNC_END1("ui", "OverviewController::EnterOverview",
this, "canceled", canceled);
}
void OverviewController::OnEndingAnimationComplete(bool canceled) {
for (auto& observer : observers_)
observer.OnOverviewModeEndingAnimationComplete(canceled);
UnpauseOcclusionTracker(occlusion_pause_duration_for_end_);
if (!canceled && !chromeos::features::IsJellyrollEnabled()) {
overview_wallpaper_controller_->Unblur();
paint_as_active_lock_.reset();
}
Shell::Get()->frame_throttling_controller()->EndThrottling();
TRACE_EVENT_NESTABLE_ASYNC_END1("ui", "OverviewController::ExitOverview",
this, "canceled", canceled);
}
void OverviewController::ResetPauser() {
if (!overview_session_) {
occlusion_tracker_pauser_.reset();
return;
}
const bool ignore_activations = overview_session_->ignore_activations();
overview_session_->set_ignore_activations(true);
occlusion_tracker_pauser_.reset();
overview_session_->set_ignore_activations(ignore_activations);
}
void OverviewController::UpdateRoundedCornersAndShadow() {
if (overview_session_)
overview_session_->UpdateRoundedCornersAndShadow();
}
}