#include "ash/wm/overview/overview_controller.h"
#include <algorithm>
#include <utility>
#include <vector>
#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/desks/overview_desk_bar_view.h"
#include "ash/wm/gestures/wm_gesture_handler.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/screen_pinning_controller.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/window_restore/informed_restore_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
OverviewController* g_instance = nullptr;
constexpr base::TimeDelta kOcclusionPauseDurationForStart =
base::Milliseconds(50);
constexpr base::TimeDelta kOcclusionPauseDurationForEnd =
base::Milliseconds(500);
OverviewEnterExitType MaybeOverrideEnterExitType(
OverviewEnterExitType original_type,
bool enter,
const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) {
if (original_type != OverviewEnterExitType::kNormal) {
return original_type;
}
if (!!Shell::Get()->informed_restore_controller()->contents_data()) {
return OverviewEnterExitType::kInformedRestore;
}
if (!display::Screen::Get()->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::ScopedOcclusionPauser::ScopedOcclusionPauser(
ScopedOcclusionPauser&&) = default;
OverviewController::ScopedOcclusionPauser&
OverviewController::ScopedOcclusionPauser::operator=(ScopedOcclusionPauser&&) =
default;
OverviewController::ScopedOcclusionPauser::~ScopedOcclusionPauser() {
if (controller_) {
controller_->MaybeUnpauseOcclusionTracker(unpause_delay_);
}
}
OverviewController::ScopedOcclusionPauser::ScopedOcclusionPauser(
base::WeakPtr<OverviewController> controller,
base::TimeDelta unpause_delay)
: controller_(controller), unpause_delay_(unpause_delay) {
controller_->MaybePauseOcclusionTracker();
}
OverviewController::OverviewController()
: occlusion_pause_duration_for_end_(kOcclusionPauseDurationForEnd),
delayed_animation_task_delay_(kTransition),
overview_window_occlusion_calculator_(this) {
Shell::Get()->activation_client()->AddObserver(this);
CHECK_EQ(g_instance, nullptr);
g_instance = this;
}
OverviewController::~OverviewController() {
Shell::Get()->activation_client()->RemoveObserver(this);
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();
}
CHECK_EQ(g_instance, this);
g_instance = nullptr;
}
OverviewController::ScopedOcclusionPauser
OverviewController::PauseOcclusionTracker(base::TimeDelta unpause_delay) {
return ScopedOcclusionPauser(weak_ptr_factory_.GetWeakPtr(), unpause_delay);
}
OverviewController* OverviewController::Get() {
CHECK(g_instance);
return g_instance;
}
bool OverviewController::StartOverview(OverviewStartAction start_action,
OverviewEnterExitType type) {
if (InOverviewSession())
return true;
if (!CanEnterOverview())
return false;
session_metrics_recorder_.emplace(start_action, this);
ToggleOverview(type);
return true;
}
bool OverviewController::EndOverview(OverviewEndAction end_action,
OverviewEnterExitType type) {
if (!InOverviewSession())
return true;
if (!CanEndOverview(type))
return false;
overview_session_->set_overview_end_action(end_action);
ToggleOverview(type);
DesksController::Get()->MaybeDismissPersistentDeskRemovalToast();
return true;
}
bool OverviewController::CanEnterOverview() const {
if (!DesksController::Get()->CanEnterOverview()) {
return false;
}
Shell* shell = Shell::Get();
if (Shell::IsSystemModalWindowOpen() ||
shell->screen_pinning_controller()->IsPinned() ||
chromeos::IsKioskSession()) {
return false;
}
const session_manager::SessionState session_state =
shell->session_controller()->GetSessionState();
return session_state == session_manager::SessionState::ACTIVE ||
session_state == session_manager::SessionState::LOGGED_IN_NOT_ACTIVE;
}
bool OverviewController::InOverviewSession() const {
return overview_session_ && !overview_session_->is_shutting_down();
}
bool OverviewController::HandleContinuousScroll(float y_offset,
OverviewEnterExitType type) {
CHECK((type ==
OverviewEnterExitType::kContinuousAnimationEnterOnScrollUpdate) ||
(type == OverviewEnterExitType::kNormal));
is_continuous_scroll_in_progress_ =
y_offset != WmGestureHandler::kVerticalThresholdDp &&
type != OverviewEnterExitType::kNormal;
if (!overview_session_) {
session_metrics_recorder_.emplace(
OverviewStartAction::k3FingerVerticalScroll, this);
ToggleOverview(type);
return true;
}
overview_session_->set_enter_exit_overview_type(type);
return overview_session_->HandleContinuousScrollIntoOverview(y_offset);
}
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::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();
std::erase_if(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();
std::erase_if(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);
}
base::AutoReset<bool> OverviewController::SetDisableAppIdCheckForTests() {
return {&disable_app_id_check_for_saved_desks_, true};
}
void OverviewController::ToggleOverview(OverviewEnterExitType type) {
aura::WindowOcclusionTracker::ScopedPause scoped_pause_occlusion;
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<raw_ptr<aura::Window, VectorExperimental>> hide_windows(
windows.size());
auto end = std::ranges::copy_if(windows, hide_windows.begin(),
should_hide_for_overview)
.out;
hide_windows.resize(end - hide_windows.begin());
std::erase_if(windows, window_util::ShouldExcludeForOverview);
window_util::EnsureTransientRoots(&windows);
if (InOverviewSession()) {
DCHECK(CanEndOverview(type));
CHECK(session_metrics_recorder_);
session_metrics_recorder_->OnOverviewSessionEnding();
exit_pauser_ = PauseOcclusionTracker(occlusion_pause_duration_for_end_);
OverviewEnterExitType new_type =
MaybeOverrideEnterExitType(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();
for (auto& observer : observers_) {
observer.OnOverviewModeEnding(overview_session_.get());
}
if (type == OverviewEnterExitType::kFadeOutExit) {
std::vector<raw_ptr<aura::Window, VectorExperimental>>
windows_to_minimize(windows.size());
auto it = std::ranges::copy_if(
windows, windows_to_minimize.begin(),
[](aura::Window* window) {
return !WindowState::Get(window)->IsMinimized();
})
.out;
windows_to_minimize.resize(
std::distance(windows_to_minimize.begin(), it));
window_util::MinimizeAndHideWithoutAnimation(windows_to_minimize);
}
overview_session_->UpdateRoundedCornersAndShadow();
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());
CHECK(session_metrics_recorder_);
session_metrics_recorder_->OnOverviewSessionInitializing();
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();
}
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;
}
}
enter_pauser_ = PauseOcclusionTracker(kOcclusionPauseDurationForStart);
overview_session_ = std::make_unique<OverviewSession>(this);
OverviewEnterExitType new_type =
MaybeOverrideEnterExitType(type, true, windows);
overview_session_->set_enter_exit_overview_type(new_type);
for (auto& observer : observers_)
observer.OnOverviewModeStarting();
overview_session_->Init(
windows, hide_windows,
overview_window_occlusion_calculator_.GetCalculator());
overview_session_->UpdateFrameThrottling();
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);
session_metrics_recorder_->OnOverviewSessionInitialized(
overview_session_.get());
if (!last_overview_session_time_.is_null()) {
UMA_HISTOGRAM_LONG_TIMES("Ash.Overview.TimeBetweenUse",
base::Time::Now() - last_overview_session_time_);
}
}
}
bool OverviewController::CanEndOverview(OverviewEnterExitType type) const {
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) {
CHECK(overview_session_);
overview_session_->OnStartingAnimationComplete(canceled,
should_focus_overview_);
for (auto& observer : observers_) {
observer.OnOverviewModeStartingAnimationComplete(canceled);
}
enter_pauser_.reset();
}
void OverviewController::OnEndingAnimationComplete(bool canceled) {
for (auto& observer : observers_)
observer.OnOverviewModeEndingAnimationComplete(canceled);
exit_pauser_.reset();
if (!canceled) {
paint_as_active_lock_.reset();
}
Shell::Get()->frame_throttling_controller()->EndThrottling();
}
void OverviewController::MaybePauseOcclusionTracker() {
pause_count_++;
if (pause_count_ > 1) {
return;
}
reset_pauser_task_.Cancel();
occlusion_tracker_pauser_ =
std::make_unique<aura::WindowOcclusionTracker::ScopedPause>();
}
void OverviewController::MaybeUnpauseOcclusionTracker(base::TimeDelta delay) {
pause_count_--;
if (pause_count_ > 0) {
return;
}
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::ResetPauser() {
CHECK_EQ(pause_count_, 0);
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();
if (overview_session_) {
overview_session_->set_ignore_activations(ignore_activations);
}
}
void OverviewController::UpdateRoundedCornersAndShadow() {
if (overview_session_)
overview_session_->UpdateRoundedCornersAndShadow();
}
}