#include "ash/wm/multi_display/persistent_window_controller.h"
#include "base/memory/raw_ptr.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/multi_display/persistent_window_info.h"
#include "ash/wm/tablet_mode/scoped_skip_user_session_blocked_check.h"
#include "ash/wm/window_state.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/ui/base/display_util.h"
#include "ui/display/manager/display_manager.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
constexpr int kMaxRestoredWindowCount = 50;
display::DisplayManager* GetDisplayManager() {
return Shell::Get()->display_manager();
}
MruWindowTracker::WindowList GetWindowList() {
ScopedSkipUserSessionBlockedCheck scoped_skip_user_session_blocked_check;
return Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kAllDesks);
}
bool ShouldProcessWindowList() {
if (!Shell::Get()->desks_controller()) {
return false;
}
return !GetDisplayManager()->IsInMirrorMode();
}
}
PersistentWindowController::WindowTracker::WindowTracker() = default;
PersistentWindowController::WindowTracker::~WindowTracker() {
RemoveAll();
}
void PersistentWindowController::WindowTracker::Add(
aura::Window* window,
const gfx::Rect& restore_bounds_in_parent) {
if (window_restore_bounds_map_.emplace(window, restore_bounds_in_parent)
.second) {
window->AddObserver(this);
}
}
void PersistentWindowController::WindowTracker::RemoveAll() {
for (auto& item : window_restore_bounds_map_) {
item.first->RemoveObserver(this);
}
window_restore_bounds_map_.clear();
}
void PersistentWindowController::WindowTracker::Remove(aura::Window* window) {
auto iter = window_restore_bounds_map_.find(window);
if (iter != window_restore_bounds_map_.end()) {
iter->first->RemoveObserver(this);
window_restore_bounds_map_.erase(iter);
}
}
void PersistentWindowController::WindowTracker::OnWindowDestroying(
aura::Window* window) {
Remove(window);
}
constexpr char
PersistentWindowController::kNumOfWindowsRestoredOnDisplayAdded[];
constexpr char
PersistentWindowController::kNumOfWindowsRestoredOnScreenRotation[];
PersistentWindowController::PersistentWindowController() {
display_manager_observation_.Observe(Shell::Get()->display_manager());
Shell::Get()->session_controller()->AddObserver(this);
}
PersistentWindowController::~PersistentWindowController() {
Shell::Get()->session_controller()->RemoveObserver(this);
}
void PersistentWindowController::OnDisplayAdded(
const display::Display& new_display) {
display_added_restore_callback_ =
base::BindOnce(&PersistentWindowController::
MaybeRestorePersistentWindowBoundsOnDisplayAdded,
base::Unretained(this));
}
void PersistentWindowController::OnWillRemoveDisplays(
const display::Displays& removed_displays) {
for (const auto& [window, restore_bounds_in_parent] :
need_persistent_info_windows_.window_restore_bounds_map()) {
WindowState* window_state = WindowState::Get(window);
window_state->CreatePersistentWindowInfo(
false, restore_bounds_in_parent,
true);
}
need_persistent_info_windows_.RemoveAll();
for (const auto& removed : removed_displays) {
is_landscape_orientation_map_.erase(removed.id());
}
}
void PersistentWindowController::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
if (!(changed_metrics & DISPLAY_METRIC_ROTATION)) {
return;
}
const bool was_landscape_before_rotation =
base::Contains(is_landscape_orientation_map_, display.id())
? is_landscape_orientation_map_[display.id()]
: false;
for (aura::Window* window : GetWindowList()) {
if (window->GetRootWindow() !=
Shell::GetRootWindowForDisplayId(display.id())) {
continue;
}
auto* window_state = WindowState::Get(window);
if (window_state->persistent_window_info_of_screen_rotation()) {
continue;
}
if (window_state->IsSnapped() || window_state->IsMaximized() ||
window_state->IsFullscreen()) {
continue;
}
window_state->CreatePersistentWindowInfo(
was_landscape_before_rotation,
gfx::Rect(),
false);
}
screen_rotation_restore_callback_ =
base::BindOnce(&PersistentWindowController::
MaybeRestorePersistentWindowBoundsOnScreenRotation,
base::Unretained(this));
}
void PersistentWindowController::OnWillProcessDisplayChanges() {
if (!ShouldProcessWindowList()) {
return;
}
for (aura::Window* window : GetWindowList()) {
WindowState* window_state = WindowState::Get(window);
if (PersistentWindowInfo* info =
window_state->persistent_window_info_of_display_removal();
info) {
info->set_display_id_after_removal(
display::Screen::Get()->GetDisplayNearestWindow(window).id());
continue;
}
need_persistent_info_windows_.Add(
window, window_state->HasRestoreBounds()
? window_state->GetRestoreBoundsInParent()
: gfx::Rect());
}
}
void PersistentWindowController::OnDidProcessDisplayChanges(
const DisplayConfigurationChange& configuration_change) {
if (display_added_restore_callback_) {
std::move(display_added_restore_callback_).Run();
}
need_persistent_info_windows_.RemoveAll();
if (screen_rotation_restore_callback_) {
std::move(screen_rotation_restore_callback_).Run();
}
if (display::Screen::Get()) {
for (const auto& display : display::Screen::Get()->GetAllDisplays()) {
is_landscape_orientation_map_[display.id()] = display.is_landscape();
}
}
}
void PersistentWindowController::OnFirstSessionStarted() {
if (!display::Screen::Get()) {
return;
}
for (const auto& display : display::Screen::Get()->GetAllDisplays()) {
is_landscape_orientation_map_[display.id()] = display.is_landscape();
}
}
void PersistentWindowController::
MaybeRestorePersistentWindowBoundsOnDisplayAdded() {
if (!ShouldProcessWindowList()) {
return;
}
int window_restored_count = 0;
std::vector<raw_ptr<aura::Window, VectorExperimental>> mru_window_list =
GetWindowList();
for (aura::Window* window : base::Reversed(mru_window_list)) {
WindowState* window_state = WindowState::Get(window);
if (!window_state->persistent_window_info_of_display_removal()) {
continue;
}
PersistentWindowInfo* info =
window_state->persistent_window_info_of_display_removal();
const int64_t persistent_display_id = info->display_id();
auto* display_manager = GetDisplayManager();
if (!display_manager->IsDisplayIdValid(persistent_display_id)) {
continue;
}
const auto& display =
display_manager->GetDisplayForId(persistent_display_id);
if (display.GetSizeInPixel() != info->display_size_in_pixel()) {
continue;
}
const int64_t display_id_after_removal = info->display_id_after_removal();
const bool is_display_changed =
persistent_display_id != display_id_after_removal;
if (!is_display_changed) {
gfx::Rect bounds = window->bounds();
wm::ConvertRectToScreen(
Shell::GetRootWindowForDisplayId(persistent_display_id), &bounds);
window->SetBoundsInScreen(bounds, display);
} else {
gfx::Rect persistent_window_bounds = info->window_bounds_in_screen();
const gfx::Vector2d offset = display.bounds().OffsetFromOrigin() -
info->display_offset_from_origin_in_screen();
persistent_window_bounds.Offset(offset);
window->SetBoundsInScreen(persistent_window_bounds, display);
}
if ((is_display_changed || window_state->HasRestoreBounds()) &&
!info->restore_bounds_in_parent().IsEmpty()) {
const gfx::Rect restore_bounds = info->restore_bounds_in_parent();
window_state->SetRestoreBoundsInParent(restore_bounds);
}
window_state->reset_persistent_window_info_of_display_removal();
++window_restored_count;
}
if (window_restored_count != 0) {
base::UmaHistogramExactLinear(kNumOfWindowsRestoredOnDisplayAdded,
window_restored_count,
kMaxRestoredWindowCount);
}
}
void PersistentWindowController::
MaybeRestorePersistentWindowBoundsOnScreenRotation() {
if (!ShouldProcessWindowList()) {
return;
}
int window_restored_count = 0;
for (aura::Window* window : GetWindowList()) {
WindowState* window_state = WindowState::Get(window);
if (!window_state->persistent_window_info_of_screen_rotation()) {
continue;
}
PersistentWindowInfo* info =
window_state->persistent_window_info_of_screen_rotation();
const int64_t display_id = info->display_id();
auto* display_manager = GetDisplayManager();
if (!display_manager->IsDisplayIdValid(display_id)) {
continue;
}
if (display_manager->GetDisplayForId(display_id).is_landscape() ==
info->is_landscape()) {
window->SetBounds(info->window_bounds_in_screen());
++window_restored_count;
}
}
if (window_restored_count != 0) {
base::UmaHistogramExactLinear(kNumOfWindowsRestoredOnScreenRotation,
window_restored_count,
kMaxRestoredWindowCount);
}
}
}