#include "ash/wm/overview/overview_session.h"
#include <functional>
#include <utility>
#include "ash/accelerators/debug_commands.h"
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/metrics/user_metrics_recorder.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/root_window_settings.h"
#include "ash/scoped_animation_disabler.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/message_center/ash_message_popup_collection.h"
#include "ash/system/message_center/unified_message_center_bubble.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/unified/unified_system_tray_bubble.h"
#include "ash/utility/haptics_util.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/legacy_desk_bar_view.h"
#include "ash/wm/desks/templates/saved_desk_dialog_controller.h"
#include "ash/wm/desks/templates/saved_desk_grid_view.h"
#include "ash/wm/desks/templates/saved_desk_item_view.h"
#include "ash/wm/desks/templates/saved_desk_library_view.h"
#include "ash/wm/desks/templates/saved_desk_presenter.h"
#include "ash/wm/desks/templates/saved_desk_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_delegate.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_highlight_controller.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/overview_window_drag_controller.h"
#include "ash/wm/overview/scoped_float_container_stacker.h"
#include "ash/wm/overview/scoped_overview_animation_settings.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/auto_reset.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/ranges/algorithm.h"
#include "base/task/single_thread_task_runner.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/events/devices/haptic_touchpad_effects.h"
#include "ui/events/event.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
constexpr int kKeyboardPressScrollingDp = 75;
constexpr int kKeyboardHoldScrollingDp = 15;
bool EndOverview(OverviewEndAction action) {
return Shell::Get()->overview_controller()->EndOverview(action);
}
class AsyncWindowStateChangeObserver : public WindowStateObserver,
public aura::WindowObserver {
public:
AsyncWindowStateChangeObserver(
aura::Window* window,
base::OnceCallback<void(WindowState*)> on_post_window_state_changed)
: window_(window),
on_post_window_state_changed_(std::move(on_post_window_state_changed)) {
DCHECK(!on_post_window_state_changed_.is_null());
WindowState::Get(window_)->AddObserver(this);
window_->AddObserver(this);
}
~AsyncWindowStateChangeObserver() override { RemoveAllObservers(); }
AsyncWindowStateChangeObserver(const AsyncWindowStateChangeObserver&) =
delete;
AsyncWindowStateChangeObserver& operator=(
const AsyncWindowStateChangeObserver&) = delete;
void OnWindowDestroying(aura::Window* window) override { delete this; }
void OnPostWindowStateTypeChange(WindowState* window_state,
chromeos::WindowStateType) override {
RemoveAllObservers();
std::move(on_post_window_state_changed_).Run(window_state);
delete this;
}
private:
void RemoveAllObservers() {
WindowState::Get(window_)->RemoveObserver(this);
window_->RemoveObserver(this);
}
raw_ptr<aura::Window, ExperimentalAsh> window_;
base::OnceCallback<void(WindowState*)> on_post_window_state_changed_;
};
class OverviewFocusButton : public views::Button {
public:
OverviewFocusButton() : views::Button(views::Button::PressedCallback()) {
SetFocusBehavior(FocusBehavior::NEVER);
}
OverviewFocusButton(const OverviewFocusButton&) = delete;
OverviewFocusButton& operator=(const OverviewFocusButton&) = delete;
~OverviewFocusButton() override = default;
};
}
OverviewSession::OverviewSession(OverviewDelegate* delegate)
: delegate_(delegate),
overview_start_time_(base::Time::Now()),
highlight_controller_(
std::make_unique<OverviewHighlightController>(this)),
chromevox_enabled_(Shell::Get()
->accessibility_controller()
->spoken_feedback()
.enabled()) {
DCHECK(delegate_);
Shell::Get()->AddPreTargetHandler(this);
}
OverviewSession::~OverviewSession() {
if (window_drag_controller_) {
window_drag_controller_->ResetOverviewSession();
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, window_drag_controller_.release());
}
}
void OverviewSession::Init(const WindowList& windows,
const WindowList& hide_windows) {
Shell::Get()->AddShellObserver(this);
if (saved_desk_util::IsSavedDesksEnabled()) {
tablet_mode_observation_.Observe(Shell::Get()->tablet_mode_controller());
hide_windows_for_saved_desks_grid_ =
std::make_unique<ScopedOverviewHideWindows>(
std::vector<aura::Window*>({}), true);
}
hide_overview_windows_ = std::make_unique<ScopedOverviewHideWindows>(
std::move(hide_windows), false);
active_window_before_overview_ = window_util::GetActiveWindow();
if (active_window_before_overview_) {
active_window_before_overview_observation_.Observe(
active_window_before_overview_.get());
}
if (saved_desk_util::IsSavedDesksEnabled() && !saved_desk_presenter_) {
saved_desk_presenter_ = std::make_unique<SavedDeskPresenter>(this);
saved_desk_dialog_controller_ =
std::make_unique<SavedDeskDialogController>();
}
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
std::sort(root_windows.begin(), root_windows.end(),
[](const aura::Window* a, const aura::Window* b) {
return (a->GetBoundsInScreen().x() + a->GetBoundsInScreen().y()) <
(b->GetBoundsInScreen().x() + b->GetBoundsInScreen().y());
});
for (auto* root : root_windows) {
auto grid = std::make_unique<OverviewGrid>(root, windows, this);
num_items_ += grid->size();
grid_list_.push_back(std::move(grid));
}
for (std::unique_ptr<OverviewGrid>& overview_grid : grid_list_) {
overview_grid->PrepareForOverview();
if (ShouldEnterWithoutAnimations()) {
overview_grid->PositionWindows(false);
} else {
DCHECK_NE(enter_exit_overview_type_, OverviewEnterExitType::kFadeOutExit);
overview_grid->PositionWindows(true, {},
OverviewTransition::kEnter);
}
}
UpdateNoWindowsWidgetOnEachGrid();
overview_focus_widget_ = std::make_unique<views::Widget>();
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.accept_events = false;
params.bounds = gfx::Rect(0, 0, 2, 2);
params.layer_type = ui::LAYER_NOT_DRAWN;
params.name = "OverviewModeFocusWidget";
params.z_order = ui::ZOrderLevel::kFloatingWindow;
params.init_properties_container.SetProperty(ash::kExcludeInMruKey, true);
overview_focus_widget_->Init(std::move(params));
overview_focus_widget_->SetContentsView(
std::make_unique<OverviewFocusButton>());
UMA_HISTOGRAM_COUNTS_100("Ash.Overview.Items", num_items_);
SplitViewController::Get(Shell::GetPrimaryRootWindow())->AddObserver(this);
display_observer_.emplace(this);
base::RecordAction(base::UserMetricsAction("WindowSelector_Overview"));
Shell::Get()->accessibility_controller()->TriggerAccessibilityAlert(
AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED);
desks_controller_observation_.Observe(DesksController::Get());
ignore_activations_ = false;
}
void OverviewSession::Shutdown() {
bool was_saved_desk_library_showing = false;
for (auto& grid : grid_list_) {
if (grid->IsShowingSavedDeskLibrary()) {
was_saved_desk_library_showing = true;
break;
}
}
DCHECK(is_shutting_down_);
desks_controller_observation_.Reset();
if (observing_desk_) {
for (auto* root : Shell::GetAllRootWindows())
observing_desk_->GetDeskContainerForRoot(root)->RemoveObserver(this);
}
Shell::Get()->RemovePreTargetHandler(this);
Shell::Get()->RemoveShellObserver(this);
float_container_stacker_.reset();
tablet_mode_observation_.Reset();
saved_desk_presenter_.reset();
saved_desk_dialog_controller_.reset();
display_observer_.reset();
SplitViewController::Get(Shell::GetPrimaryRootWindow())->RemoveObserver(this);
size_t remaining_items = 0;
for (std::unique_ptr<OverviewGrid>& overview_grid : grid_list_) {
if (overview_grid->should_animate_when_exiting() &&
enter_exit_overview_type_ != OverviewEnterExitType::kImmediateExit) {
overview_grid->CalculateWindowListAnimationStates(
selected_item_ &&
selected_item_->overview_grid() == overview_grid.get()
? selected_item_.get()
: nullptr,
OverviewTransition::kExit, {});
}
for (const auto& overview_item : overview_grid->window_list()) {
overview_item->RestoreWindow(true,
was_saved_desk_library_showing);
}
remaining_items += overview_grid->size();
}
const bool should_restore =
enter_exit_overview_type_ == OverviewEnterExitType::kNormal ||
enter_exit_overview_type_ == OverviewEnterExitType::kImmediateExit;
RestoreWindowActivation(should_restore);
RemoveAllObservers();
for (std::unique_ptr<OverviewGrid>& overview_grid : grid_list_)
overview_grid->Shutdown(enter_exit_overview_type_);
DCHECK(num_items_ >= remaining_items);
if (!was_saved_desk_library_showing) {
UMA_HISTOGRAM_COUNTS_100("Ash.Overview.OverviewClosedItems",
num_items_ - remaining_items);
UMA_HISTOGRAM_MEDIUM_TIMES("Ash.Overview.TimeInOverview",
base::Time::Now() - overview_start_time_);
}
grid_list_.clear();
if (overview_focus_widget_)
overview_focus_widget_->Hide();
}
void OverviewSession::OnGridEmpty() {
if (!IsEmpty())
return;
if (SplitViewController::Get(Shell::GetPrimaryRootWindow())
->InTabletSplitViewMode()) {
UpdateNoWindowsWidgetOnEachGrid();
} else if (!allow_empty_desk_without_exiting_ &&
!IsShowingSavedDeskLibrary()) {
EndOverview(OverviewEndAction::kLastWindowRemoved);
}
}
void OverviewSession::IncrementSelection(bool forward) {
Move(!forward);
}
bool OverviewSession::AcceptSelection() {
return highlight_controller_->MaybeActivateHighlightedViewOnOverviewExit();
}
void OverviewSession::SelectWindow(OverviewItem* item) {
aura::Window* window = item->GetWindow();
aura::Window::Windows window_list =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
if (!window_list.empty()) {
if (window_list[0] != window) {
base::RecordAction(
base::UserMetricsAction("WindowSelector_ActiveWindowChanged"));
Shell::Get()->metrics()->task_switch_metrics_recorder().OnTaskSwitch(
TaskSwitchSource::OVERVIEW_MODE);
}
const auto it = base::ranges::find(window_list, window);
if (it != window_list.end()) {
UMA_HISTOGRAM_COUNTS_100("Ash.Overview.SelectionDepth",
1 + it - window_list.begin());
}
}
item->EnsureVisible();
if (window->GetProperty(kPipOriginalWindowKey)) {
window_util::ExpandArcPipWindow();
return;
}
auto* window_state = WindowState::Get(window);
if (window_state->IsMinimized()) {
ScopedAnimationDisabler disabler(window);
new AsyncWindowStateChangeObserver(
window, base::BindOnce([](WindowState* window_state) {
for (auto* window_iter : window_util::GetVisibleTransientTreeIterator(
window_state->window())) {
window_iter->layer()->SetOpacity(1.0);
}
wm::ActivateWindow(window_state->window());
}));
if (SplitViewController::Get(window)->InSplitViewMode())
window->Show();
else
window_state->Unminimize();
return;
}
wm::ActivateWindow(window);
}
void OverviewSession::SetSplitViewDragIndicatorsDraggedWindow(
aura::Window* dragged_window) {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_)
grid->SetSplitViewDragIndicatorsDraggedWindow(dragged_window);
}
void OverviewSession::UpdateSplitViewDragIndicatorsWindowDraggingStates(
const aura::Window* root_window_being_dragged_in,
SplitViewDragIndicators::WindowDraggingState
state_on_root_window_being_dragged_in) {
if (state_on_root_window_being_dragged_in ==
SplitViewDragIndicators::WindowDraggingState::kNoDrag) {
ResetSplitViewDragIndicatorsWindowDraggingStates();
return;
}
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
grid->SetSplitViewDragIndicatorsWindowDraggingState(
grid->root_window() == root_window_being_dragged_in
? state_on_root_window_being_dragged_in
: SplitViewDragIndicators::WindowDraggingState::kOtherDisplay);
}
}
void OverviewSession::ResetSplitViewDragIndicatorsWindowDraggingStates() {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
grid->SetSplitViewDragIndicatorsWindowDraggingState(
SplitViewDragIndicators::WindowDraggingState::kNoDrag);
}
}
void OverviewSession::RearrangeDuringDrag(OverviewItem* dragged_item) {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
DCHECK(grid->split_view_drag_indicators());
grid->RearrangeDuringDrag(
dragged_item,
grid->split_view_drag_indicators()->current_window_dragging_state());
}
}
void OverviewSession::UpdateDropTargetsBackgroundVisibilities(
OverviewItem* dragged_item,
const gfx::PointF& location_in_screen) {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
if (grid->GetDropTarget()) {
grid->UpdateDropTargetBackgroundVisibility(dragged_item,
location_in_screen);
}
}
}
OverviewGrid* OverviewSession::GetGridWithRootWindow(
aura::Window* root_window) {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
if (grid->root_window() == root_window)
return grid.get();
}
return nullptr;
}
void OverviewSession::AddItem(
aura::Window* window,
bool reposition,
bool animate,
const base::flat_set<OverviewItem*>& ignored_items,
size_t index) {
OverviewGrid* grid = GetGridWithRootWindow(window->GetRootWindow());
if (!grid || grid->GetOverviewItemContaining(window))
return;
base::AutoReset<bool> ignore(&ignore_activations_, true);
grid->AddItem(window, reposition, animate, ignored_items, index,
false, false);
OnItemAdded(window);
}
void OverviewSession::AppendItem(aura::Window* window,
bool reposition,
bool animate) {
OverviewGrid* grid = GetGridWithRootWindow(window->GetRootWindow());
if (!grid || grid->GetOverviewItemContaining(window))
return;
if (IsShowingSavedDeskLibrary())
animate = false;
base::AutoReset<bool> ignore(&ignore_activations_, true);
grid->AppendItem(window, reposition, animate, true);
OnItemAdded(window);
}
void OverviewSession::AddItemInMruOrder(aura::Window* window,
bool reposition,
bool animate,
bool restack,
bool use_spawn_animation) {
OverviewGrid* grid = GetGridWithRootWindow(window->GetRootWindow());
if (!grid || grid->GetOverviewItemContaining(window))
return;
base::AutoReset<bool> ignore(&ignore_activations_, true);
grid->AddItemInMruOrder(window, reposition, animate, restack,
use_spawn_animation);
OnItemAdded(window);
}
void OverviewSession::RemoveItem(OverviewItem* overview_item) {
RemoveItem(overview_item, false, false);
}
void OverviewSession::RemoveItem(OverviewItem* overview_item,
bool item_destroying,
bool reposition) {
if (overview_item->GetWindow() == active_window_before_overview_) {
active_window_before_overview_observation_.Reset();
active_window_before_overview_ = nullptr;
}
overview_item->overview_grid()->RemoveItem(overview_item, item_destroying,
reposition);
--num_items_;
UpdateNoWindowsWidgetOnEachGrid();
UpdateAccessibilityFocus();
}
void OverviewSession::RemoveDropTargets() {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
if (grid->GetDropTarget())
grid->RemoveDropTarget();
}
}
void OverviewSession::InitiateDrag(OverviewItem* item,
const gfx::PointF& location_in_screen,
bool is_touch_dragging) {
if (Shell::Get()->overview_controller()->IsInStartAnimation() ||
SplitViewController::Get(Shell::GetPrimaryRootWindow())
->IsDividerAnimating()) {
return;
}
highlight_controller_->SetFocusHighlightVisibility(false);
window_drag_controller_ = std::make_unique<OverviewWindowDragController>(
this, item, is_touch_dragging);
window_drag_controller_->InitiateDrag(location_in_screen);
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
grid->OnSelectorItemDragStarted(item);
grid->UpdateSaveDeskButtons();
}
if (!is_touch_dragging) {
haptics_util::PlayHapticTouchpadEffect(
ui::HapticTouchpadEffect::kTick,
ui::HapticTouchpadEffectStrength::kMedium);
}
}
void OverviewSession::Drag(OverviewItem* item,
const gfx::PointF& location_in_screen) {
DCHECK(window_drag_controller_);
DCHECK_EQ(item, window_drag_controller_->item());
window_drag_controller_->Drag(location_in_screen);
}
void OverviewSession::CompleteDrag(OverviewItem* item,
const gfx::PointF& location_in_screen) {
DCHECK(window_drag_controller_);
DCHECK_EQ(item, window_drag_controller_->item());
highlight_controller_->SetFocusHighlightVisibility(true);
const bool snap = window_drag_controller_->CompleteDrag(location_in_screen) ==
OverviewWindowDragController::DragResult::kSnap;
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
grid->OnSelectorItemDragEnded(snap);
grid->UpdateSaveDeskButtons();
}
}
void OverviewSession::StartNormalDragMode(
const gfx::PointF& location_in_screen) {
window_drag_controller_->StartNormalDragMode(location_in_screen);
}
void OverviewSession::Fling(OverviewItem* item,
const gfx::PointF& location_in_screen,
float velocity_x,
float velocity_y) {
if (!window_drag_controller_ || item != window_drag_controller_->item())
return;
const bool snap = window_drag_controller_->Fling(location_in_screen,
velocity_x, velocity_y) ==
OverviewWindowDragController::DragResult::kSnap;
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
grid->OnSelectorItemDragEnded(snap);
grid->UpdateSaveDeskButtons();
}
}
void OverviewSession::ActivateDraggedWindow() {
window_drag_controller_->ActivateDraggedWindow();
}
void OverviewSession::ResetDraggedWindowGesture() {
window_drag_controller_->ResetGesture();
for (std::unique_ptr<OverviewGrid>& grid : grid_list_) {
grid->OnSelectorItemDragEnded(false);
grid->UpdateSaveDeskButtons();
}
}
void OverviewSession::OnWindowDragStarted(aura::Window* dragged_window,
bool animate) {
OverviewGrid* target_grid =
GetGridWithRootWindow(dragged_window->GetRootWindow());
if (!target_grid)
return;
target_grid->OnWindowDragStarted(dragged_window, animate);
if (!float_container_stacker_) {
float_container_stacker_ = std::make_unique<ScopedFloatContainerStacker>();
}
float_container_stacker_->OnDragStarted(dragged_window);
}
void OverviewSession::OnWindowDragContinued(
aura::Window* dragged_window,
const gfx::PointF& location_in_screen,
SplitViewDragIndicators::WindowDraggingState window_dragging_state) {
OverviewGrid* target_grid =
GetGridWithRootWindow(dragged_window->GetRootWindow());
if (!target_grid)
return;
target_grid->OnWindowDragContinued(dragged_window, location_in_screen,
window_dragging_state);
}
void OverviewSession::OnWindowDragEnded(aura::Window* dragged_window,
const gfx::PointF& location_in_screen,
bool should_drop_window_into_overview,
bool snap) {
DCHECK(float_container_stacker_);
float_container_stacker_->OnDragFinished(dragged_window);
OverviewGrid* target_grid =
GetGridWithRootWindow(dragged_window->GetRootWindow());
if (!target_grid)
return;
target_grid->OnWindowDragEnded(dragged_window, location_in_screen,
should_drop_window_into_overview, snap);
}
void OverviewSession::MergeWindowIntoOverviewForWebUITabStrip(
aura::Window* dragged_window) {
OverviewGrid* target_grid =
GetGridWithRootWindow(dragged_window->GetRootWindow());
if (!target_grid)
return;
target_grid->MergeWindowIntoOverviewForWebUITabStrip(dragged_window);
}
void OverviewSession::SetVisibleDuringWindowDragging(bool visible,
bool animate) {
for (auto& grid : grid_list_)
grid->SetVisibleDuringWindowDragging(visible, animate);
}
void OverviewSession::PositionWindows(
bool animate,
const base::flat_set<OverviewItem*>& ignored_items) {
for (std::unique_ptr<OverviewGrid>& grid : grid_list_)
grid->PositionWindows(animate, ignored_items);
RefreshNoWindowsWidgetBoundsOnEachGrid(animate);
}
bool OverviewSession::IsWindowInOverview(const aura::Window* window) {
for (const std::unique_ptr<OverviewGrid>& grid : grid_list_) {
if (grid->GetOverviewItemContaining(window))
return true;
}
return false;
}
OverviewItem* OverviewSession::GetOverviewItemForWindow(
const aura::Window* window) {
for (const std::unique_ptr<OverviewGrid>& grid : grid_list_) {
OverviewItem* item = grid->GetOverviewItemContaining(window);
if (item)
return item;
}
return nullptr;
}
void OverviewSession::SetWindowListNotAnimatedWhenExiting(
aura::Window* root_window) {
OverviewGrid* grid = GetGridWithRootWindow(root_window);
if (grid)
grid->SetWindowListNotAnimatedWhenExiting();
}
void OverviewSession::UpdateRoundedCornersAndShadow() {
for (auto& grid : grid_list_)
for (auto& window : grid->window_list())
window->UpdateRoundedCornersAndShadow();
}
void OverviewSession::OnStartingAnimationComplete(bool canceled,
bool should_focus_overview) {
for (auto& grid : grid_list_)
grid->OnStartingAnimationComplete(canceled);
if (canceled)
return;
if (overview_focus_widget_) {
if (should_focus_overview) {
overview_focus_widget_->Show();
} else {
overview_focus_widget_->ShowInactive();
if (IsWindowInOverview(window_util::GetActiveWindow()) &&
SplitViewController::Get(Shell::GetPrimaryRootWindow())
->InSplitViewMode()) {
wm::ActivateWindow(
SplitViewController::Get(Shell::GetPrimaryRootWindow())
->GetDefaultSnappedWindow());
}
}
}
UpdateAccessibilityFocus();
Shell::Get()->overview_controller()->DelayedUpdateRoundedCornersAndShadow();
if (!float_container_stacker_) {
float_container_stacker_ = std::make_unique<ScopedFloatContainerStacker>();
}
}
void OverviewSession::OnWindowActivating(
::wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (ignore_activations_ || gained_active == GetOverviewFocusWindow())
return;
if (gained_active &&
(gained_active->GetId() == kShellWindowId_DesksBarWindow ||
gained_active->GetId() == kShellWindowId_SavedDeskLibraryWindow ||
gained_active->GetId() == kShellWindowId_SaveDeskButtonContainer)) {
return;
}
if (gained_active && saved_desk_util::IsSavedDesksEnabled()) {
if (ShouldKeepOverviewOpenForSavedDeskDialog(gained_active, lost_active))
return;
}
if (DesksController::Get()->AreDesksBeingModified()) {
return;
}
if (!gained_active) {
RestoreWindowActivation(false);
EndOverview(OverviewEndAction::kWindowActivating);
return;
}
if (gained_active->GetName() ==
AshMessagePopupCollection::kMessagePopupWidgetName) {
return;
}
for (RootWindowController* root_window_controller :
Shell::GetAllRootWindowControllers()) {
UnifiedSystemTray* system_tray =
root_window_controller->GetStatusAreaWidget()->unified_system_tray();
if (system_tray->IsMessageCenterBubbleShown()) {
if (gained_active == system_tray->message_center_bubble()
->GetBubbleWidget()
->GetNativeWindow()) {
return;
}
}
}
if (gained_active == Shell::Get()->app_list_controller()->GetWindow() &&
!Shell::Get()->tablet_mode_controller()->InTabletMode()) {
RestoreWindowActivation(false);
EndOverview(OverviewEndAction::kAppListActivatedInClamshell);
return;
}
SplitViewController* split_view_controller =
SplitViewController::Get(gained_active);
if (split_view_controller->primary_window() ||
split_view_controller->secondary_window() ||
split_view_controller->IsWindowInTransitionalState(gained_active)) {
RestoreWindowActivation(false);
return;
}
for (std::unique_ptr<OverviewGrid>& overview_grid : grid_list_) {
if (overview_grid->GetDropTarget())
return;
}
auto* grid = GetGridWithRootWindow(gained_active->GetRootWindow());
DCHECK(grid);
if (OverviewItem* item = grid->GetOverviewItemContaining(gained_active))
selected_item_ = item;
RestoreWindowActivation(false);
EndOverview(OverviewEndAction::kWindowActivating);
}
bool OverviewSession::IsSavedDeskUiLosingActivation(aura::Window* lost_active) {
if (!saved_desk_util::IsSavedDesksEnabled() || !lost_active)
return false;
for (auto& grid : grid_list_) {
auto* desk_library_view = grid->GetSavedDeskLibraryView();
if (desk_library_view &&
lost_active == desk_library_view->GetWidget()->GetNativeWindow()) {
return true;
}
}
return saved_desk_dialog_controller_ &&
saved_desk_dialog_controller_->dialog_widget() &&
saved_desk_dialog_controller_->dialog_widget()->GetNativeWindow() ==
lost_active;
}
aura::Window* OverviewSession::GetOverviewFocusWindow() {
if (overview_focus_widget_)
return overview_focus_widget_->GetNativeWindow();
return nullptr;
}
aura::Window* OverviewSession::GetHighlightedWindow() {
OverviewItem* item = highlight_controller_->GetHighlightedItem();
if (!item)
return nullptr;
return item->GetWindow();
}
void OverviewSession::SuspendReposition() {
for (auto& grid : grid_list_)
grid->set_suspend_reposition(true);
}
void OverviewSession::ResumeReposition() {
for (auto& grid : grid_list_)
grid->set_suspend_reposition(false);
}
bool OverviewSession::IsEmpty() const {
for (const auto& grid : grid_list_) {
if (!grid->empty())
return false;
}
return true;
}
void OverviewSession::RestoreWindowActivation(bool restore) {
if (!active_window_before_overview_)
return;
restore &= base::Contains(DesksController::Get()->active_desk()->windows(),
active_window_before_overview_);
if (restore && active_window_before_overview_->GetRootWindow()) {
base::AutoReset<bool> restoring_focus(&ignore_activations_, true);
wm::ActivateWindow(active_window_before_overview_);
}
active_window_before_overview_observation_.Reset();
active_window_before_overview_ = nullptr;
}
void OverviewSession::OnHighlightedItemActivated(OverviewItem* item) {
UMA_HISTOGRAM_COUNTS_100("Ash.Overview.ArrowKeyPresses", num_key_presses_);
UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.Overview.KeyPressesOverItemsRatio",
(num_key_presses_ * 100) / num_items_, 1, 300,
30);
base::RecordAction(
base::UserMetricsAction("WindowSelector_OverviewEnterKey"));
SelectWindow(item);
}
void OverviewSession::OnHighlightedItemClosed(OverviewItem* item) {
base::RecordAction(
base::UserMetricsAction("WindowSelector_OverviewCloseKey"));
item->CloseWindow();
}
void OverviewSession::OnRootWindowClosing(aura::Window* root) {
auto iter = base::ranges::find(grid_list_, root, &OverviewGrid::root_window);
DCHECK(iter != grid_list_.end());
(*iter)->Shutdown(OverviewEnterExitType::kImmediateExit);
grid_list_.erase(iter);
}
OverviewItem* OverviewSession::GetCurrentDraggedOverviewItem() const {
if (!window_drag_controller_)
return nullptr;
return window_drag_controller_->item();
}
bool OverviewSession::CanProcessEvent() const {
return CanProcessEvent(nullptr, false);
}
bool OverviewSession::CanProcessEvent(OverviewItem* sender,
bool from_touch_gesture) const {
const bool drag_in_progress = window_util::IsAnyWindowDragged();
if (!drag_in_progress)
return true;
if (!sender || !window_drag_controller_)
return false;
if (sender == window_drag_controller_->item() &&
from_touch_gesture == window_drag_controller_->is_touch_dragging()) {
return true;
}
return false;
}
bool OverviewSession::IsWindowActiveWindowBeforeOverview(
aura::Window* window) const {
DCHECK(window);
return window == active_window_before_overview_;
}
void OverviewSession::ShowSavedDeskLibrary(
const base::Uuid& item_to_focus,
const std::u16string& saved_desk_name,
aura::Window* const root_window) {
if (Shell::Get()->tablet_mode_controller()->InTabletMode() ||
IsShowingSavedDeskLibrary()) {
return;
}
const bool created_grid_widgets =
!grid_list_.front()->GetSavedDeskLibraryView();
Shell::Get()->accessibility_controller()->TriggerAccessibilityAlert(
AccessibilityAlert::SAVED_DESKS_MODE_ENTERED);
for (auto& grid : grid_list_)
grid->ShowSavedDeskLibrary();
if (created_grid_widgets) {
saved_desk_presenter_->GetAllEntries(item_to_focus, saved_desk_name,
root_window);
}
UpdateNoWindowsWidgetOnEachGrid();
UpdateAccessibilityFocus();
highlight_controller_->ResetHighlightedView();
if (item_to_focus.is_valid())
return;
OverviewGrid* overview_grid = GetGridWithRootWindow(root_window);
if (!overview_grid)
return;
SavedDeskLibraryView* library_view = overview_grid->GetSavedDeskLibraryView();
if (!library_view)
return;
std::vector<SavedDeskGridView*> grid_views = library_view->grid_views();
if (grid_views.empty())
return;
std::vector<SavedDeskItemView*> grid_items = grid_views.front()->grid_items();
if (grid_items.empty() ||
library_view->GetWidget()->GetNativeWindow()->GetRootWindow() !=
root_window) {
return;
}
highlight_controller_->MoveHighlightToView(
grid_items.front(), false);
}
void OverviewSession::HideSavedDeskLibrary() {
wm::ActivateWindow(GetOverviewFocusWindow());
for (auto& grid : grid_list_)
grid->HideSavedDeskLibrary(false);
UpdateAccessibilityFocus();
}
bool OverviewSession::IsShowingSavedDeskLibrary() const {
return grid_list_.empty() ? false
: grid_list_.front()->IsShowingSavedDeskLibrary();
}
bool OverviewSession::WillShowSavedDeskLibrary() const {
return grid_list_.empty() ? false
: grid_list_.front()->WillShowSavedDeskLibrary();
}
bool OverviewSession::ShouldEnterWithoutAnimations() const {
return enter_exit_overview_type_ == OverviewEnterExitType::kImmediateEnter ||
enter_exit_overview_type_ ==
OverviewEnterExitType::kImmediateEnterWithoutFocus;
}
void OverviewSession::UpdateAccessibilityFocus() {
if (is_shutting_down())
return;
std::vector<views::Widget*> a11y_widgets;
if (overview_focus_widget_)
a11y_widgets.push_back(overview_focus_widget_.get());
for (auto& grid : grid_list_) {
if (grid->IsShowingSavedDeskLibrary()) {
a11y_widgets.push_back(grid->saved_desk_library_widget());
} else {
for (const auto& item : grid->window_list())
a11y_widgets.push_back(item->item_widget());
}
if (grid->desks_widget())
a11y_widgets.push_back(const_cast<views::Widget*>(grid->desks_widget()));
if (grid->IsSaveDeskButtonContainerVisible())
a11y_widgets.push_back(grid->save_desk_button_container_widget());
auto* no_windows_widget = grid->no_windows_widget();
if (no_windows_widget) {
a11y_widgets.push_back(
static_cast<views::Widget*>(grid->no_windows_widget()));
}
}
if (a11y_widgets.empty())
return;
auto get_view_a11y = [&a11y_widgets](int index) -> views::ViewAccessibility& {
return a11y_widgets[index]->GetContentsView()->GetViewAccessibility();
};
if (a11y_widgets.size() == 1) {
get_view_a11y(0).OverridePreviousFocus(nullptr);
get_view_a11y(0).OverrideNextFocus(nullptr);
a11y_widgets[0]->GetContentsView()->NotifyAccessibilityEvent(
ax::mojom::Event::kTreeChanged, true);
return;
}
int size = a11y_widgets.size();
for (int i = 0; i < size; ++i) {
int previous_index = (i + size - 1) % size;
int next_index = (i + 1) % size;
get_view_a11y(i).OverridePreviousFocus(a11y_widgets[previous_index]);
get_view_a11y(i).OverrideNextFocus(a11y_widgets[next_index]);
a11y_widgets[i]->GetContentsView()->NotifyAccessibilityEvent(
ax::mojom::Event::kTreeChanged, true);
}
}
void OverviewSession::OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) {
observing_desk_ = activated;
for (auto* root : Shell::GetAllRootWindows()) {
activated->GetDeskContainerForRoot(root)->AddObserver(this);
deactivated->GetDeskContainerForRoot(root)->RemoveObserver(this);
if (auto* overview_grid = GetGridWithRootWindow(root))
overview_grid->UpdateSaveDeskButtons();
}
}
void OverviewSession::OnDisplayAdded(const display::Display& display) {
if (EndOverview(OverviewEndAction::kDisplayAdded))
return;
SplitViewController::Get(Shell::GetPrimaryRootWindow())->EndSplitView();
EndOverview(OverviewEndAction::kDisplayAdded);
}
void OverviewSession::OnDisplayMetricsChanged(const display::Display& display,
uint32_t metrics) {
if (window_drag_controller_ && window_drag_controller_->item())
ResetDraggedWindowGesture();
auto* overview_grid =
GetGridWithRootWindow(Shell::GetRootWindowForDisplayId(display.id()));
overview_grid->OnDisplayMetricsChanged();
if (SplitViewController::Get(Shell::GetPrimaryRootWindow())
->InSplitViewMode()) {
return;
}
overview_grid->RefreshNoWindowsWidgetBounds(false);
}
void OverviewSession::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(active_window_before_overview_, window);
active_window_before_overview_observation_.Reset();
active_window_before_overview_ = nullptr;
}
void OverviewSession::OnWindowAdded(aura::Window* new_window) {
if (!auto_add_windows_enabled_)
return;
if (is_adding_new_item_)
return;
base::AutoReset<bool> adding_new_item_resetter(&is_adding_new_item_, true);
if (!WindowState::Get(new_window) ||
window_util::ShouldExcludeForOverview(new_window)) {
return;
}
AddItemInMruOrder(new_window, true, true,
false, true);
active_window_before_overview_observation_.Reset();
active_window_before_overview_ = nullptr;
}
void OverviewSession::OnKeyEvent(ui::KeyEvent* event) {
Shell* shell = Shell::Get();
if (!shell->tablet_mode_controller()->InTabletMode() &&
shell->app_list_controller()->IsVisible()) {
return;
}
if (saved_desk_dialog_controller_ &&
saved_desk_dialog_controller_->dialog_widget()) {
return;
}
const ui::KeyboardCode key_code = event->key_code();
const bool is_key_press = event->type() == ui::ET_KEY_PRESSED;
const bool should_commit_name_changes =
is_key_press && key_code == ui::VKEY_TAB;
for (auto& grid : grid_list_) {
if (grid->IsDeskNameBeingModified() ||
grid->IsSavedDeskNameBeingModified()) {
if (!should_commit_name_changes)
return;
grid->CommitNameChanges();
break;
}
}
if (ProcessForScrolling(*event)) {
event->SetHandled();
event->StopPropagation();
return;
}
if (!is_key_press)
return;
const bool is_control_down = event->IsControlDown();
switch (key_code) {
case ui::VKEY_BROWSER_BACK:
case ui::VKEY_ESCAPE:
EndOverview(OverviewEndAction::kKeyEscapeOrBack);
break;
case ui::VKEY_UP:
++num_key_presses_;
Move(true);
break;
case ui::VKEY_DOWN:
++num_key_presses_;
Move(false);
break;
case ui::VKEY_RIGHT:
++num_key_presses_;
if (!is_control_down ||
!highlight_controller_->MaybeSwapHighlightedView(true)) {
Move(false);
}
break;
case ui::VKEY_TAB: {
const bool reverse = event->IsShiftDown();
++num_key_presses_;
Move(reverse);
break;
}
case ui::VKEY_LEFT:
++num_key_presses_;
if (!is_control_down ||
!highlight_controller_->MaybeSwapHighlightedView(false)) {
Move(true);
}
break;
case ui::VKEY_W: {
if (!is_control_down)
return;
const bool primary_action = !event->IsShiftDown();
if (!highlight_controller_->MaybeCloseHighlightedView(primary_action))
return;
break;
}
case ui::VKEY_Z: {
if (!is_control_down || (is_control_down && event->IsAltDown())) {
return;
}
DesksController::Get()->MaybeCancelDeskRemoval();
break;
}
case ui::VKEY_RETURN: {
if (!highlight_controller_->MaybeActivateHighlightedView())
return;
break;
}
default: {
if (shell->overview_controller()->IsInStartAnimation())
break;
return;
}
}
event->SetHandled();
event->StopPropagation();
}
void OverviewSession::OnShellDestroying() {
EndOverview(OverviewEndAction::kShuttingDown);
}
void OverviewSession::OnShelfAlignmentChanged(aura::Window* root_window,
ShelfAlignment old_alignment) {
auto same_effective_alignment = [](ShelfAlignment prev,
ShelfAlignment curr) -> bool {
auto bottom = ShelfAlignment::kBottom;
auto locked = ShelfAlignment::kBottomLocked;
return (prev == bottom && curr == locked) ||
(prev == locked && curr == bottom);
};
ShelfAlignment current_alignment = Shelf::ForWindow(root_window)->alignment();
if (SplitViewController::Get(root_window)->InSplitViewMode() &&
same_effective_alignment(old_alignment, current_alignment)) {
return;
}
EndOverview(OverviewEndAction::kShelfAlignmentChanged);
}
void OverviewSession::OnUserWorkAreaInsetsChanged(aura::Window* root_window) {
if (root_window != Shell::GetPrimaryRootWindow())
return;
const bool new_chromevox_enabled =
Shell::Get()->accessibility_controller()->spoken_feedback().enabled();
if (new_chromevox_enabled == chromevox_enabled_)
return;
chromevox_enabled_ = new_chromevox_enabled;
for (std::unique_ptr<OverviewGrid>& overview_grid : grid_list_) {
if (root_window == overview_grid->root_window())
overview_grid->OnUserWorkAreaInsetsChanged(root_window);
}
}
void OverviewSession::OnSplitViewStateChanged(
SplitViewController::State previous_state,
SplitViewController::State state) {
if (!Shell::Get()->overview_controller()->InOverviewSession())
return;
RefreshNoWindowsWidgetBoundsOnEachGrid(false);
}
void OverviewSession::OnSplitViewDividerPositionChanged() {
RefreshNoWindowsWidgetBoundsOnEachGrid(false);
}
void OverviewSession::OnTabletModeStarted() {
OnTabletModeChanged();
}
void OverviewSession::OnTabletModeEnded() {
OnTabletModeChanged();
}
void OverviewSession::OnTabletModeChanged() {
DCHECK(saved_desk_util::IsSavedDesksEnabled());
DCHECK(saved_desk_presenter_);
saved_desk_presenter_->UpdateUIForSavedDeskLibrary();
}
void OverviewSession::Move(bool reverse) {
if (window_util::IsAnyWindowDragged() || desks_util::IsDraggingAnyDesk())
return;
highlight_controller_->MoveHighlight(reverse);
}
bool OverviewSession::ProcessForScrolling(const ui::KeyEvent& event) {
if (!ShouldUseTabletModeGridLayout())
return false;
auto* grid = GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
const bool press = (event.type() == ui::ET_KEY_PRESSED);
if (!press) {
if (is_keyboard_scrolling_grid_) {
is_keyboard_scrolling_grid_ = false;
grid->EndScroll();
return true;
}
return false;
}
if (event.key_code() != ui::VKEY_LEFT && event.key_code() != ui::VKEY_RIGHT)
return false;
if (!event.IsControlDown())
return false;
const bool repeat = event.is_repeat();
const bool reverse = event.key_code() == ui::VKEY_LEFT;
if (!repeat) {
is_keyboard_scrolling_grid_ = true;
grid->StartScroll();
grid->UpdateScrollOffset(kKeyboardPressScrollingDp * (reverse ? 1 : -1));
return true;
}
grid->UpdateScrollOffset(kKeyboardHoldScrollingDp * (reverse ? 1 : -1));
return true;
}
void OverviewSession::RemoveAllObservers() {
display_observer_.reset();
active_window_before_overview_observation_.Reset();
active_window_before_overview_ = nullptr;
}
void OverviewSession::UpdateNoWindowsWidgetOnEachGrid() {
if (is_shutting_down_)
return;
for (auto& grid : grid_list_)
grid->UpdateNoWindowsWidget(IsEmpty());
}
void OverviewSession::RefreshNoWindowsWidgetBoundsOnEachGrid(bool animate) {
if (!IsEmpty())
return;
for (auto& grid : grid_list_)
grid->RefreshNoWindowsWidgetBounds(animate);
}
void OverviewSession::OnItemAdded(aura::Window* window) {
++num_items_;
UpdateNoWindowsWidgetOnEachGrid();
OverviewGrid* grid = GetGridWithRootWindow(window->GetRootWindow());
if (grid && grid->IsDropTargetWindow(window))
return;
DCHECK(overview_focus_widget_);
bool saved_desk_grid_should_keep_focus =
IsShowingSavedDeskLibrary() || WillShowSavedDeskLibrary();
if (saved_desk_grid_should_keep_focus)
overview_focus_widget_->ShowInactive();
else
overview_focus_widget_->Show();
UpdateAccessibilityFocus();
}
bool OverviewSession::ShouldKeepOverviewOpenForSavedDeskDialog(
aura::Window* gained_active,
aura::Window* lost_active) {
DCHECK(saved_desk_util::IsSavedDesksEnabled());
const views::Widget* dialog_widget =
saved_desk_dialog_controller_->dialog_widget();
if (!dialog_widget)
return false;
auto* dialog_window = dialog_widget->GetNativeWindow();
return gained_active == dialog_window || lost_active == dialog_window;
}
void OverviewSession::UpdateFrameThrottling() {
std::vector<aura::Window*> windows_to_throttle;
if (!grid_list_.empty()) {
windows_to_throttle.reserve(grid_list_.size() * grid_list_[0]->size() * 2);
for (auto& grid : grid_list_) {
for (auto& item : grid->window_list())
windows_to_throttle.push_back(item->GetWindow());
}
}
Shell::Get()->frame_throttling_controller()->StartThrottling(
windows_to_throttle);
}
}