[go: nahoru, domu]

blob: ae557d6d464d8d0d7ec467cd9150ade9df1d7662 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/window_state.h"
#include <absl/cleanup/cleanup.h>
#include <optional>
#include <utility>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/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/bounds_tracker/window_bounds_tracker.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/containers/adapters.h"
#include "base/containers/fixed_flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.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/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/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 {
// The threshold for us to judge if drag to maximize behavior is mis-triggered.
// If a window is dragged to maximized and remains maximized longer than this
// threshold, then drag to maximize behavior is not mis-triggered, otherwise it
// will be counted as one mis-trigger.
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;
// This defines the map from different window states to their restore layers.
// The assumption is that a window state with higher restore layer number can
// restore back to a window state with lower restore layer number, but not the
// other way around. For example, a window whose window state is kMinimized can
// restore to kMaximized window state, but kMaximized window state can not
// restore back to kMinimized window state. Please see
// go/window-state-restore-history for details.
// Note the map does not contain all WindowStateTypes, for the ones that's not
// in the map, they can't be put into the window state restore history stack,
// and restore from those state will simply go back to kNormal window state.
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},
});
// Whether the window state type is valid for putting in the restore history.
bool IsValidForRestoreHistory(WindowStateType state_type) {
return kWindowStateRestoreHistoryLayerMap.contains(state_type);
}
// Returns true if |current_state| can restore back to |previous_state|.
// Normally, a state can only restore back to another state at a lower level.
bool CanRestoreState(WindowStateType current_state,
WindowStateType previous_state) {
if (!IsValidForRestoreHistory(current_state) ||
!IsValidForRestoreHistory(previous_state)) {
return false;
}
if (kWindowStateRestoreHistoryLayerMap.at(current_state) >
kWindowStateRestoreHistoryLayerMap.at(previous_state)) {
return true;
}
return false;
}
bool IsTabletModeEnabled() {
return display::Screen::GetScreen()->InTabletMode();
}
bool IsToplevelContainer(aura::Window* window) {
DCHECK(window);
int container_id = window->GetId();
// ArcVirtualKeyboard is implemented as a exo window which requires
// WindowState to manage its state.
return IsActivatableShellWindowId(container_id) ||
container_id == kShellWindowId_ArcVirtualKeyboardContainer;
}
// ARC windows will not be in a top level container until they are associated
// with a task. We still want a WindowState created for these windows and their
// transient children as they will be moved to a top level container soon.
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);
}
// A tentative class to set the bounds on the window.
// TODO(oshima): Once all logic is cleaned up, move this to the real layout
// manager with proper friendship.
class BoundsSetter : public aura::LayoutManager {
public:
BoundsSetter() = default;
BoundsSetter(const BoundsSetter&) = delete;
BoundsSetter& operator=(const BoundsSetter&) = delete;
~BoundsSetter() override = default;
// aura::LayoutManager overrides:
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::WindowShowState requested_show_state) {
switch (requested_show_state) {
case ui::SHOW_STATE_DEFAULT:
case ui::SHOW_STATE_NORMAL:
return WM_EVENT_NORMAL;
case ui::SHOW_STATE_MINIMIZED:
return WM_EVENT_MINIMIZE;
case ui::SHOW_STATE_MAXIMIZED:
return WM_EVENT_MAXIMIZE;
case ui::SHOW_STATE_FULLSCREEN:
return WM_EVENT_FULLSCREEN;
case ui::SHOW_STATE_INACTIVE:
return WM_EVENT_SHOW_INACTIVE;
case ui::SHOW_STATE_END:
NOTREACHED() << "No WMEvent defined for the show state:"
<< requested_show_state;
}
return WM_EVENT_NORMAL;
}
// Returns true if the split view divider exits which should be taken into
// consideration when calculating the snap ratio.
bool ShouldConsiderDivider(aura::Window* window) {
SplitViewController* split_view_controller =
SplitViewController::Get(window->GetRootWindow());
return split_view_controller->InSplitViewMode() &&
split_view_controller->split_view_divider()->divider_widget();
}
// Returns the snap ratio for the given `window` and `snap_event`.
// - In tablet mode, window will snap to the prefixed snap ratios and some
// adjustments will be made to account for window minimum size if needed. See
// `SplitViewController::FindClosestPositionRatio()` for more details;
// - In clamshell mode, window can be snapped with an arbitrary snap ratio and
// we need to consider the window minimum size and adjust the window snap ratio
// before committing the snap event if needed.
float GetTargetSnapRatio(aura::Window* window,
const WindowSnapWMEvent* snap_event) {
if (Shell::Get()->IsInTabletMode()) {
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);
}
// This applies after the wm event has been applied and window bounds have been
// modified.
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) /
static_cast<float>(maximized_bounds.width());
}
return static_cast<float>(target_bounds.height() + divider_delta) /
static_cast<float>(maximized_bounds.height());
}
// Move all transient children to |dst_root|, including the ones in the child
// windows and transient children of the transient children.
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();
DCHECK_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::GetScreen()->GetDisplayNearestWindow(window));
// Transient children may have transient children.
MoveAllTransientChildrenToNewRoot(transient_child);
}
// Move transient children of the child windows if any.
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);
}
// Notifies the window restore controller to write to file.
void SaveWindowForWindowRestore(WindowState* window_state) {
if (auto* controller = WindowRestoreController::Get())
controller->SaveWindow(window_state);
}
bool ShouldSetExplicitOpaqueRegionsForOcclusion(WindowState* window_state) {
// If the window manager manages the window opacity, set the opaque regions
// explicitly if the window must be transparent (e.g. has rounded corners).
return chromeos::ShouldWindowStateHaveRoundedCorners(
window_state->GetStateType()) &&
window_state->window()->GetProperty(
ash::kWindowManagerManagesOpacityKey);
}
} // namespace
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() {
// WindowState is registered as an owned property of |window_|, and window
// unregisters all of its observers in its d'tor before destroying its
// properties. As a result, window_->RemoveObserver() doesn't need to (and
// shouldn't) be called here.
// Records the number of mis-triggers of drag to maximize behavior if
// `window_` has been dragged to maximized during its lifetime.
if (has_ever_been_dragged_to_maximized_) {
base::UmaHistogramCounts100(kDragToMaximizeMisTriggersHistogramName,
num_of_drag_to_maximize_mis_triggers_);
}
}
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::kTrustedPinned;
}
bool WindowState::IsTrustedPinned() const {
return GetStateType() == WindowStateType::kTrustedPinned;
}
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::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::SHOW_STATE_MAXIMIZED);
}
void WindowState::Minimize() {
wm::SetWindowState(window_, ui::SHOW_STATE_MINIMIZED);
}
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()) {
// |window_| is hidden first to avoid canceling fullscreen mode when it is
// no longer always on top and gets added to default container. This avoids
// sending redundant OnFullscreenStateChanged to the layout manager. The
// |window_| visibility is restored after it no longer obscures the
// |window_on_top|.
bool visible = window_->IsVisible();
if (visible)
window_->Hide();
window_->SetProperty(aura::client::kZOrderingKey, ui::ZOrderLevel::kNormal);
// Technically it is possible that a |window_| could make itself
// always_on_top really quickly. This is probably not a realistic case but
// check if the two windows are in the same container just in case.
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) {
// A float/unfloat may trigger another event. If that's the case, we don't
// want to handle the nested event and let the original event take care of
// things.
if (is_handling_float_event_) {
return;
}
if (const WindowSnapWMEvent* snap_event = event->AsSnapEvent()) {
// Save `event` requested snap ratio.
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 {
// If a different snap ratio was requested, partial may have just ended.
MaybeRecordPartialDuration();
}
}
std::unique_ptr<base::AutoReset<bool>> auto_reset;
if (event->type() == WM_EVENT_FLOAT ||
(current_state_->GetType() == chromeos::WindowStateType::kFloated &&
event->IsTransitionEvent())) {
auto_reset = std::make_unique<base::AutoReset<bool>>(
&is_handling_float_event_, true);
}
current_state_->OnWMEvent(this, event);
// The current snap ratio may be different from the requested snap ratio, if
// the window has a minimum size requirement.
if (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;
// Check if window is not work area vertical maximized.
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;
// Shrink from work area maximized window.
if (bounds == work_area) {
new_bounds = gfx::Rect(work_area.x(), restore_bounds.y(), work_area.width(),
restore_bounds.height());
// Restore bounds is not cleared here in case a 2nd shrink is called next.
} else {
ClearRestoreBounds();
}
SetBoundsDirectCrossFade(new_bounds);
return true;
}
bool WindowState::HorizontallyShrinkWindow(const gfx::Rect& work_area) {
if (!HasRestoreBounds())
return false;
// Check if window is not work area horizontal maximized.
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;
// Shrink from work area maximized window.
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));
// If the snap ratio was adjusted, partial may have ended.
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();
}
if (auto* window_bounds_tracker = Shell::Get()->window_bounds_tracker()) {
window_bounds_tracker->SetWindowBoundsChangedByUser(window_,
bounds_changed_by_user);
}
}
std::unique_ptr<PresentationTimeRecorder> WindowState::OnDragStarted(
int window_component) {
DCHECK(drag_details_);
SplitViewController* split_view_controller(
SplitViewController::Get(Shell::GetPrimaryRootWindow()));
DCHECK(split_view_controller);
if (split_view_controller->IsWindowInSplitView(window_)) {
split_view_controller->MaybeDetachWindow(window_);
}
if (delegate_) {
return delegate_->OnDragStarted(window_component);
}
return nullptr;
}
void WindowState::OnCompleteDrag(const gfx::PointF& location) {
DCHECK(drag_details_);
if (delegate_) {
delegate_->OnDragFinished(/*cancel=*/false, location);
}
SaveWindowForWindowRestore(this);
}
void WindowState::OnRevertDrag(const gfx::PointF& location) {
DCHECK(drag_details_);
if (delegate_) {
delegate_->OnDragFinished(/*cancel=*/true, location);
}
}
void WindowState::OnActivationLost() {
if (IsPip()) {
views::Widget::GetWidgetForNativeWindow(window_)
->widget_delegate()
->SetCanActivate(false);
}
}
display::Display WindowState::GetDisplay() const {
return display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
}
WindowStateType WindowState::GetRestoreWindowState() const {
WindowStateType restore_state =
window_state_restore_history_.empty() ||
window_state_restore_history_.back() == WindowStateType::kDefault
? WindowStateType::kNormal
: window_state_restore_history_.back();
// Floated state has a limitation of one floated window per desk. So if we try
// to restore a window to floated state, and there is a existing floated
// window on the desk, we do not float the window as doing so would unfloat
// the existing floated window.
if (IsMinimized() && restore_state == WindowStateType::kFloated) {
if (window_util::GetFloatedWindowForActiveDesk()) {
return IsTabletModeEnabled() ? GetWindowTypeOnMaximizable()
: WindowStateType::kNormal;
}
}
// Different with the restore behaviors in clamshell mode, a window can not be
// restored to kNormal window state if it's a maximize-able window.
// We should still be able to restore a fullscreen/minimized/snapped window to
// kMaximized window state for a maximize-able window, and also should be able
// to support restoring a fullscreen/minimized/maximized window to snapped
// window states.
if (IsTabletModeEnabled()) {
// In tablet mode, if we reset a floated window that's previously snapped
// (float another window will reset currently floated window), maximize
// floated window instead of restore floated window back to snapped state.
if (restore_state == WindowStateType::kNormal ||
(IsFloated() &&
(restore_state == WindowStateType::kPrimarySnapped ||
restore_state == WindowStateType::kSecondarySnapped))) {
restore_state = GetWindowTypeOnMaximizable();
}
}
return restore_state;
}
void WindowState::TrackDragToMaximizeBehavior() {
if (!has_ever_been_dragged_to_maximized_)
has_ever_been_dragged_to_maximized_ = true;
// If drag to maximize is triggered again before we check for the previous
// one, then the previous one must be a mis-trigger. Record the mis-trigger
// and reset `drag_to_maximize_mis_trigger_timer_`.
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::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) {
// Tablet mode should use bounds calculation in SplitViewController.
// However, transient state from transitioning clamshell to tablet mode
// might end up calling this function during work area changes, so we avoid
// unnecessary task in that case when it will be overwritten by tablet mode
// work.
if (is_dragged() || !IsSnapped() ||
display::Screen::GetScreen()->InTabletMode()) {
return;
}
gfx::Rect maximized_bounds =
screen_util::GetMaximizedWindowBoundsInParent(window_);
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
// For snapped window, `GetSnappedWindowBounds` computes bounds position
// from snap type and size from |snap_ratio|.
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_| exists adjust the size of the window. Otherwise only
// maximize it vertically for horizontal screen and maximize horizontally for
// vertical screen.
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::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());
}
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(
WindowStateType old_window_state_type) {
for (auto& observer : observer_list_)
observer.OnPostWindowStateTypeChange(this, old_window_state_type);
OnPostPipStateChange(old_window_state_type);
UpdateWindowStateRestoreHistoryStack(old_window_state_type);
SaveWindowForWindowRestore(this);
if (chromeos::IsSnappedWindowStateType(old_window_state_type)) {
// If the state type is no longer snapped, partial may have ended.
MaybeRecordPartialDuration();
}
}
void WindowState::OnPostPipStateChange(WindowStateType old_window_state_type) {
if (old_window_state_type == WindowStateType::kPip) {
// The animation type may be FADE_OUT_SLIDE_IN at this point, which we don't
// want it to be anymore if the window is not PIP anymore.
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) {
gfx::Rect actual_new_bounds(bounds);
// Ensure we don't go smaller than our minimum bounds in "normal" window
// modes
if (window_->delegate() && !IsMaximized() && !IsFullscreen()) {
// Get the minimum usable size of the minimum size and the screen size.
gfx::Size min_size = window_->delegate()
? window_->delegate()->GetMinimumSize()
: gfx::Size();
gfx::Size max_size = window_->delegate()
? window_->delegate()->GetMaximumSize()
: gfx::Size();
const display::Display display =
display::Screen::GetScreen()->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()));
}
// Changing the size of the PIP window can detach it from one of the edges
// of the screen, which makes the snap fraction logic fail. Ensure to snap
// it again.
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) {
gfx::Rect work_area_in_parent =
screen_util::GetDisplayWorkAreaBoundsInParent(window_);
gfx::Rect child_bounds(bounds);
if (window_->GetType() == aura::client::WINDOW_TYPE_NORMAL) {
// Normal windows should have the top of the bounds visible.
AdjustBoundsToEnsureWindowVisibility(work_area_in_parent, 0, 0,
&child_bounds);
} else {
// Other types of window can have the top of the bounds outside
// of the screen, but we still require their size to be smaller
// than the screen.
AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds);
}
SetBoundsDirect(child_bounds);
}
void WindowState::SetBoundsDirectAnimated(const gfx::Rect& bounds,
base::TimeDelta duration,
gfx::Tween::Type tween_type) {
if (wm::WindowAnimationsDisabled(window_)) {
SetBoundsDirect(bounds);
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);
}
void WindowState::SetBoundsDirectCrossFade(const gfx::Rect& new_bounds,
std::optional<bool> float_state) {
// Some test results in invoking CrossFadeToBounds when window is not visible.
// No animation is necessary in that case, thus just change the bounds and
// quit.
if (!window_->TargetVisibility()) {
SetBoundsConstrained(new_bounds);
return;
}
// If the window already has a transform in place, do not use the cross fade
// animation, set the bounds directly instead, or animation is disabled.
if (!window_->layer()->GetTargetTransform().IsIdentity() ||
wm::WindowAnimationsDisabled(window_)) {
SetBoundsDirect(new_bounds);
return;
}
// Create fresh layers for the window and all its children to paint into.
// Takes ownership of the old layer and all its children, which will be
// cleaned up after the animation completes.
// Specify |set_bounds| to true here to keep the old bounds in the child
// windows of |window|.
std::unique_ptr<ui::LayerTreeOwner> old_layer_owner =
wm::RecreateLayers(window_);
// Resize the window to the new size, which will force a layout and paint.
SetBoundsDirect(new_bounds);
if (float_state) {
CrossFadeAnimationForFloatUnfloat(window_, std::move(old_layer_owner),
*float_state);
return;
}
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()) {
// Set this window to `PipController`.
// The window has to be set to the controller before
// `widget->Deactivate()` because this sometimes calls
// `PipController::UpdatePipBounds()`.
pip_controller->SetPipWindow(window_);
CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
window_, CollisionDetectionUtils::RelativePriority::kPictureInPicture);
// widget may not exit in some unit tests.
// TODO(oshima): Fix unit tests and add DCHECK.
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);
// There may already be a system ui window on the initial position.
pip_controller->UpdatePipBounds();
if (!was_pip) {
if (widget && widget->GetContentsView()) {
widget->GetContentsView()->GetViewAccessibility().AnnounceText(
l10n_util::GetStringUTF16(IDS_ENTER_PIP_A11Y_NOTIFICATION));
}
}
CollectPipEnterExitMetrics(/*enter=*/true);
// PIP window shouldn't be tracked in MruWindowTracker.
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(/*enter=*/false);
window_->ClearProperty(ash::kExcludeInMruKey);
// Unset PiP window when exiting PiP state to another state.
pip_controller->UnsetPipWindow(window_);
}
// PIP uses the snap fraction to place the PIP window at the correct position
// after screen rotation, system UI area change, etc. Make sure to reset this
// when the window enters/exits PIP so the obsolete fraction won't be used.
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() {
// No-op if `partial_start_time_` is null, i.e. partial never started.
if (!partial_start_time_.is_null()) {
base::UmaHistogramCustomCounts(
kPartialSplitDurationHistogramName,
(base::TimeTicks::Now() - partial_start_time_).InMinutes(), /*min=*/1,
/*exclusive_max=*/base::Days(7).InMinutes(), 50);
partial_start_time_ = base::TimeTicks();
}
}
void WindowState::UpdateWindowStateRestoreHistoryStack(
chromeos::WindowStateType previous_state_type) {
WindowStateType current_state_type = GetStateType();
if (!IsValidForRestoreHistory(current_state_type)) {
window_state_restore_history_.clear();
window_->ClearProperty(aura::client::kRestoreShowStateKey);
return;
}
// We'll need to pop out any window state that the `current_state_type` can
// not restore back to (i.e., whose restore order is equal or higher than
// `current_state_type`).
for (auto state : base::Reversed(window_state_restore_history_)) {
if (CanRestoreState(current_state_type, state)) {
break;
}
window_state_restore_history_.pop_back();
}
if (IsValidForRestoreHistory(previous_state_type) &&
CanRestoreState(current_state_type, previous_state_type)) {
window_state_restore_history_.push_back(previous_state_type);
}
// TODO(xdai): For now we don't save the restore history in tablet mode in the
// window property, so that when exiting tablet mode, the window can still
// restore back to its old window state (see the test case
// TabletModeWindowManagerTest.UnminimizeInTabletMode). We should revisit this
// logic.
if (!IsTabletModeEnabled()) {
window_->SetProperty(aura::client::kRestoreShowStateKey,
chromeos::ToWindowShowState(GetRestoreWindowState()));
}
// This is a special logic for windows that were created from full restore.
// In those cases, the full history of window states is truncated. We detect
// this by asserting that any non-normal windows that have no previous history
// must have a truncated history.
//
// Unfortunately this case is particularly tricky because the restore bounds
// will be set externally, using the same windows property key.
//
// If we detect that we are in full restore, we will artificially create a
// normal restore state in history to retain the bounds.
if (window_state_restore_history_.empty() && HasRestoreBounds() &&
!IsNormalStateType()) {
window_state_restore_history_.push_back(WindowStateType::kDefault);
}
}
chromeos::WindowStateType WindowState::GetWindowTypeOnMaximizable() const {
return CanMaximize() && wm::GetTransientParent(window_) == nullptr
? WindowStateType::kMaximized
: WindowStateType::kNormal;
}
// static
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());
// WindowState is only for windows in top level container, unless they are
// temporarily hidden when launched by window restore. The will be reparented
// to a top level container soon, and need a WindowState.
if (!IsToplevelContainer(window->parent()) &&
!IsTemporarilyHiddenForFullrestore(window)) {
return nullptr;
}
state = new WindowState(window);
window->SetProperty(kWindowStateKey, state);
return state;
}
// static
const WindowState* WindowState::Get(const aura::Window* window) {
return Get(const_cast<aura::Window*>(window));
}
// static
WindowState* WindowState::ForActiveWindow() {
aura::Window* active = window_util::GetActiveWindow();
return active ? WindowState::Get(active) : nullptr;
}
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 {
// Currently "restore" is not implemented.
NOTIMPLEMENTED();
}
return;
}
if (key == chromeos::kWindowStateTypeKey) {
if (!ignore_property_change_) {
// This change came from somewhere else. Revert it.
window->SetProperty(chromeos::kWindowStateTypeKey, GetStateType());
}
return;
}
if (key == aura::client::kWindowWorkspaceKey ||
key == aura::client::kDeskUuidKey) {
// Save the window for window restore purposes unless
// |ignore_property_change_| is true. Note that moving windows across
// displays will also trigger a kWindowWorkspaceKey change, even if the
// value stays the same, so we do not need to save the window when it
// changes root windows (OnWindowAddedToRootWindow).
if (!ignore_property_change_)
SaveWindowForWindowRestore(this);
return;
}
// The shelf visibility should be updated if kHideShelfWhenFullscreenKey or
// kImmersiveIsActive change - these property affect the shelf behavior, and
// the shelf is expected to be hidden when fullscreen or immersive mode start.
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 the window is destroyed during PIP, count that as exiting.
if (IsPip())
CollectPipEnterExitMetrics(/*enter=*/false);
MaybeRecordPartialDuration();
auto* widget = views::Widget::GetWidgetForNativeWindow(window);
if (widget)
Shell::Get()->focus_cycler()->RemoveWidget(widget);
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;
}
// If the window is moved to another desk, partial may have ended.
MaybeRecordPartialDuration();
}
void WindowState::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
if (IsPip() && window == window_) {
if (visible) {
// If this window is a PiP and its SnapFraction is null.
// Note that, at this point, ARC PiP may not be ready as visibility can be
// updated when it transitions from minimized to PiP. In this case, snap
// fraction is updated in
// `ClientControlledShellSurface::OnPostWidgetCommit`.
if (!PipPositioner::HasSnapFraction(this) && !IsArcWindow(window)) {
PipPositioner::SaveSnapFraction(this, window_->GetBoundsInScreen());
}
Shell::Get()->pip_controller()->SetPipWindow(window);
} else {
Shell::Get()->pip_controller()->UnsetPipWindow(window);
}
}
// From here, we are only interested if the parent visibility changes, i.e.
// desk changes.
if (window != window_->parent()) {
return;
}
// If the parent just became visible and `window_` is partial split, start
// recording.
if (visible && snap_ratio_ && IsPartial(*snap_ratio_)) {
partial_start_time_ = base::TimeTicks::Now();
}
// If the parent is no longer visible, partial may have ended.
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));
}
} // namespace ash