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