1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ash/wm/window_state.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "ash/focus_cycler.h"
11 #include "ash/metrics/pip_uma.h"
12 #include "ash/public/cpp/app_types.h"
13 #include "ash/public/cpp/shell_window_ids.h"
14 #include "ash/public/cpp/window_animation_types.h"
15 #include "ash/public/cpp/window_properties.h"
16 #include "ash/screen_util.h"
17 #include "ash/shell.h"
18 #include "ash/wm/collision_detection/collision_detection_utils.h"
19 #include "ash/wm/default_state.h"
20 #include "ash/wm/pip/pip_positioner.h"
21 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
22 #include "ash/wm/window_animations.h"
23 #include "ash/wm/window_positioning_utils.h"
24 #include "ash/wm/window_properties.h"
25 #include "ash/wm/window_state_delegate.h"
26 #include "ash/wm/window_state_observer.h"
27 #include "ash/wm/window_util.h"
28 #include "ash/wm/wm_event.h"
29 #include "base/auto_reset.h"
30 #include "base/metrics/histogram_functions.h"
31 #include "base/metrics/histogram_macros.h"
32 #include "chromeos/ui/base/window_pin_type.h"
33 #include "chromeos/ui/base/window_properties.h"
34 #include "chromeos/ui/base/window_state_type.h"
35 #include "ui/aura/client/aura_constants.h"
36 #include "ui/aura/layout_manager.h"
37 #include "ui/aura/window.h"
38 #include "ui/aura/window_delegate.h"
39 #include "ui/compositor/layer_tree_owner.h"
40 #include "ui/compositor/paint_recorder.h"
41 #include "ui/compositor/scoped_layer_animation_settings.h"
42 #include "ui/display/display.h"
43 #include "ui/display/screen.h"
44 #include "ui/views/painter.h"
45 #include "ui/views/widget/widget.h"
46 #include "ui/views/widget/widget_delegate.h"
47 #include "ui/wm/core/coordinate_conversion.h"
48 #include "ui/wm/core/ime_util_chromeos.h"
49 #include "ui/wm/core/window_util.h"
50
51 namespace ash {
52 namespace {
53
54 using ::chromeos::kHideShelfWhenFullscreenKey;
55 using ::chromeos::kImmersiveIsActive;
56 using ::chromeos::kWindowManagerManagesOpacityKey;
57 using ::chromeos::kWindowPinTypeKey;
58 using ::chromeos::WindowPinType;
59 using ::chromeos::WindowStateType;
60
IsTabletModeEnabled()61 bool IsTabletModeEnabled() {
62 return Shell::Get()->tablet_mode_controller()->InTabletMode();
63 }
64
IsToplevelContainer(aura::Window * window)65 bool IsToplevelContainer(aura::Window* window) {
66 DCHECK(window);
67 int container_id = window->id();
68 // ArcVirtualKeyboard is implemented as a exo window which requires
69 // WindowState to manage its state.
70 return IsActivatableShellWindowId(container_id) ||
71 container_id == kShellWindowId_ArcVirtualKeyboardContainer;
72 }
73
74 // A tentative class to set the bounds on the window.
75 // TODO(oshima): Once all logic is cleaned up, move this to the real layout
76 // manager with proper friendship.
77 class BoundsSetter : public aura::LayoutManager {
78 public:
79 BoundsSetter() = default;
80 ~BoundsSetter() override = default;
81
82 // aura::LayoutManager overrides:
OnWindowResized()83 void OnWindowResized() override {}
OnWindowAddedToLayout(aura::Window * child)84 void OnWindowAddedToLayout(aura::Window* child) override {}
OnWillRemoveWindowFromLayout(aura::Window * child)85 void OnWillRemoveWindowFromLayout(aura::Window* child) override {}
OnWindowRemovedFromLayout(aura::Window * child)86 void OnWindowRemovedFromLayout(aura::Window* child) override {}
OnChildWindowVisibilityChanged(aura::Window * child,bool visible)87 void OnChildWindowVisibilityChanged(aura::Window* child,
88 bool visible) override {}
SetChildBounds(aura::Window * child,const gfx::Rect & requested_bounds)89 void SetChildBounds(aura::Window* child,
90 const gfx::Rect& requested_bounds) override {}
91
SetBounds(aura::Window * window,const gfx::Rect & bounds)92 void SetBounds(aura::Window* window, const gfx::Rect& bounds) {
93 SetChildBoundsDirect(window, bounds);
94 }
95
96 private:
97 DISALLOW_COPY_AND_ASSIGN(BoundsSetter);
98 };
99
WMEventTypeFromShowState(ui::WindowShowState requested_show_state)100 WMEventType WMEventTypeFromShowState(ui::WindowShowState requested_show_state) {
101 switch (requested_show_state) {
102 case ui::SHOW_STATE_DEFAULT:
103 case ui::SHOW_STATE_NORMAL:
104 return WM_EVENT_NORMAL;
105 case ui::SHOW_STATE_MINIMIZED:
106 return WM_EVENT_MINIMIZE;
107 case ui::SHOW_STATE_MAXIMIZED:
108 return WM_EVENT_MAXIMIZE;
109 case ui::SHOW_STATE_FULLSCREEN:
110 return WM_EVENT_FULLSCREEN;
111 case ui::SHOW_STATE_INACTIVE:
112 return WM_EVENT_SHOW_INACTIVE;
113
114 case ui::SHOW_STATE_END:
115 NOTREACHED() << "No WMEvent defined for the show state:"
116 << requested_show_state;
117 }
118 return WM_EVENT_NORMAL;
119 }
120
WMEventTypeFromWindowPinType(chromeos::WindowPinType type)121 WMEventType WMEventTypeFromWindowPinType(chromeos::WindowPinType type) {
122 switch (type) {
123 case chromeos::WindowPinType::kNone:
124 return WM_EVENT_NORMAL;
125 case chromeos::WindowPinType::kPinned:
126 return WM_EVENT_PIN;
127 case chromeos::WindowPinType::kTrustedPinned:
128 return WM_EVENT_TRUSTED_PIN;
129 }
130 NOTREACHED() << "No WMEvent defined for the window pin type:" << type;
131 return WM_EVENT_NORMAL;
132 }
133
GetCurrentSnappedWidthRatio(aura::Window * window)134 float GetCurrentSnappedWidthRatio(aura::Window* window) {
135 gfx::Rect maximized_bounds =
136 screen_util::GetMaximizedWindowBoundsInParent(window);
137 return static_cast<float>(window->bounds().width()) /
138 static_cast<float>(maximized_bounds.width());
139 }
140
141 // Move all transient children to |dst_root|, including the ones in the child
142 // windows and transient children of the transient children.
MoveAllTransientChildrenToNewRoot(aura::Window * window)143 void MoveAllTransientChildrenToNewRoot(aura::Window* window) {
144 aura::Window* dst_root = window->GetRootWindow();
145 for (aura::Window* transient_child : ::wm::GetTransientChildren(window)) {
146 if (!transient_child->parent())
147 continue;
148 const int container_id = transient_child->parent()->id();
149 DCHECK_GE(container_id, 0);
150 aura::Window* container = dst_root->GetChildById(container_id);
151 if (container->Contains(transient_child))
152 continue;
153 gfx::Rect child_bounds = transient_child->bounds();
154 ::wm::ConvertRectToScreen(dst_root, &child_bounds);
155 container->AddChild(transient_child);
156 transient_child->SetBoundsInScreen(
157 child_bounds,
158 display::Screen::GetScreen()->GetDisplayNearestWindow(window));
159
160 // Transient children may have transient children.
161 MoveAllTransientChildrenToNewRoot(transient_child);
162 }
163 // Move transient children of the child windows if any.
164 for (aura::Window* child : window->children())
165 MoveAllTransientChildrenToNewRoot(child);
166 }
167
ReportAshPipEvents(AshPipEvents event)168 void ReportAshPipEvents(AshPipEvents event) {
169 UMA_HISTOGRAM_ENUMERATION(kAshPipEventsHistogramName, event);
170 }
171
ReportAshPipAndroidPipUseTime(base::TimeDelta duration)172 void ReportAshPipAndroidPipUseTime(base::TimeDelta duration) {
173 UMA_HISTOGRAM_CUSTOM_TIMES(kAshPipAndroidPipUseTimeHistogramName, duration,
174 base::TimeDelta::FromSeconds(1),
175 base::TimeDelta::FromHours(10), 50);
176 }
177
178 } // namespace
179
180 constexpr base::TimeDelta WindowState::kBoundsChangeSlideDuration;
181
ScopedBoundsChangeAnimation(aura::Window * window,BoundsChangeAnimationType bounds_animation_type)182 WindowState::ScopedBoundsChangeAnimation::ScopedBoundsChangeAnimation(
183 aura::Window* window,
184 BoundsChangeAnimationType bounds_animation_type)
185 : window_(window) {
186 window_->AddObserver(this);
187 previous_bounds_animation_type_ =
188 WindowState::Get(window_)->bounds_animation_type_;
189 WindowState::Get(window_)->bounds_animation_type_ = bounds_animation_type;
190 }
191
~ScopedBoundsChangeAnimation()192 WindowState::ScopedBoundsChangeAnimation::~ScopedBoundsChangeAnimation() {
193 if (window_) {
194 WindowState::Get(window_)->bounds_animation_type_ =
195 previous_bounds_animation_type_;
196 window_->RemoveObserver(this);
197 window_ = nullptr;
198 }
199 }
200
OnWindowDestroying(aura::Window * window)201 void WindowState::ScopedBoundsChangeAnimation::OnWindowDestroying(
202 aura::Window* window) {
203 window_->RemoveObserver(this);
204 window_ = nullptr;
205 }
206
~WindowState()207 WindowState::~WindowState() {
208 // WindowState is registered as an owned property of |window_|, and window
209 // unregisters all of its observers in its d'tor before destroying its
210 // properties. As a result, window_->RemoveObserver() doesn't need to (and
211 // shouldn't) be called here.
212 }
213
HasDelegate() const214 bool WindowState::HasDelegate() const {
215 return !!delegate_;
216 }
217
SetDelegate(std::unique_ptr<WindowStateDelegate> delegate)218 void WindowState::SetDelegate(std::unique_ptr<WindowStateDelegate> delegate) {
219 DCHECK((!delegate_.get() && !!delegate.get()) ||
220 (!!delegate_.get() && !delegate.get()));
221 delegate_ = std::move(delegate);
222 }
223
GetStateType() const224 WindowStateType WindowState::GetStateType() const {
225 return current_state_->GetType();
226 }
227
IsMinimized() const228 bool WindowState::IsMinimized() const {
229 return IsMinimizedWindowStateType(GetStateType());
230 }
231
IsMaximized() const232 bool WindowState::IsMaximized() const {
233 return GetStateType() == WindowStateType::kMaximized;
234 }
235
IsFullscreen() const236 bool WindowState::IsFullscreen() const {
237 return GetStateType() == WindowStateType::kFullscreen;
238 }
239
IsMaximizedOrFullscreenOrPinned() const240 bool WindowState::IsMaximizedOrFullscreenOrPinned() const {
241 return IsMaximizedOrFullscreenOrPinnedWindowStateType(GetStateType());
242 }
243
IsSnapped() const244 bool WindowState::IsSnapped() const {
245 return GetStateType() == WindowStateType::kLeftSnapped ||
246 GetStateType() == WindowStateType::kRightSnapped;
247 }
248
IsPinned() const249 bool WindowState::IsPinned() const {
250 return GetStateType() == WindowStateType::kPinned ||
251 GetStateType() == WindowStateType::kTrustedPinned;
252 }
253
IsTrustedPinned() const254 bool WindowState::IsTrustedPinned() const {
255 return GetStateType() == WindowStateType::kTrustedPinned;
256 }
257
IsPip() const258 bool WindowState::IsPip() const {
259 return GetStateType() == WindowStateType::kPip;
260 }
261
IsNormalStateType() const262 bool WindowState::IsNormalStateType() const {
263 return IsNormalWindowStateType(GetStateType());
264 }
265
IsNormalOrSnapped() const266 bool WindowState::IsNormalOrSnapped() const {
267 return IsNormalStateType() || IsSnapped();
268 }
269
IsActive() const270 bool WindowState::IsActive() const {
271 return wm::IsActiveWindow(window_);
272 }
273
IsUserPositionable() const274 bool WindowState::IsUserPositionable() const {
275 return window_util::IsWindowUserPositionable(window_);
276 }
277
HasMaximumWidthOrHeight() const278 bool WindowState::HasMaximumWidthOrHeight() const {
279 if (!window_->delegate())
280 return false;
281
282 const gfx::Size max_size = window_->delegate()->GetMaximumSize();
283 return max_size.width() || max_size.height();
284 }
285
CanMaximize() const286 bool WindowState::CanMaximize() const {
287 // Window must allow maximization and have no maximum width or height.
288 if ((window_->GetProperty(aura::client::kResizeBehaviorKey) &
289 aura::client::kResizeBehaviorCanMaximize) == 0) {
290 return false;
291 }
292
293 return !HasMaximumWidthOrHeight();
294 }
295
CanMinimize() const296 bool WindowState::CanMinimize() const {
297 return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
298 aura::client::kResizeBehaviorCanMinimize) != 0;
299 }
300
CanResize() const301 bool WindowState::CanResize() const {
302 return (window_->GetProperty(aura::client::kResizeBehaviorKey) &
303 aura::client::kResizeBehaviorCanResize) != 0;
304 }
305
CanActivate() const306 bool WindowState::CanActivate() const {
307 return wm::CanActivateWindow(window_);
308 }
309
CanSnap() const310 bool WindowState::CanSnap() const {
311 if (!CanResize() || IsPip())
312 return false;
313
314 // Allow windows with no maximum width or height to be snapped.
315 // TODO(oshima): We should probably snap if the maximum size is defined
316 // and greater than the snapped size.
317 return !HasMaximumWidthOrHeight();
318 }
319
HasRestoreBounds() const320 bool WindowState::HasRestoreBounds() const {
321 gfx::Rect* bounds = window_->GetProperty(aura::client::kRestoreBoundsKey);
322 return bounds != nullptr && !bounds->IsEmpty();
323 }
324
Maximize()325 void WindowState::Maximize() {
326 ::wm::SetWindowState(window_, ui::SHOW_STATE_MAXIMIZED);
327 }
328
Minimize()329 void WindowState::Minimize() {
330 ::wm::SetWindowState(window_, ui::SHOW_STATE_MINIMIZED);
331 }
332
Unminimize()333 void WindowState::Unminimize() {
334 ::wm::Unminimize(window_);
335 }
336
Activate()337 void WindowState::Activate() {
338 wm::ActivateWindow(window_);
339 }
340
Deactivate()341 void WindowState::Deactivate() {
342 wm::DeactivateWindow(window_);
343 }
344
Restore()345 void WindowState::Restore() {
346 if (!IsNormalStateType()) {
347 const WMEvent event(WM_EVENT_NORMAL);
348 OnWMEvent(&event);
349 }
350 }
351
DisableZOrdering(aura::Window * window_on_top)352 void WindowState::DisableZOrdering(aura::Window* window_on_top) {
353 ui::ZOrderLevel z_order = GetZOrdering();
354 if (z_order != ui::ZOrderLevel::kNormal && !IsPip()) {
355 // |window_| is hidden first to avoid canceling fullscreen mode when it is
356 // no longer always on top and gets added to default container. This avoids
357 // sending redundant OnFullscreenStateChanged to the layout manager. The
358 // |window_| visibility is restored after it no longer obscures the
359 // |window_on_top|.
360 bool visible = window_->IsVisible();
361 if (visible)
362 window_->Hide();
363 window_->SetProperty(aura::client::kZOrderingKey, ui::ZOrderLevel::kNormal);
364 // Technically it is possible that a |window_| could make itself
365 // always_on_top really quickly. This is probably not a realistic case but
366 // check if the two windows are in the same container just in case.
367 if (window_on_top && window_on_top->parent() == window_->parent())
368 window_->parent()->StackChildAbove(window_on_top, window_);
369 if (visible)
370 window_->Show();
371 cached_z_order_ = z_order;
372 }
373 }
374
RestoreZOrdering()375 void WindowState::RestoreZOrdering() {
376 if (cached_z_order_ != ui::ZOrderLevel::kNormal) {
377 window_->SetProperty(aura::client::kZOrderingKey, cached_z_order_);
378 cached_z_order_ = ui::ZOrderLevel::kNormal;
379 }
380 }
381
OnWMEvent(const WMEvent * event)382 void WindowState::OnWMEvent(const WMEvent* event) {
383 current_state_->OnWMEvent(this, event);
384
385 UpdateSnappedWidthRatio(event);
386 }
387
SaveCurrentBoundsForRestore()388 void WindowState::SaveCurrentBoundsForRestore() {
389 gfx::Rect bounds_in_screen = window_->GetTargetBounds();
390 ::wm::ConvertRectToScreen(window_->parent(), &bounds_in_screen);
391 SetRestoreBoundsInScreen(bounds_in_screen);
392 }
393
GetRestoreBoundsInScreen() const394 gfx::Rect WindowState::GetRestoreBoundsInScreen() const {
395 gfx::Rect* restore_bounds =
396 window_->GetProperty(aura::client::kRestoreBoundsKey);
397 return restore_bounds ? *restore_bounds : gfx::Rect();
398 }
399
GetRestoreBoundsInParent() const400 gfx::Rect WindowState::GetRestoreBoundsInParent() const {
401 gfx::Rect result = GetRestoreBoundsInScreen();
402 ::wm::ConvertRectFromScreen(window_->parent(), &result);
403 return result;
404 }
405
SetRestoreBoundsInScreen(const gfx::Rect & bounds)406 void WindowState::SetRestoreBoundsInScreen(const gfx::Rect& bounds) {
407 window_->SetProperty(aura::client::kRestoreBoundsKey, bounds);
408 }
409
SetRestoreBoundsInParent(const gfx::Rect & bounds)410 void WindowState::SetRestoreBoundsInParent(const gfx::Rect& bounds) {
411 gfx::Rect bounds_in_screen = bounds;
412 ::wm::ConvertRectToScreen(window_->parent(), &bounds_in_screen);
413 SetRestoreBoundsInScreen(bounds_in_screen);
414 }
415
ClearRestoreBounds()416 void WindowState::ClearRestoreBounds() {
417 window_->ClearProperty(aura::client::kRestoreBoundsKey);
418 window_->ClearProperty(::wm::kVirtualKeyboardRestoreBoundsKey);
419 }
420
SetStateObject(std::unique_ptr<WindowState::State> new_state)421 std::unique_ptr<WindowState::State> WindowState::SetStateObject(
422 std::unique_ptr<WindowState::State> new_state) {
423 current_state_->DetachState(this);
424 std::unique_ptr<WindowState::State> old_object = std::move(current_state_);
425 current_state_ = std::move(new_state);
426 current_state_->AttachState(this, old_object.get());
427 return old_object;
428 }
429
UpdateSnappedWidthRatio(const WMEvent * event)430 void WindowState::UpdateSnappedWidthRatio(const WMEvent* event) {
431 if (!IsSnapped()) {
432 snapped_width_ratio_.reset();
433 return;
434 }
435
436 const WMEventType type = event->type();
437 // Initializes |snapped_width_ratio_| whenever |event| is snapping event.
438 if (type == WM_EVENT_SNAP_LEFT || type == WM_EVENT_SNAP_RIGHT ||
439 type == WM_EVENT_CYCLE_SNAP_LEFT || type == WM_EVENT_CYCLE_SNAP_RIGHT) {
440 // Since |UpdateSnappedWidthRatio()| is called post WMEvent taking effect,
441 // |window_|'s bounds is in a correct state for ratio update.
442 snapped_width_ratio_ =
443 base::make_optional(GetCurrentSnappedWidthRatio(window_));
444 return;
445 }
446
447 // |snapped_width_ratio_| under snapped state may change due to bounds event.
448 if (event->IsBoundsEvent()) {
449 snapped_width_ratio_ =
450 base::make_optional(GetCurrentSnappedWidthRatio(window_));
451 }
452 }
453
SetPreAutoManageWindowBounds(const gfx::Rect & bounds)454 void WindowState::SetPreAutoManageWindowBounds(const gfx::Rect& bounds) {
455 pre_auto_manage_window_bounds_ = base::make_optional(bounds);
456 }
457
SetPreAddedToWorkspaceWindowBounds(const gfx::Rect & bounds)458 void WindowState::SetPreAddedToWorkspaceWindowBounds(const gfx::Rect& bounds) {
459 pre_added_to_workspace_window_bounds_ = base::make_optional(bounds);
460 }
461
SetPersistentWindowInfo(const PersistentWindowInfo & persistent_window_info)462 void WindowState::SetPersistentWindowInfo(
463 const PersistentWindowInfo& persistent_window_info) {
464 persistent_window_info_ = base::make_optional(persistent_window_info);
465 }
466
ResetPersistentWindowInfo()467 void WindowState::ResetPersistentWindowInfo() {
468 persistent_window_info_.reset();
469 }
470
AddObserver(WindowStateObserver * observer)471 void WindowState::AddObserver(WindowStateObserver* observer) {
472 observer_list_.AddObserver(observer);
473 }
474
RemoveObserver(WindowStateObserver * observer)475 void WindowState::RemoveObserver(WindowStateObserver* observer) {
476 observer_list_.RemoveObserver(observer);
477 }
478
GetHideShelfWhenFullscreen() const479 bool WindowState::GetHideShelfWhenFullscreen() const {
480 return window_->GetProperty(kHideShelfWhenFullscreenKey);
481 }
482
SetHideShelfWhenFullscreen(bool value)483 void WindowState::SetHideShelfWhenFullscreen(bool value) {
484 base::AutoReset<bool> resetter(&ignore_property_change_, true);
485 window_->SetProperty(kHideShelfWhenFullscreenKey, value);
486 }
487
GetWindowPositionManaged() const488 bool WindowState::GetWindowPositionManaged() const {
489 return window_->GetProperty(kWindowPositionManagedTypeKey);
490 }
491
SetWindowPositionManaged(bool managed)492 void WindowState::SetWindowPositionManaged(bool managed) {
493 window_->SetProperty(kWindowPositionManagedTypeKey, managed);
494 }
495
CanConsumeSystemKeys() const496 bool WindowState::CanConsumeSystemKeys() const {
497 return window_->GetProperty(kCanConsumeSystemKeysKey);
498 }
499
SetCanConsumeSystemKeys(bool can_consume_system_keys)500 void WindowState::SetCanConsumeSystemKeys(bool can_consume_system_keys) {
501 window_->SetProperty(kCanConsumeSystemKeysKey, can_consume_system_keys);
502 }
503
IsInImmersiveFullscreen() const504 bool WindowState::IsInImmersiveFullscreen() const {
505 return window_->GetProperty(kImmersiveIsActive);
506 }
507
set_bounds_changed_by_user(bool bounds_changed_by_user)508 void WindowState::set_bounds_changed_by_user(bool bounds_changed_by_user) {
509 bounds_changed_by_user_ = bounds_changed_by_user;
510 if (bounds_changed_by_user) {
511 pre_auto_manage_window_bounds_.reset();
512 pre_added_to_workspace_window_bounds_.reset();
513 persistent_window_info_.reset();
514 }
515 }
516
OnDragStarted(int window_component)517 void WindowState::OnDragStarted(int window_component) {
518 DCHECK(drag_details_);
519 if (delegate_)
520 delegate_->OnDragStarted(window_component);
521 }
522
OnCompleteDrag(const gfx::PointF & location)523 void WindowState::OnCompleteDrag(const gfx::PointF& location) {
524 DCHECK(drag_details_);
525 if (delegate_)
526 delegate_->OnDragFinished(/*canceled=*/false, location);
527 }
528
OnRevertDrag(const gfx::PointF & location)529 void WindowState::OnRevertDrag(const gfx::PointF& location) {
530 DCHECK(drag_details_);
531 if (delegate_)
532 delegate_->OnDragFinished(/*canceled=*/true, location);
533 }
534
OnActivationLost()535 void WindowState::OnActivationLost() {
536 if (IsPip()) {
537 views::Widget::GetWidgetForNativeWindow(window())
538 ->widget_delegate()
539 ->SetCanActivate(false);
540 }
541 }
542
GetDisplay()543 display::Display WindowState::GetDisplay() {
544 return display::Screen::GetScreen()->GetDisplayNearestWindow(window());
545 }
546
CreateDragDetails(const gfx::PointF & point_in_parent,int window_component,::wm::WindowMoveSource source)547 void WindowState::CreateDragDetails(const gfx::PointF& point_in_parent,
548 int window_component,
549 ::wm::WindowMoveSource source) {
550 drag_details_ = std::make_unique<DragDetails>(window_, point_in_parent,
551 window_component, source);
552 }
553
DeleteDragDetails()554 void WindowState::DeleteDragDetails() {
555 drag_details_.reset();
556 }
557
SetAndClearRestoreBounds()558 void WindowState::SetAndClearRestoreBounds() {
559 DCHECK(HasRestoreBounds());
560 SetBoundsInScreen(GetRestoreBoundsInScreen());
561 ClearRestoreBounds();
562 }
563
WindowState(aura::Window * window)564 WindowState::WindowState(aura::Window* window)
565 : window_(window),
566 bounds_changed_by_user_(false),
567 can_consume_system_keys_(false),
568 unminimize_to_restore_bounds_(false),
569 hide_shelf_when_fullscreen_(true),
570 autohide_shelf_when_maximized_or_fullscreen_(false),
571 cached_z_order_(ui::ZOrderLevel::kNormal),
572 ignore_property_change_(false),
573 current_state_(
574 new DefaultState(chromeos::ToWindowStateType(GetShowState()))) {
575 window_->AddObserver(this);
576 UpdateWindowPropertiesFromStateType();
577 OnPrePipStateChange(WindowStateType::kDefault);
578 }
579
GetZOrdering() const580 ui::ZOrderLevel WindowState::GetZOrdering() const {
581 return window_->GetProperty(aura::client::kZOrderingKey);
582 }
583
GetShowState() const584 ui::WindowShowState WindowState::GetShowState() const {
585 return window_->GetProperty(aura::client::kShowStateKey);
586 }
587
GetPinType() const588 WindowPinType WindowState::GetPinType() const {
589 return window_->GetProperty(kWindowPinTypeKey);
590 }
591
SetBoundsInScreen(const gfx::Rect & bounds_in_screen)592 void WindowState::SetBoundsInScreen(const gfx::Rect& bounds_in_screen) {
593 gfx::Rect bounds_in_parent = bounds_in_screen;
594 ::wm::ConvertRectFromScreen(window_->parent(), &bounds_in_parent);
595 window_->SetBounds(bounds_in_parent);
596 }
597
AdjustSnappedBounds(gfx::Rect * bounds)598 void WindowState::AdjustSnappedBounds(gfx::Rect* bounds) {
599 if (is_dragged() || !IsSnapped())
600 return;
601 gfx::Rect maximized_bounds =
602 screen_util::GetMaximizedWindowBoundsInParent(window_);
603 if (snapped_width_ratio_) {
604 bounds->set_width(
605 static_cast<int>(*snapped_width_ratio_ * maximized_bounds.width()));
606 }
607 if (GetStateType() == WindowStateType::kLeftSnapped)
608 bounds->set_x(maximized_bounds.x());
609 else if (GetStateType() == WindowStateType::kRightSnapped)
610 bounds->set_x(maximized_bounds.right() - bounds->width());
611 bounds->set_y(maximized_bounds.y());
612 bounds->set_height(maximized_bounds.height());
613 }
614
UpdateWindowPropertiesFromStateType()615 void WindowState::UpdateWindowPropertiesFromStateType() {
616 ui::WindowShowState new_window_state =
617 ToWindowShowState(current_state_->GetType());
618 // Clear |kPreMinimizedShowStateKey| property only when the window is actually
619 // Unminimized and not in tablet mode.
620 if (new_window_state != ui::SHOW_STATE_MINIMIZED && IsMinimized() &&
621 !IsTabletModeEnabled()) {
622 window()->ClearProperty(aura::client::kPreMinimizedShowStateKey);
623 }
624 if (new_window_state != GetShowState()) {
625 base::AutoReset<bool> resetter(&ignore_property_change_, true);
626 window_->SetProperty(aura::client::kShowStateKey, new_window_state);
627 }
628
629 if (GetStateType() != window_->GetProperty(chromeos::kWindowStateTypeKey)) {
630 base::AutoReset<bool> resetter(&ignore_property_change_, true);
631 window_->SetProperty(chromeos::kWindowStateTypeKey, GetStateType());
632 }
633
634 // sync up current window show state with PinType property.
635 WindowPinType pin_type = WindowPinType::kNone;
636 if (GetStateType() == WindowStateType::kPinned)
637 pin_type = WindowPinType::kPinned;
638 else if (GetStateType() == WindowStateType::kTrustedPinned)
639 pin_type = WindowPinType::kTrustedPinned;
640 if (pin_type != GetPinType()) {
641 base::AutoReset<bool> resetter(&ignore_property_change_, true);
642 window_->SetProperty(kWindowPinTypeKey, pin_type);
643 }
644
645 if (window_->GetProperty(ash::kWindowManagerManagesOpacityKey)) {
646 const gfx::Size& size = window_->bounds().size();
647 // WindowManager manages the window opacity. Make it opaque unless
648 // the window is in normal state whose frame has rounded corners.
649 if (IsNormalStateType()) {
650 window_->SetTransparent(true);
651 window_->SetOpaqueRegionsForOcclusion({gfx::Rect(size)});
652 } else {
653 window_->SetOpaqueRegionsForOcclusion({});
654 window_->SetTransparent(false);
655 }
656 }
657 }
658
NotifyPreStateTypeChange(WindowStateType old_window_state_type)659 void WindowState::NotifyPreStateTypeChange(
660 WindowStateType old_window_state_type) {
661 for (auto& observer : observer_list_)
662 observer.OnPreWindowStateTypeChange(this, old_window_state_type);
663 OnPrePipStateChange(old_window_state_type);
664 }
665
NotifyPostStateTypeChange(WindowStateType old_window_state_type)666 void WindowState::NotifyPostStateTypeChange(
667 WindowStateType old_window_state_type) {
668 for (auto& observer : observer_list_)
669 observer.OnPostWindowStateTypeChange(this, old_window_state_type);
670 OnPostPipStateChange(old_window_state_type);
671 }
672
OnPostPipStateChange(WindowStateType old_window_state_type)673 void WindowState::OnPostPipStateChange(WindowStateType old_window_state_type) {
674 if (old_window_state_type == WindowStateType::kPip) {
675 // The animation type may be FADE_OUT_SLIDE_IN at this point, which we don't
676 // want it to be anymore if the window is not PIP anymore.
677 ::wm::SetWindowVisibilityAnimationType(
678 window_, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
679 }
680 }
681
SetBoundsDirect(const gfx::Rect & bounds)682 void WindowState::SetBoundsDirect(const gfx::Rect& bounds) {
683 gfx::Rect actual_new_bounds(bounds);
684 // Ensure we don't go smaller than our minimum bounds in "normal" window
685 // modes
686 if (window_->delegate() && !IsMaximized() && !IsFullscreen()) {
687 // Get the minimum usable size of the minimum size and the screen size.
688 gfx::Size min_size = window_->delegate()
689 ? window_->delegate()->GetMinimumSize()
690 : gfx::Size();
691 const display::Display display =
692 display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
693 min_size.SetToMin(display.work_area().size());
694
695 actual_new_bounds.set_width(
696 std::max(min_size.width(), actual_new_bounds.width()));
697 actual_new_bounds.set_height(
698 std::max(min_size.height(), actual_new_bounds.height()));
699
700 // Changing the size of the PIP window can detach it from one of the edges
701 // of the screen, which makes the snap fraction logic fail. Ensure to snap
702 // it again.
703 if (IsPip() && !is_dragged()) {
704 ::wm::ConvertRectToScreen(window_->GetRootWindow(), &actual_new_bounds);
705 actual_new_bounds = CollisionDetectionUtils::GetRestingPosition(
706 display, actual_new_bounds,
707 CollisionDetectionUtils::RelativePriority::kPictureInPicture);
708 ::wm::ConvertRectFromScreen(window_->GetRootWindow(), &actual_new_bounds);
709 }
710 }
711 BoundsSetter().SetBounds(window_, actual_new_bounds);
712 }
713
SetBoundsConstrained(const gfx::Rect & bounds)714 void WindowState::SetBoundsConstrained(const gfx::Rect& bounds) {
715 gfx::Rect work_area_in_parent =
716 screen_util::GetDisplayWorkAreaBoundsInParent(window_);
717 gfx::Rect child_bounds(bounds);
718 AdjustBoundsSmallerThan(work_area_in_parent.size(), &child_bounds);
719 SetBoundsDirect(child_bounds);
720 }
721
SetBoundsDirectAnimated(const gfx::Rect & bounds,base::TimeDelta duration,gfx::Tween::Type tween_type)722 void WindowState::SetBoundsDirectAnimated(const gfx::Rect& bounds,
723 base::TimeDelta duration,
724 gfx::Tween::Type tween_type) {
725 if (::wm::WindowAnimationsDisabled(window_)) {
726 SetBoundsDirect(bounds);
727 return;
728 }
729 ui::Layer* layer = window_->layer();
730 ui::ScopedLayerAnimationSettings slide_settings(layer->GetAnimator());
731 slide_settings.SetPreemptionStrategy(
732 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
733 slide_settings.SetTweenType(tween_type);
734 slide_settings.SetTransitionDuration(duration);
735 SetBoundsDirect(bounds);
736 }
737
SetBoundsDirectCrossFade(const gfx::Rect & new_bounds)738 void WindowState::SetBoundsDirectCrossFade(const gfx::Rect& new_bounds) {
739 // Some test results in invoking CrossFadeToBounds when window is not visible.
740 // No animation is necessary in that case, thus just change the bounds and
741 // quit.
742 if (!window_->TargetVisibility()) {
743 SetBoundsConstrained(new_bounds);
744 return;
745 }
746
747 // If the window already has a transform in place, do not use the cross fade
748 // animation, set the bounds directly instead, or animation is disabled.
749 if (!window_->layer()->GetTargetTransform().IsIdentity() ||
750 ::wm::WindowAnimationsDisabled(window_)) {
751 SetBoundsDirect(new_bounds);
752 return;
753 }
754
755 // Create fresh layers for the window and all its children to paint into.
756 // Takes ownership of the old layer and all its children, which will be
757 // cleaned up after the animation completes.
758 // Specify |set_bounds| to true here to keep the old bounds in the child
759 // windows of |window|.
760 std::unique_ptr<ui::LayerTreeOwner> old_layer_owner =
761 ::wm::RecreateLayers(window_);
762
763 // Resize the window to the new size, which will force a layout and paint.
764 SetBoundsDirect(new_bounds);
765
766 CrossFadeAnimation(window_, std::move(old_layer_owner));
767 }
768
OnPrePipStateChange(WindowStateType old_window_state_type)769 void WindowState::OnPrePipStateChange(WindowStateType old_window_state_type) {
770 auto* widget = views::Widget::GetWidgetForNativeWindow(window());
771 const bool was_pip = old_window_state_type == WindowStateType::kPip;
772 if (IsPip()) {
773 CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
774 window(), CollisionDetectionUtils::RelativePriority::kPictureInPicture);
775 // widget may not exit in some unit tests.
776 // TODO(oshima): Fix unit tests and add DCHECK.
777 if (widget) {
778 widget->widget_delegate()->SetCanActivate(false);
779 if (widget->IsActive())
780 widget->Deactivate();
781 Shell::Get()->focus_cycler()->AddWidget(widget);
782 }
783 ::wm::SetWindowVisibilityAnimationType(
784 window(), WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT);
785 // There may already be a system ui window on the initial position.
786 UpdatePipBounds();
787 if (!was_pip) {
788 window()->SetProperty(kPrePipWindowStateTypeKey, old_window_state_type);
789 }
790
791 CollectPipEnterExitMetrics(/*enter=*/true);
792
793 // PIP window shouldn't be tracked in MruWindowTracker.
794 window()->SetProperty(ash::kExcludeInMruKey, true);
795 } else if (was_pip) {
796 if (widget) {
797 widget->widget_delegate()->SetCanActivate(true);
798 Shell::Get()->focus_cycler()->RemoveWidget(widget);
799 }
800 ::wm::SetWindowVisibilityAnimationType(
801 window(), ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
802
803 CollectPipEnterExitMetrics(/*enter=*/false);
804 window()->ClearProperty(ash::kExcludeInMruKey);
805 }
806 // PIP uses the snap fraction to place the PIP window at the correct position
807 // after screen rotation, system UI area change, etc. Make sure to reset this
808 // when the window enters/exits PIP so the obsolete fraction won't be used.
809 if (IsPip() || was_pip)
810 ash::PipPositioner::ClearSnapFraction(this);
811 }
812
UpdatePipBounds()813 void WindowState::UpdatePipBounds() {
814 gfx::Rect new_bounds =
815 PipPositioner::GetPositionAfterMovementAreaChange(this);
816 ::wm::ConvertRectFromScreen(window()->GetRootWindow(), &new_bounds);
817 if (window()->bounds() != new_bounds) {
818 SetBoundsWMEvent event(new_bounds, /*animate=*/true);
819 OnWMEvent(&event);
820 }
821 }
822
CollectPipEnterExitMetrics(bool enter)823 void WindowState::CollectPipEnterExitMetrics(bool enter) {
824 const bool is_arc = window_util::IsArcWindow(window());
825 if (enter) {
826 pip_start_time_ = base::TimeTicks::Now();
827
828 ReportAshPipEvents(AshPipEvents::PIP_START);
829 ReportAshPipEvents(is_arc ? AshPipEvents::ANDROID_PIP_START
830 : AshPipEvents::CHROME_PIP_START);
831 } else {
832 ReportAshPipEvents(AshPipEvents::PIP_END);
833 ReportAshPipEvents(is_arc ? AshPipEvents::ANDROID_PIP_END
834 : AshPipEvents::CHROME_PIP_END);
835
836 if (is_arc) {
837 DCHECK(!pip_start_time_.is_null());
838 const auto session_duration = base::TimeTicks::Now() - pip_start_time_;
839 ReportAshPipAndroidPipUseTime(session_duration);
840 }
841 pip_start_time_ = base::TimeTicks();
842 }
843 }
844
845 // static
Get(aura::Window * window)846 WindowState* WindowState::Get(aura::Window* window) {
847 if (!window)
848 return nullptr;
849
850 WindowState* state = window->GetProperty(kWindowStateKey);
851 if (state)
852 return state;
853
854 if (window->type() == aura::client::WINDOW_TYPE_CONTROL)
855 return nullptr;
856
857 DCHECK(window->parent());
858
859 if (!IsToplevelContainer(window->parent()))
860 return nullptr;
861
862 state = new WindowState(window);
863 window->SetProperty(kWindowStateKey, state);
864 return state;
865 }
866
867 // static
Get(const aura::Window * window)868 const WindowState* WindowState::Get(const aura::Window* window) {
869 return Get(const_cast<aura::Window*>(window));
870 }
871
872 // static
ForActiveWindow()873 WindowState* WindowState::ForActiveWindow() {
874 aura::Window* active = window_util::GetActiveWindow();
875 return active ? WindowState::Get(active) : nullptr;
876 }
877
OnWindowPropertyChanged(aura::Window * window,const void * key,intptr_t old)878 void WindowState::OnWindowPropertyChanged(aura::Window* window,
879 const void* key,
880 intptr_t old) {
881 DCHECK_EQ(window_, window);
882 if (key == aura::client::kShowStateKey) {
883 if (!ignore_property_change_) {
884 WMEvent event(WMEventTypeFromShowState(GetShowState()));
885 OnWMEvent(&event);
886 }
887 return;
888 }
889 if (key == kWindowPinTypeKey) {
890 if (!ignore_property_change_) {
891 WMEvent event(WMEventTypeFromWindowPinType(GetPinType()));
892 OnWMEvent(&event);
893 }
894 return;
895 }
896 if (key == kWindowPipTypeKey) {
897 if (window->GetProperty(kWindowPipTypeKey)) {
898 WMEvent event(WM_EVENT_PIP);
899 OnWMEvent(&event);
900 } else {
901 // Currently "restore" is not implemented.
902 NOTIMPLEMENTED();
903 }
904 return;
905 }
906 if (key == chromeos::kWindowStateTypeKey) {
907 if (!ignore_property_change_) {
908 // This change came from somewhere else. Revert it.
909 window->SetProperty(chromeos::kWindowStateTypeKey, GetStateType());
910 }
911 return;
912 }
913
914 // The shelf visibility should be updated if kHideShelfWhenFullscreenKey or
915 // kImmersiveIsActive change - these property affect the shelf behavior, and
916 // the shelf is expected to be hidden when fullscreen or immersive mode start.
917 const bool requires_shelf_visibility_update =
918 (key == kHideShelfWhenFullscreenKey &&
919 old != window->GetProperty(kHideShelfWhenFullscreenKey)) ||
920 (key == kImmersiveIsActive &&
921 old != window->GetProperty(kImmersiveIsActive));
922
923 if (requires_shelf_visibility_update && !ignore_property_change_) {
924 Shell::Get()->UpdateShelfVisibility();
925 return;
926 }
927 }
928
OnWindowAddedToRootWindow(aura::Window * window)929 void WindowState::OnWindowAddedToRootWindow(aura::Window* window) {
930 DCHECK_EQ(window_, window);
931 if (::wm::GetTransientParent(window))
932 return;
933 MoveAllTransientChildrenToNewRoot(window);
934 }
935
OnWindowDestroying(aura::Window * window)936 void WindowState::OnWindowDestroying(aura::Window* window) {
937 DCHECK_EQ(window_, window);
938
939 // If the window is destroyed during PIP, count that as exiting.
940 if (IsPip())
941 CollectPipEnterExitMetrics(/*enter=*/false);
942
943 auto* widget = views::Widget::GetWidgetForNativeWindow(window);
944 if (widget)
945 Shell::Get()->focus_cycler()->RemoveWidget(widget);
946
947 current_state_->OnWindowDestroying(this);
948 delegate_.reset();
949 }
950
OnWindowBoundsChanged(aura::Window * window,const gfx::Rect & old_bounds,const gfx::Rect & new_bounds,ui::PropertyChangeReason reason)951 void WindowState::OnWindowBoundsChanged(aura::Window* window,
952 const gfx::Rect& old_bounds,
953 const gfx::Rect& new_bounds,
954 ui::PropertyChangeReason reason) {
955 DCHECK_EQ(this->window(), window);
956 if (window_->transparent() && IsNormalStateType() &&
957 window_->GetProperty(ash::kWindowManagerManagesOpacityKey)) {
958 window_->SetOpaqueRegionsForOcclusion({gfx::Rect(new_bounds.size())});
959 }
960 }
961
962 } // namespace ash
963