#include "ash/wm/window_state.h"
#include <absl/cleanup/cleanup.h>
#include <optional>
#include <utility>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/focus/focus_cycler.h"
#include "ash/metrics/pip_uma.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_animation_types.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/screen_util.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/collision_detection/collision_detection_utils.h"
#include "ash/wm/default_state.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/pip/pip_controller.h"
#include "ash/wm/pip/pip_positioner.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_restore/window_restore_controller.h"
#include "ash/wm/window_state_delegate.h"
#include "ash/wm/window_state_observer.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/wm_metrics.h"
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/containers/adapters.h"
#include "base/containers/fixed_flat_map.h"
#include "base/debug/crash_logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/types/cxx23_to_underlying.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/base/chromeos_ui_constants.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "chromeos/ui/frame/frame_utils.h"
#include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/painter.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/ime_util_chromeos.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
constexpr base::TimeDelta kDragToMaximizeMisTriggerThreshold = base::Seconds(5);
constexpr char kDragToMaximizeMisTriggersHistogramName[] =
"Ash.Window.DragMaximized.NumberOfMisTriggers";
constexpr char kValidDragMaximizedHistogramName[] =
"Ash.Window.DragMaximized.Valid";
using ::chromeos::kHideShelfWhenFullscreenKey;
using ::chromeos::kImmersiveIsActive;
using ::chromeos::kPartialSplitDurationHistogramName;
using ::chromeos::kWindowManagerManagesOpacityKey;
using ::chromeos::WindowStateType;
using enum WindowSnapGrouping;
constexpr auto kWindowStateRestoreHistoryLayerMap =
base::MakeFixedFlatMap<WindowStateType, int>({
{WindowStateType::kNormal, 0},
{WindowStateType::kDefault, 0},
{WindowStateType::kPrimarySnapped, 1},
{WindowStateType::kSecondarySnapped, 1},
{WindowStateType::kMaximized, 2},
{WindowStateType::kFullscreen, 3},
{WindowStateType::kFloated, 3},
{WindowStateType::kPip, 4},
{WindowStateType::kMinimized, 4},
});
bool IsValidForRestoreHistory(WindowStateType state_type) {
return kWindowStateRestoreHistoryLayerMap.contains(state_type);
}
bool CanRestoreState(WindowStateType current_state,
WindowStateType previous_state) {
if (!IsValidForRestoreHistory(current_state) ||
!IsValidForRestoreHistory(previous_state)) {
return false;
}
return kWindowStateRestoreHistoryLayerMap.at(current_state) >
kWindowStateRestoreHistoryLayerMap.at(previous_state);
}
bool IsTabletModeEnabled() {
return display::Screen::Get()->InTabletMode();
}
bool IsToplevelContainer(aura::Window* window) {
DCHECK(window);
int container_id = window->GetId();
return IsActivatableShellWindowId(container_id) ||
container_id == kShellWindowId_ArcVirtualKeyboardContainer;
}
bool IsTemporarilyHiddenForFullrestore(aura::Window* window) {
if (window->GetProperty(app_restore::kParentToHiddenContainerKey))
return true;
auto* transient_parent = wm::GetTransientParent(window);
return transient_parent && transient_parent->GetProperty(
app_restore::kParentToHiddenContainerKey);
}
bool IsPartial(float snap_ratio) {
return cc::MathUtil::IsWithinEpsilon(snap_ratio,
chromeos::kOneThirdSnapRatio) ||
cc::MathUtil::IsWithinEpsilon(snap_ratio,
chromeos::kTwoThirdSnapRatio);
}
class BoundsSetter : public aura::LayoutManager {
public:
BoundsSetter() = default;
BoundsSetter(const BoundsSetter&) = delete;
BoundsSetter& operator=(const BoundsSetter&) = delete;
~BoundsSetter() override = default;
void OnWindowResized() override {}
void OnWindowAddedToLayout(aura::Window* child) override {}
void OnWillRemoveWindowFromLayout(aura::Window* child) override {}
void OnWindowRemovedFromLayout(aura::Window* child) override {}
void OnChildWindowVisibilityChanged(aura::Window* child,
bool visible) override {}
void SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) override {}
void SetBounds(aura::Window* window, const gfx::Rect& bounds) {
if (window->GetTargetBounds() != bounds)
SetChildBoundsDirect(window, bounds);
}
};
WMEventType WMEventTypeFromShowState(
ui::mojom::WindowShowState requested_show_state) {
switch (requested_show_state) {
case ui::mojom::WindowShowState::kDefault:
case ui::mojom::WindowShowState::kNormal:
return WM_EVENT_NORMAL;
case ui::mojom::WindowShowState::kMinimized:
return WM_EVENT_MINIMIZE;
case ui::mojom::WindowShowState::kMaximized:
return WM_EVENT_MAXIMIZE;
case ui::mojom::WindowShowState::kFullscreen:
return WM_EVENT_FULLSCREEN;
case ui::mojom::WindowShowState::kInactive:
return WM_EVENT_SHOW_INACTIVE;
case ui::mojom::WindowShowState::kEnd:
NOTREACHED() << "No WMEvent defined for the show state:"
<< requested_show_state;
}
return WM_EVENT_NORMAL;
}
float GetTargetSnapRatio(aura::Window* window,
const WindowSnapWMEvent* snap_event) {
if (display::Screen::Get()->InTabletMode()) {
return snap_event->snap_ratio();
}
const gfx::Rect work_area(
screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
window->GetRootWindow()));
const bool is_horizontal = IsLayoutHorizontal(window);
const float window_minimum_length =
GetMinimumWindowLength(window, is_horizontal);
const float snap_ratio = snap_event->snap_ratio();
return std::max(window_minimum_length /
(is_horizontal ? work_area.width() : work_area.height()),
snap_ratio);
}
float AdjustCurrentSnapRatio(aura::Window* window,
const gfx::Rect& target_bounds) {
gfx::Rect maximized_bounds =
screen_util::GetMaximizedWindowBoundsInParent(window);
const int divider_delta =
ShouldConsiderDivider(window) ? kSplitviewDividerShortSideLength / 2 : 0;
if (IsLayoutHorizontal(window)) {
return static_cast<float>(target_bounds.width() + divider_delta) /
maximized_bounds.width();
}
return static_cast<float>(target_bounds.height() + divider_delta) /
maximized_bounds.height();
}
void MoveAllTransientChildrenToNewRoot(aura::Window* window) {
aura::Window* dst_root = window->GetRootWindow();
for (aura::Window* transient_child : wm::GetTransientChildren(window)) {
if (!transient_child->parent())
continue;
const int container_id = transient_child->parent()->GetId();
CHECK_GE(container_id, 0);
aura::Window* container = dst_root->GetChildById(container_id);
if (container->Contains(transient_child))
continue;
gfx::Rect child_bounds = transient_child->bounds();
wm::ConvertRectToScreen(dst_root, &child_bounds);
container->AddChild(transient_child);
transient_child->SetBoundsInScreen(
child_bounds, display::Screen::Get()->GetDisplayNearestWindow(window));
MoveAllTransientChildrenToNewRoot(transient_child);
}
for (aura::Window* child : window->children())
MoveAllTransientChildrenToNewRoot(child);
}
void ReportAshPipEvents(AshPipEvents event) {
UMA_HISTOGRAM_ENUMERATION(kAshPipEventsHistogramName, event);
}
void ReportAshPipAndroidPipUseTime(base::TimeDelta duration) {
UMA_HISTOGRAM_CUSTOM_TIMES(kAshPipAndroidPipUseTimeHistogramName, duration,
base::Seconds(1), base::Hours(10), 50);
}
void SaveWindowForWindowRestore(WindowState* window_state) {
if (auto* controller = WindowRestoreController::Get())
controller->SaveWindow(window_state);
}
bool ShouldWindowHaveRoundedCornersForWindowState(
const WindowState* window_state) {
const bool in_overview =
window_state->window()->GetProperty(chromeos::kIsShowingInOverviewKey);
return !in_overview && WindowState::ShouldWindowStateHaveRoundedCorners(
window_state->GetStateType());
}
bool ShouldSetExplicitOpaqueRegionsForOcclusion(WindowState* window_state) {
return ShouldWindowHaveRoundedCornersForWindowState(window_state) &&
window_state->window()->GetProperty(
ash::kWindowManagerManagesOpacityKey);
}
WindowStateType IgnoreGrouping(ExtendedWindowStateType type) {
if (std::holds_alternative<WindowStateType>(type)) {
return std::get<WindowStateType>(type);
}
switch (std::get<GroupedWindowStateType>(type)) {
case GroupedWindowStateType::kPrimarySnapped:
return chromeos::WindowStateType::kPrimarySnapped;
case GroupedWindowStateType::kSecondarySnapped:
return chromeos::WindowStateType::kSecondarySnapped;
}
}
}
constexpr base::TimeDelta WindowState::kBoundsChangeSlideDuration;
WindowState::ScopedBoundsChangeAnimation::ScopedBoundsChangeAnimation(
aura::Window* window,
BoundsChangeAnimationType bounds_animation_type)
: window_(window) {
window_->AddObserver(this);
previous_bounds_animation_type_ =
WindowState::Get(window_)->bounds_animation_type_;
WindowState::Get(window_)->bounds_animation_type_ = bounds_animation_type;
}
WindowState::ScopedBoundsChangeAnimation::~ScopedBoundsChangeAnimation() {
if (window_) {
WindowState::Get(window_)->bounds_animation_type_ =
previous_bounds_animation_type_;
window_->RemoveObserver(this);
window_ = nullptr;
}
}
void WindowState::ScopedBoundsChangeAnimation::OnWindowDestroying(
aura::Window* window) {
window_->RemoveObserver(this);
window_ = nullptr;
}
WindowState::~WindowState() {
if (has_ever_been_dragged_to_maximized_) {
base::UmaHistogramCounts100(kDragToMaximizeMisTriggersHistogramName,
num_of_drag_to_maximize_mis_triggers_);
}
}
std::vector<chromeos::WindowStateType>
WindowState::GetWindowStateTypeRestoreHistoryForTesting() const {
std::vector<chromeos::WindowStateType> result;
for (auto type : restore_history_.GetWindowStatesForTesting()) {
result.push_back(IgnoreGrouping(type));
}
return result;
}
bool WindowState::HasDelegate() const {
return !!delegate_;
}
void WindowState::SetDelegate(std::unique_ptr<WindowStateDelegate> delegate) {
DCHECK((!delegate_.get() && !!delegate.get()) ||
(!!delegate_.get() && !delegate.get()));
delegate_ = std::move(delegate);
}
void WindowState::CreatePersistentWindowInfo(
bool was_landscape_before_rotation,
const gfx::Rect& restore_bounds_in_parent,
bool for_display_removal) {
if (for_display_removal) {
CHECK(!persistent_window_info_of_display_removal_);
persistent_window_info_of_display_removal_ =
std::make_unique<PersistentWindowInfo>(
window_, was_landscape_before_rotation, restore_bounds_in_parent);
return;
}
CHECK(!persistent_window_info_of_screen_rotation_);
persistent_window_info_of_screen_rotation_ =
std::make_unique<PersistentWindowInfo>(
window_, was_landscape_before_rotation, restore_bounds_in_parent);
}
WindowStateType WindowState::GetStateType() const {
return current_state_->GetType();
}
bool WindowState::IsMinimized() const {
return IsMinimizedWindowStateType(GetStateType());
}
bool WindowState::IsMaximized() const {
return GetStateType() == WindowStateType::kMaximized;
}
bool WindowState::IsFullscreen() const {
return GetStateType() == WindowStateType::kFullscreen;
}
bool WindowState::IsMaximizedOrFullscreenOrPinned() const {
return IsMaximizedOrFullscreenOrPinnedWindowStateType(GetStateType());
}
bool WindowState::IsSnapped() const {
return GetStateType() == WindowStateType::kPrimarySnapped ||
GetStateType() == WindowStateType::kSecondarySnapped;
}
bool WindowState::IsPinned() const {
return GetStateType() == WindowStateType::kPinned ||
GetStateType() == WindowStateType::kLockedFullscreen;
}
bool WindowState::IsLockedFullscreen() const {
return GetStateType() == WindowStateType::kLockedFullscreen;
}
bool WindowState::IsPip() const {
return GetStateType() == WindowStateType::kPip;
}
bool WindowState::IsFloated() const {
return GetStateType() == WindowStateType::kFloated;
}
int64_t WindowState::GetFullscreenTargetDisplayId() const {
return window_->GetProperty(aura::client::kFullscreenTargetDisplayIdKey);
}
bool WindowState::IsNormalStateType() const {
return IsNormalWindowStateType(GetStateType());
}
bool WindowState::IsNormalOrSnapped() const {
return IsNormalStateType() || IsSnapped();
}
bool WindowState::IsVerticalOrHorizontalMaximized() const {
return IsNormalStateType() && HasRestoreBounds();
}
bool WindowState::IsNonVerticalOrHorizontalMaximizedNormalState() const {
return IsNormalStateType() && !HasRestoreBounds();
}
bool WindowState::IsActive() const {
return wm::IsActiveWindow(window_);
}
bool WindowState::IsUserPositionable() const {
return window_util::IsWindowUserPositionable(window_);
}
bool WindowState::CanFullscreen() const {
return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanFullscreen) != 0;
}
bool WindowState::CanMaximize() const {
return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanMaximize) != 0;
}
bool WindowState::CanMinimize() const {
return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanMinimize) != 0;
}
bool WindowState::CanResize() const {
return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
aura::client::kResizeBehaviorCanResize) != 0;
}
bool WindowState::CanActivate() const {
return wm::CanActivateWindow(window_);
}
bool WindowState::ShouldWindowHaveRoundedCorners() const {
return window_->GetProperty(chromeos::kWindowHasRoundedCornersKey);
}
gfx::RoundedCornersF WindowState::GetWindowRoundedCorners() const {
if (!ShouldWindowHaveRoundedCorners()) {
return gfx::RoundedCornersF();
}
auto radii = window_->GetProperty(aura::client::kWindowRoundedCornersKey);
if (radii) {
return *radii;
}
return gfx::RoundedCornersF();
}
bool WindowState::CanSnap() {
return CanSnapOnDisplay(GetDisplay());
}
bool WindowState::CanSnapOnDisplay(display::Display display) const {
const bool can_resize = CanResize();
const bool can_resizable_snap = !IsPip() && can_resize && CanMaximize();
return can_resizable_snap ||
(!can_resize && CanUnresizableSnapOnDisplay(display));
}
bool WindowState::HasRestoreBounds() const {
gfx::Rect* bounds = window_->GetProperty(aura::client::kRestoreBoundsKey);
return bounds != nullptr && !bounds->IsEmpty();
}
void WindowState::Maximize() {
wm::SetWindowState(window_, ui::mojom::WindowShowState::kMaximized);
}
void WindowState::Minimize() {
wm::SetWindowState(window_, ui::mojom::WindowShowState::kMinimized);
}
void WindowState::Unminimize() {
wm::Unminimize(window_);
}
void WindowState::Activate() {
wm::ActivateWindow(window_);
}
void WindowState::Deactivate() {
wm::DeactivateWindow(window_);
}
void WindowState::Restore() {
const WMEvent event(WM_EVENT_RESTORE);
OnWMEvent(&event);
}
bool WindowState::IsRestoring(WindowStateType previous_state) const {
return CanRestoreState(previous_state, GetStateType());
}
void WindowState::DisableZOrdering(aura::Window* window_on_top) {
ui::ZOrderLevel z_order = GetZOrdering();
if (z_order != ui::ZOrderLevel::kNormal && !IsPip()) {
bool visible = window_->IsVisible();
if (visible)
window_->Hide();
window_->SetProperty(aura::client::kZOrderingKey, ui::ZOrderLevel::kNormal);
if (window_on_top && window_on_top->parent() == window_->parent())
window_->parent()->StackChildAbove(window_on_top, window_);
if (visible)
window_->Show();
cached_z_order_ = z_order;
}
}
void WindowState::RestoreZOrdering() {
if (cached_z_order_ != ui::ZOrderLevel::kNormal) {
window_->SetProperty(aura::client::kZOrderingKey, cached_z_order_);
cached_z_order_ = ui::ZOrderLevel::kNormal;
}
}
void WindowState::OnWMEvent(const WMEvent* event) {
if (is_handling_float_event_) {
return;
}
std::unique_ptr<base::AutoReset<bool>> snap_event_lock;
if (const WindowSnapWMEvent* snap_event = event->AsSnapEvent()) {
snap_event_lock =
std::make_unique<base::AutoReset<bool>>(&is_handling_snap_event_, true);
const float target_snap_ratio = GetTargetSnapRatio(window_, snap_event);
snap_ratio_ = std::make_optional(target_snap_ratio);
snap_action_source_ = std::make_optional(snap_event->snap_action_source());
if (IsPartial(target_snap_ratio)) {
partial_start_time_ = base::TimeTicks::Now();
} else {
MaybeRecordPartialDuration();
}
}
if (event->type() == WM_EVENT_FLOAT ||
(current_state_->GetType() == chromeos::WindowStateType::kFloated &&
event->IsTransitionEvent())) {
{
base::AutoReset<bool> float_lock(&is_handling_float_event_, true);
current_state_->OnWMEvent(this, event);
}
if (auto* snap_group_controller = SnapGroupController::Get()) {
snap_group_controller->OnFloatUnfloatCompleted(window_);
}
} else {
current_state_->OnWMEvent(this, event);
}
if (!is_handling_snap_event_ && can_update_snap_ratio_ &&
event->IsBoundsEvent()) {
UpdateSnapRatio();
}
}
gfx::Rect WindowState::GetCurrentBoundsInScreen() const {
gfx::Rect bounds_in_screen = window_->GetTargetBounds();
wm::ConvertRectToScreen(window_->parent(), &bounds_in_screen);
return bounds_in_screen;
}
void WindowState::SaveCurrentBoundsForRestore() {
SetRestoreBoundsInScreen(GetCurrentBoundsInScreen());
}
gfx::Rect WindowState::GetRestoreBoundsInScreen() const {
gfx::Rect* restore_bounds =
window_->GetProperty(aura::client::kRestoreBoundsKey);
return restore_bounds ? *restore_bounds : gfx::Rect();
}
gfx::Rect WindowState::GetRestoreBoundsInParent() const {
gfx::Rect result = GetRestoreBoundsInScreen();
wm::ConvertRectFromScreen(window_->parent(), &result);
return result;
}
void WindowState::SetRestoreBoundsInScreen(const gfx::Rect& bounds) {
window_->SetProperty(aura::client::kRestoreBoundsKey, bounds);
}
void WindowState::SetRestoreBoundsInParent(const gfx::Rect& bounds) {
gfx::Rect bounds_in_screen = bounds;
wm::ConvertRectToScreen(window_->parent(), &bounds_in_screen);
SetRestoreBoundsInScreen(bounds_in_screen);
}
void WindowState::ClearRestoreBounds() {
window_->ClearProperty(aura::client::kRestoreBoundsKey);
window_->ClearProperty(wm::kVirtualKeyboardRestoreBoundsKey);
}
bool WindowState::VerticallyShrinkWindow(const gfx::Rect& work_area) {
if (!HasRestoreBounds())
return false;
gfx::Rect bounds = window_->bounds();
if (bounds.height() != work_area.height() || bounds.y() != work_area.y())
return false;
gfx::Rect restore_bounds = GetRestoreBoundsInParent();
gfx::Rect new_bounds = restore_bounds;
if (bounds == work_area) {
new_bounds = gfx::Rect(work_area.x(), restore_bounds.y(), work_area.width(),
restore_bounds.height());
} else {
ClearRestoreBounds();
}
SetBoundsDirectCrossFade(new_bounds);
return true;
}
bool WindowState::HorizontallyShrinkWindow(const gfx::Rect& work_area) {
if (!HasRestoreBounds())
return false;
gfx::Rect bounds = window_->bounds();
if (bounds.width() != work_area.width() || bounds.x() != work_area.x())
return false;
gfx::Rect restore_bounds = GetRestoreBoundsInParent();
gfx::Rect new_bounds = restore_bounds;
if (bounds == work_area) {
new_bounds = gfx::Rect(restore_bounds.x(), work_area.y(),
restore_bounds.width(), work_area.height());
} else {
ClearRestoreBounds();
}
SetBoundsDirectCrossFade(new_bounds);
return true;
}
std::unique_ptr<WindowState::State> WindowState::SetStateObject(
std::unique_ptr<WindowState::State> new_state) {
current_state_->DetachState(this);
std::unique_ptr<WindowState::State> old_object = std::move(current_state_);
current_state_ = std::move(new_state);
current_state_->AttachState(this, old_object.get());
return old_object;
}
void WindowState::UpdateSnapRatio() {
if (!IsSnapped())
return;
ForceUpdateSnapRatio(window_->GetTargetBounds());
}
void WindowState::ForceUpdateSnapRatio(const gfx::Rect& target_bounds) {
snap_ratio_ =
std::make_optional(AdjustCurrentSnapRatio(window_, target_bounds));
MaybeRecordPartialDuration();
}
void WindowState::AddObserver(WindowStateObserver* observer) {
observer_list_.AddObserver(observer);
}
void WindowState::RemoveObserver(WindowStateObserver* observer) {
observer_list_.RemoveObserver(observer);
}
bool WindowState::GetHideShelfWhenFullscreen() const {
return window_->GetProperty(kHideShelfWhenFullscreenKey);
}
void WindowState::SetHideShelfWhenFullscreen(bool value) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(kHideShelfWhenFullscreenKey, value);
}
bool WindowState::GetWindowPositionManaged() const {
return window_->GetProperty(kWindowPositionManagedTypeKey);
}
void WindowState::SetWindowPositionManaged(bool managed) {
window_->SetProperty(kWindowPositionManagedTypeKey, managed);
}
bool WindowState::CanConsumeSystemKeys() const {
return window_->GetProperty(kCanConsumeSystemKeysKey);
}
void WindowState::SetCanConsumeSystemKeys(bool can_consume_system_keys) {
window_->SetProperty(kCanConsumeSystemKeysKey, can_consume_system_keys);
}
bool WindowState::IsInImmersiveFullscreen() const {
return window_->GetProperty(kImmersiveIsActive);
}
void WindowState::SetBoundsChangedByUser(bool bounds_changed_by_user) {
bounds_changed_by_user_ = bounds_changed_by_user;
if (bounds_changed_by_user) {
pre_auto_manage_window_bounds_.reset();
pre_added_to_workspace_window_bounds_.reset();
persistent_window_info_of_display_removal_.reset();
persistent_window_info_of_screen_rotation_.reset();
}
}
void WindowState::OnDragStarted(int window_component) {
DCHECK(drag_details_);
if (delegate_) {
delegate_->OnDragStarted(window_component);
}
}
void WindowState::OnCompleteDrag(const gfx::PointF& location) {
DCHECK(drag_details_);
if (delegate_) {
delegate_->OnDragFinished(false, location);
}
SaveWindowForWindowRestore(this);
}
void WindowState::OnRevertDrag(const gfx::PointF& location) {
DCHECK(drag_details_);
if (delegate_) {
delegate_->OnDragFinished(true, location);
}
}
void WindowState::OnActivationLost() {
if (IsPip()) {
views::Widget::GetWidgetForNativeWindow(window_)
->widget_delegate()
->SetCanActivate(false);
}
}
display::Display WindowState::GetDisplay() const {
return display::Screen::Get()->GetDisplayNearestWindow(window_);
}
WindowStateType WindowState::GetRestoreWindowState() const {
WindowStateType restore_state = WindowStateType::kNormal;
if (!restore_history_.IsEmpty()) {
WindowStateType top_state = IgnoreGrouping(restore_history_.GetTop());
restore_state = (top_state == WindowStateType::kDefault)
? WindowStateType::kNormal
: top_state;
}
if (IsMinimized() && restore_state == WindowStateType::kFloated) {
if (window_util::GetFloatedWindowForActiveDesk()) {
return IsTabletModeEnabled() ? GetWindowTypeOnMaximizable()
: WindowStateType::kNormal;
}
}
if (IsTabletModeEnabled()) {
if (restore_state == WindowStateType::kNormal ||
(IsFloated() &&
(restore_state == WindowStateType::kPrimarySnapped ||
restore_state == WindowStateType::kSecondarySnapped))) {
restore_state = GetWindowTypeOnMaximizable();
}
}
return restore_state;
}
WindowSnapGrouping WindowState::GetSnapGroupingForRestore() const {
return (!restore_history_.IsEmpty() &&
std::holds_alternative<GroupedWindowStateType>(
restore_history_.GetTop()))
? kGrouped
: kUngrouped;
}
void WindowState::TrackDragToMaximizeBehavior() {
if (!has_ever_been_dragged_to_maximized_)
has_ever_been_dragged_to_maximized_ = true;
if (drag_to_maximize_mis_trigger_timer_.IsRunning()) {
num_of_drag_to_maximize_mis_triggers_++;
base::UmaHistogramBoolean(kValidDragMaximizedHistogramName, false);
drag_to_maximize_mis_trigger_timer_.Stop();
}
drag_to_maximize_mis_trigger_timer_.Start(
FROM_HERE, kDragToMaximizeMisTriggerThreshold, this,
&WindowState::CheckAndRecordDragMaximizedBehavior);
}
base::AutoReset<bool> WindowState::GetScopedIgnorePropertyChange() {
return base::AutoReset<bool>(&ignore_property_change_, true);
}
void WindowState::CreateDragDetails(const gfx::PointF& point_in_parent,
int window_component,
wm::WindowMoveSource source) {
drag_details_ = std::make_unique<DragDetails>(window_, point_in_parent,
window_component, source);
}
void WindowState::DeleteDragDetails() {
drag_details_.reset();
}
void WindowState::SetAndClearRestoreBounds() {
DCHECK(HasRestoreBounds());
SetBoundsInScreen(GetRestoreBoundsInScreen());
ClearRestoreBounds();
}
WindowState::WindowState(aura::Window* window)
: window_(window),
current_state_(
new DefaultState(chromeos::ToWindowStateType(GetShowState()))) {
window_->AddObserver(this);
UpdateWindowPropertiesFromStateType();
OnPrePipStateChange(WindowStateType::kDefault);
}
ui::ZOrderLevel WindowState::GetZOrdering() const {
return window_->GetProperty(aura::client::kZOrderingKey);
}
ui::mojom::WindowShowState WindowState::GetShowState() const {
return window_->GetProperty(aura::client::kShowStateKey);
}
void WindowState::SetBoundsInScreen(const gfx::Rect& bounds_in_screen) {
gfx::Rect bounds_in_parent = bounds_in_screen;
wm::ConvertRectFromScreen(window_->parent(), &bounds_in_parent);
window_->SetBounds(bounds_in_parent);
}
void WindowState::AdjustSnappedBoundsForDisplayWorkspaceChange(
gfx::Rect* bounds) {
if (is_dragged() || !IsSnapped() || display::Screen::Get()->InTabletMode()) {
return;
}
gfx::Rect maximized_bounds =
screen_util::GetMaximizedWindowBoundsInParent(window_);
const display::Display display =
display::Screen::Get()->GetDisplayNearestWindow(window_);
const gfx::Rect snapped_bounds =
snap_ratio_ ? GetSnappedWindowBounds(
maximized_bounds, display, window_,
GetStateType() == WindowStateType::kPrimarySnapped
? ash::SnapViewType::kPrimary
: ash::SnapViewType::kSecondary,
*snap_ratio_)
: maximized_bounds;
bounds->set_origin(snapped_bounds.origin());
if (snap_ratio_) {
bounds->set_size(snapped_bounds.size());
} else if (IsLayoutHorizontal(display)) {
bounds->set_height(snapped_bounds.height());
} else {
bounds->set_width(snapped_bounds.width());
}
}
void WindowState::UpdateWindowPropertiesFromStateType() {
ui::mojom::WindowShowState new_window_state =
ToWindowShowState(current_state_->GetType());
if (new_window_state != GetShowState()) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(aura::client::kShowStateKey, new_window_state);
}
if (GetStateType() != window_->GetProperty(chromeos::kWindowStateTypeKey)) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(chromeos::kWindowStateTypeKey, GetStateType());
}
const bool should_round_window =
ShouldWindowHaveRoundedCornersForWindowState(this);
if (window_->GetProperty(chromeos::kWindowHasRoundedCornersKey) !=
should_round_window) {
base::AutoReset<bool> resetter(&ignore_property_change_, true);
window_->SetProperty(chromeos::kWindowHasRoundedCornersKey,
should_round_window);
}
if (window_->GetProperty(ash::kWindowManagerManagesOpacityKey)) {
const gfx::Size& size = window_->bounds().size();
if (ShouldSetExplicitOpaqueRegionsForOcclusion(this)) {
window_->SetTransparent(true);
window_->SetOpaqueRegionsForOcclusion({gfx::Rect(size)});
} else {
window_->SetOpaqueRegionsForOcclusion({});
window_->SetTransparent(false);
}
}
}
void WindowState::NotifyPreStateTypeChange(
WindowStateType old_window_state_type) {
for (auto& observer : observer_list_)
observer.OnPreWindowStateTypeChange(this, old_window_state_type);
OnPrePipStateChange(old_window_state_type);
}
void WindowState::NotifyPostStateTypeChange(
ExtendedWindowStateType old_window_state_type) {
WindowStateType old_plain_type = IgnoreGrouping(old_window_state_type);
for (auto& observer : observer_list_)
observer.OnPostWindowStateTypeChange(this, old_plain_type);
OnPostPipStateChange(old_plain_type);
UpdateWindowStateRestoreHistoryStack(old_window_state_type);
SaveWindowForWindowRestore(this);
if (chromeos::IsSnappedWindowStateType(old_plain_type)) {
MaybeRecordPartialDuration();
}
}
void WindowState::OnPostPipStateChange(WindowStateType old_window_state_type) {
if (old_window_state_type == WindowStateType::kPip) {
wm::SetWindowVisibilityAnimationType(
window_, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
}
}
void WindowState::SetBoundsDirectForTesting(const gfx::Rect& bounds) {
SetBoundsDirect(bounds);
}
void WindowState::SetBoundsDirect(const gfx::Rect& bounds_in_parent) {
gfx::Rect actual_new_bounds(bounds_in_parent);
if (window_->delegate() && !IsMaximized() && !IsFullscreen()) {
gfx::Size min_size = window_->delegate()
? window_->delegate()->GetMinimumSize()
: gfx::Size();
gfx::Size max_size =
window_->delegate()
? window_->delegate()->GetMaximumSize().value_or(gfx::Size())
: gfx::Size();
const display::Display display =
display::Screen::Get()->GetDisplayNearestWindow(window_);
min_size.SetToMin(display.work_area().size());
actual_new_bounds.set_width(
std::max(min_size.width(), actual_new_bounds.width()));
actual_new_bounds.set_height(
std::max(min_size.height(), actual_new_bounds.height()));
if (!max_size.IsEmpty()) {
DCHECK_LE(min_size.width(), max_size.width());
DCHECK_LE(min_size.height(), max_size.height());
actual_new_bounds.set_width(
std::min(max_size.width(), actual_new_bounds.width()));
actual_new_bounds.set_height(
std::min(max_size.height(), actual_new_bounds.height()));
}
if (IsPip() && !is_dragged() &&
!Shell::Get()->pip_controller()->is_tucked()) {
wm::ConvertRectToScreen(window_->GetRootWindow(), &actual_new_bounds);
actual_new_bounds = CollisionDetectionUtils::GetRestingPosition(
display, actual_new_bounds,
CollisionDetectionUtils::RelativePriority::kPictureInPicture);
wm::ConvertRectFromScreen(window_->GetRootWindow(), &actual_new_bounds);
}
}
BoundsSetter().SetBounds(window_, actual_new_bounds);
}
void WindowState::SetBoundsConstrained(const gfx::Rect& bounds_in_parent) {
const gfx::Rect work_area_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window_);
gfx::Rect child_bounds_in_parent(bounds_in_parent);
AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds_in_parent);
if (window_->GetType() == aura::client::WINDOW_TYPE_NORMAL &&
child_bounds_in_parent.y() < work_area_in_parent.y()) {
child_bounds_in_parent.set_y(work_area_in_parent.y());
}
SetBoundsDirect(child_bounds_in_parent);
}
void WindowState::SetBoundsDirectAnimated(const gfx::Rect& bounds_in_parent,
base::TimeDelta duration,
gfx::Tween::Type tween_type) {
if (wm::WindowAnimationsDisabled(window_)) {
SetBoundsDirect(bounds_in_parent);
return;
}
ui::Layer* layer = window_->layer();
ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator());
slide_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
slide_settings.SetTweenType(tween_type);
slide_settings.SetTransitionDuration(duration);
SetBoundsDirect(bounds_in_parent);
}
void WindowState::SetBoundsDirectCrossFade(const gfx::Rect& bounds_in_parent,
std::optional<bool> float_state) {
if (!window_->TargetVisibility()) {
SetBoundsConstrained(bounds_in_parent);
return;
}
if (!window_->layer()->GetTargetTransform().IsIdentity() ||
wm::WindowAnimationsDisabled(window_)) {
SetBoundsDirect(bounds_in_parent);
return;
}
std::unique_ptr<ui::LayerTreeOwner> old_layer_owner =
wm::RecreateLayers(window_);
SetBoundsDirect(bounds_in_parent);
if (float_state) {
CrossFadeAnimationForFloatUnfloat(window_, std::move(old_layer_owner),
*float_state);
return;
}
SCOPED_CRASH_KEY_NUMBER("333095196", "state_type",
base::to_underlying(GetStateType()));
CrossFadeAnimation(window_, std::move(old_layer_owner));
}
void WindowState::OnPrePipStateChange(WindowStateType old_window_state_type) {
auto* widget = views::Widget::GetWidgetForNativeWindow(window_);
const bool was_pip = old_window_state_type == WindowStateType::kPip;
auto* const pip_controller = Shell::Get()->pip_controller();
if (IsPip()) {
pip_controller->SetPipWindow(window_);
CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
window_, CollisionDetectionUtils::RelativePriority::kPictureInPicture);
if (widget) {
widget->widget_delegate()->SetCanActivate(false);
if (widget->IsActive())
widget->Deactivate();
Shell::Get()->focus_cycler()->AddWidget(widget);
}
wm::SetWindowVisibilityAnimationType(
window_, WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT);
pip_controller->UpdatePipBounds();
if (!was_pip) {
if (widget && widget->GetContentsView()) {
widget->GetContentsView()->GetViewAccessibility().AnnounceText(
l10n_util::GetStringUTF16(IDS_ENTER_PIP_A11Y_NOTIFICATION));
}
}
CollectPipEnterExitMetrics(true);
window_->SetProperty(ash::kExcludeInMruKey, true);
} else if (was_pip) {
if (widget) {
widget->widget_delegate()->SetCanActivate(true);
Shell::Get()->focus_cycler()->RemoveWidget(widget);
}
wm::SetWindowVisibilityAnimationType(
window_, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
CollectPipEnterExitMetrics(false);
window_->ClearProperty(ash::kExcludeInMruKey);
pip_controller->UnsetPipWindow(window_);
}
if (IsPip() || was_pip)
ash::PipPositioner::ClearSnapFraction(this);
}
void WindowState::CollectPipEnterExitMetrics(bool enter) {
const bool is_arc = IsArcWindow(window_);
if (enter) {
pip_start_time_ = base::TimeTicks::Now();
ReportAshPipEvents(AshPipEvents::PIP_START);
ReportAshPipEvents(is_arc ? AshPipEvents::ANDROID_PIP_START
: AshPipEvents::CHROME_PIP_START);
} else {
ReportAshPipEvents(AshPipEvents::PIP_END);
ReportAshPipEvents(is_arc ? AshPipEvents::ANDROID_PIP_END
: AshPipEvents::CHROME_PIP_END);
if (is_arc) {
DCHECK(!pip_start_time_.is_null());
const auto session_duration = base::TimeTicks::Now() - pip_start_time_;
ReportAshPipAndroidPipUseTime(session_duration);
}
pip_start_time_ = base::TimeTicks();
}
}
void WindowState::MaybeRecordPartialDuration() {
if (!partial_start_time_.is_null()) {
base::UmaHistogramCustomCounts(
kPartialSplitDurationHistogramName,
(base::TimeTicks::Now() - partial_start_time_).InMinutes(), 1,
base::Days(7).InMinutes(), 50);
partial_start_time_ = base::TimeTicks();
}
}
WindowState::RestoreHistoryStack::RestoreHistoryStack() = default;
WindowState::RestoreHistoryStack::~RestoreHistoryStack() = default;
void WindowState::RestoreHistoryStack::Push(
ExtendedWindowStateType state_type) {
CHECK(IsValidForRestoreHistory(IgnoreGrouping(state_type)));
window_states_.push_back(state_type);
}
void WindowState::RestoreHistoryStack::Clear() {
window_states_.clear();
}
void WindowState::RestoreHistoryStack::PopIncompatible(
WindowStateType current_state_type) {
for (auto state_type : base::Reversed(window_states_)) {
if (CanRestoreState(current_state_type, IgnoreGrouping(state_type))) {
break;
}
window_states_.pop_back();
}
}
bool WindowState::RestoreHistoryStack::IsEmpty() const {
return window_states_.empty();
}
ExtendedWindowStateType WindowState::RestoreHistoryStack::GetTop() const {
CHECK(!IsEmpty());
return window_states_.back();
}
const std::vector<ExtendedWindowStateType>&
WindowState::RestoreHistoryStack::GetWindowStatesForTesting() const {
CHECK_IS_TEST();
return window_states_;
}
void WindowState::UpdateWindowStateRestoreHistoryStack(
ExtendedWindowStateType previous_state_type) {
WindowStateType current_state_type = GetStateType();
if (!IsValidForRestoreHistory(current_state_type)) {
restore_history_.Clear();
window_->ClearProperty(aura::client::kRestoreShowStateKey);
return;
}
restore_history_.PopIncompatible(current_state_type);
if (CanRestoreState(current_state_type,
IgnoreGrouping(previous_state_type))) {
restore_history_.Push(previous_state_type);
}
if (!IsTabletModeEnabled()) {
window_->SetProperty(aura::client::kRestoreShowStateKey,
chromeos::ToWindowShowState(GetRestoreWindowState()));
}
if (restore_history_.IsEmpty() && HasRestoreBounds() &&
!IsNormalStateType()) {
restore_history_.Push(WindowStateType::kDefault);
}
}
WindowStateType WindowState::GetWindowTypeOnMaximizable() const {
return CanMaximize() && wm::GetTransientParent(window_) == nullptr
? WindowStateType::kMaximized
: WindowStateType::kNormal;
}
WindowState* WindowState::Get(aura::Window* window) {
if (!window)
return nullptr;
WindowState* state = window->GetProperty(kWindowStateKey);
if (state)
return state;
if (window->GetType() == aura::client::WINDOW_TYPE_CONTROL)
return nullptr;
DCHECK(window->parent());
if (!IsToplevelContainer(window->parent()) &&
!IsTemporarilyHiddenForFullrestore(window)) {
return nullptr;
}
state = new WindowState(window);
window->SetProperty(kWindowStateKey, state);
return state;
}
const WindowState* WindowState::Get(const aura::Window* window) {
return Get(const_cast<aura::Window*>(window));
}
WindowState* WindowState::ForActiveWindow() {
aura::Window* active = window_util::GetActiveWindow();
return active ? WindowState::Get(active) : nullptr;
}
bool WindowState::ShouldWindowStateHaveRoundedCorners(
chromeos::WindowStateType state_type) {
return IsNormalWindowStateType(state_type) ||
state_type == WindowStateType::kFloated ||
state_type == WindowStateType::kPip;
}
void WindowState::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
DCHECK_EQ(window_, window);
if (key == aura::client::kShowStateKey) {
if (!ignore_property_change_) {
WMEvent event(WMEventTypeFromShowState(GetShowState()));
OnWMEvent(&event);
}
return;
}
if (key == kWindowPipTypeKey) {
if (window->GetProperty(kWindowPipTypeKey)) {
WMEvent event(WM_EVENT_PIP);
OnWMEvent(&event);
} else {
NOTIMPLEMENTED();
}
return;
}
if (key == chromeos::kWindowStateTypeKey) {
if (!ignore_property_change_) {
window->SetProperty(chromeos::kWindowStateTypeKey, GetStateType());
}
return;
}
if (key == chromeos::kIsShowingInOverviewKey ||
key == aura::client::kWindowRoundedCornersKey) {
if (!ignore_property_change_) {
UpdateWindowPropertiesFromStateType();
}
return;
}
if (key == aura::client::kWindowWorkspaceKey ||
key == aura::client::kDeskUuidKey) {
if (!ignore_property_change_)
SaveWindowForWindowRestore(this);
return;
}
const bool requires_shelf_visibility_update =
(key == kHideShelfWhenFullscreenKey &&
old != window->GetProperty(kHideShelfWhenFullscreenKey)) ||
(key == kImmersiveIsActive &&
old != window->GetProperty(kImmersiveIsActive));
if (requires_shelf_visibility_update && !ignore_property_change_) {
Shelf::UpdateShelfVisibility();
return;
}
}
void WindowState::OnWindowAddedToRootWindow(aura::Window* window) {
DCHECK_EQ(window_, window);
if (wm::GetTransientParent(window)) {
return;
}
MoveAllTransientChildrenToNewRoot(window);
}
void WindowState::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(window_, window);
if (IsPip())
CollectPipEnterExitMetrics(false);
MaybeRecordPartialDuration();
auto* widget = views::Widget::GetWidgetForNativeWindow(window);
if (widget)
Shell::Get()->focus_cycler()->RemoveWidget(widget);
if (delegate_) {
delegate_->OnWindowDestroying();
}
current_state_->OnWindowDestroying(this);
delegate_.reset();
}
void WindowState::OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
CHECK_EQ(window_, window);
if (window_->GetTransparent() &&
ShouldSetExplicitOpaqueRegionsForOcclusion(this)) {
window_->SetOpaqueRegionsForOcclusion({gfx::Rect(new_bounds.size())});
}
if (reason != ui::PropertyChangeReason::FROM_ANIMATION && !is_dragged())
SaveWindowForWindowRestore(this);
}
void WindowState::OnWindowParentChanged(aura::Window* window,
aura::Window* parent) {
if (window != window_) {
return;
}
MaybeRecordPartialDuration();
}
void WindowState::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
if (IsPip() && window == window_) {
if (visible) {
if (!PipPositioner::HasSnapFraction(this) && !IsArcWindow(window)) {
PipPositioner::SaveSnapFraction(this, window_->GetBoundsInScreen());
}
Shell::Get()->pip_controller()->SetPipWindow(window);
} else {
Shell::Get()->pip_controller()->UnsetPipWindow(window);
}
}
if (window != window_->parent()) {
return;
}
if (visible && snap_ratio_ && IsPartial(*snap_ratio_)) {
partial_start_time_ = base::TimeTicks::Now();
}
if (!visible) {
MaybeRecordPartialDuration();
}
}
bool WindowState::CanUnresizableSnapOnDisplay(display::Display display) const {
DCHECK(!CanResize());
if (IsPip())
return false;
if (IsTabletModeEnabled())
return false;
const gfx::Size* preferred_size =
window_->GetProperty(kUnresizableSnappedSizeKey);
if (!preferred_size || preferred_size->IsZero())
return false;
const auto orientation = GetSnapDisplayOrientation(display);
const bool is_horizontal =
orientation == chromeos::OrientationType::kLandscapePrimary ||
orientation == chromeos::OrientationType::kLandscapeSecondary;
const gfx::Rect work_area = display.work_area();
if (is_horizontal && (preferred_size->width() == 0 ||
work_area.width() < preferred_size->width())) {
return false;
}
if (!is_horizontal && (preferred_size->height() == 0 ||
work_area.height() < preferred_size->height())) {
return false;
}
return true;
}
void WindowState::RecordWindowSnapActionSource(
WindowSnapActionSource snap_action_source) {
base::UmaHistogramEnumeration(kWindowSnapActionSourceHistogram,
snap_action_source);
}
void WindowState::CheckAndRecordDragMaximizedBehavior() {
if (!IsMaximized()) {
num_of_drag_to_maximize_mis_triggers_++;
base::UmaHistogramBoolean(kValidDragMaximizedHistogramName, false);
} else {
base::UmaHistogramBoolean(kValidDragMaximizedHistogramName, true);
}
}
void WindowState::ReadOutWindowCycleSnapAction(int message_id) {
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(
l10n_util::GetStringUTF8(message_id));
}
}