| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/display/persistent_window_controller.h" |
| |
| #include "ash/display/persistent_window_info.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/tablet_mode/scoped_skip_user_session_blocked_check.h" |
| #include "ash/wm/window_state.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/containers/adapters.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "chromeos/ui/base/display_util.h" |
| #include "ui/display/manager/display_manager.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // This controls the UMA histogram `kNumOfWindowsRestoredOnDisplayAdded` and |
| // `kNumOfWindowsRestoredOnScreenRotation`. It should not be changed without |
| // deprecating these two metrics. |
| constexpr int kMaxRestoredWindowCount = 50; |
| |
| display::DisplayManager* GetDisplayManager() { |
| return Shell::Get()->display_manager(); |
| } |
| |
| MruWindowTracker::WindowList GetWindowList() { |
| // MRU tracker normally skips windows if called during a non active session. |
| // |scoped_skip_user_session_blocked_check| allows us to get the list of MRU |
| // windows even when a display is added during for example lock session. |
| ScopedSkipUserSessionBlockedCheck scoped_skip_user_session_blocked_check; |
| return Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kAllDesks); |
| } |
| |
| // Returns true when window cycle list can be processed to perform save/restore |
| // operations on observing display changes. |
| bool ShouldProcessWindowList() { |
| if (!Shell::Get()->desks_controller()) |
| return false; |
| return !GetDisplayManager()->IsInMirrorMode(); |
| } |
| |
| } // namespace |
| |
| constexpr char |
| PersistentWindowController::kNumOfWindowsRestoredOnDisplayAdded[]; |
| constexpr char |
| PersistentWindowController::kNumOfWindowsRestoredOnScreenRotation[]; |
| |
| PersistentWindowController::PersistentWindowController() { |
| Shell::Get()->session_controller()->AddObserver(this); |
| } |
| |
| PersistentWindowController::~PersistentWindowController() { |
| Shell::Get()->session_controller()->RemoveObserver(this); |
| } |
| |
| void PersistentWindowController::OnWillProcessDisplayChanges() { |
| if (!ShouldProcessWindowList()) |
| return; |
| |
| for (auto* window : GetWindowList()) { |
| WindowState* window_state = WindowState::Get(window); |
| // This implies that we keep the first persistent info until they're valid |
| // to restore, or until they're cleared by user-invoked bounds change. |
| if (window_state->persistent_window_info_of_display_removal()) |
| continue; |
| // Place the window that needs persistent window info into the temporary |
| // set. The persistent window info will be created and set if a display is |
| // removed. |
| need_persistent_info_windows_.Add(window); |
| } |
| } |
| |
| void PersistentWindowController::OnDisplayAdded( |
| const display::Display& new_display) { |
| display_added_restore_callback_ = |
| base::BindOnce(&PersistentWindowController:: |
| MaybeRestorePersistentWindowBoundsOnDisplayAdded, |
| base::Unretained(this)); |
| } |
| |
| void PersistentWindowController::OnDisplayRemoved( |
| const display::Display& old_display) { |
| for (aura::Window* window : need_persistent_info_windows_.windows()) { |
| WindowState* window_state = WindowState::Get(window); |
| window_state->SetPersistentWindowInfoOfDisplayRemoval( |
| PersistentWindowInfo(window, /*is_landscape_before_rotation=*/false)); |
| } |
| need_persistent_info_windows_.RemoveAll(); |
| is_landscape_orientation_map_.erase(old_display.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 = |
| is_landscape_orientation_map_.find(display.id()) != |
| is_landscape_orientation_map_.end() |
| ? is_landscape_orientation_map_[display.id()] |
| : false; |
| for (auto* 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; |
| // Do not restore the bounds on screen rotation of windows that are snapped, |
| // maximized or fullscreened. A snapped window is expected to have different |
| // snap positions in different orientations, which means different bounds. |
| // E.g, a left snapped window in landscape primary is expected to be right |
| // snapped in landscape secondary. Restoring is not needed for maximized or |
| // fullscreened windows either, since they will be kept maximized or |
| // fullscreened after rotation. |
| if (window_state->IsSnapped() || window_state->IsMaximized() || |
| window_state->IsFullscreen()) { |
| continue; |
| } |
| window_state->SetPersistentWindowInfoOfScreenRotation( |
| PersistentWindowInfo(window, was_landscape_before_rotation)); |
| } |
| screen_rotation_restore_callback_ = |
| base::BindOnce(&PersistentWindowController:: |
| MaybeRestorePersistentWindowBoundsOnScreenRotation, |
| base::Unretained(this)); |
| } |
| |
| void PersistentWindowController::OnDidProcessDisplayChanges() { |
| 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::GetScreen()) { |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| is_landscape_orientation_map_[display.id()] = |
| chromeos::IsDisplayLayoutHorizontal(display); |
| } |
| } |
| } |
| |
| void PersistentWindowController::OnFirstSessionStarted() { |
| if (!display::Screen::GetScreen()) |
| return; |
| |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| is_landscape_orientation_map_[display.id()] = |
| chromeos::IsDisplayLayoutHorizontal(display); |
| } |
| } |
| |
| void PersistentWindowController:: |
| MaybeRestorePersistentWindowBoundsOnDisplayAdded() { |
| if (!ShouldProcessWindowList()) |
| return; |
| |
| int window_restored_count = 0; |
| // Maybe add the windows to a new display via SetBoundsInScreen() depending on |
| // their persistent window info. Go backwards so that if they do get added to |
| // another root window's container, the stacking order will match the MRU |
| // order (windows added first are stacked at the bottom). |
| std::vector<aura::Window*> mru_window_list = GetWindowList(); |
| for (auto* 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); |
| |
| // Update |persistent_window_bounds| based on |persistent_display_bounds|'s |
| // position change. This ensures that |persistent_window_bounds| is |
| // associated with the right target display. |
| gfx::Rect persistent_window_bounds = info.window_bounds_in_screen; |
| const auto& persistent_display_bounds = info.display_bounds_in_screen; |
| // It is possible to have display size change, such as changing cable, bad |
| // cable signal etc., but it should be rare. |
| if (display.bounds().size() != persistent_display_bounds.size()) |
| continue; |
| const gfx::Vector2d offset = display.bounds().OffsetFromOrigin() - |
| persistent_display_bounds.OffsetFromOrigin(); |
| persistent_window_bounds.Offset(offset); |
| |
| window->SetBoundsInScreen(persistent_window_bounds, display); |
| if (info.restore_bounds_in_screen) { |
| gfx::Rect restore_bounds = *info.restore_bounds_in_screen; |
| restore_bounds.Offset(offset); |
| window_state->SetRestoreBoundsInScreen(restore_bounds); |
| } |
| // Reset persistent window info every time the window bounds have restored. |
| window_state->ResetPersistentWindowInfoOfDisplayRemoval(); |
| |
| ++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 (auto* 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; |
| |
| // Restore window's bounds if we are rotating back to the screen orientation |
| // that window's bounds was stored. Note, `kLandscapePrimary` and |
| // `kLandscapeSecondary` will be treated the same in this case since |
| // window's bounds should be the same in each landscape orientation. Same |
| // for portrait screen orientation. |
| if (chromeos::IsDisplayLayoutHorizontal(display_manager->GetDisplayForId( |
| display_id)) == 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); |
| } |
| } |
| |
| } // namespace ash |