1 // Copyright 2017 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/splitview/split_view_controller.h"
6 
7 #include <cmath>
8 #include <memory>
9 
10 #include "ash/accessibility/accessibility_controller_impl.h"
11 #include "ash/display/screen_orientation_controller.h"
12 #include "ash/public/cpp/ash_features.h"
13 #include "ash/public/cpp/metrics_util.h"
14 #include "ash/public/cpp/presentation_time_recorder.h"
15 #include "ash/public/cpp/window_properties.h"
16 #include "ash/root_window_controller.h"
17 #include "ash/root_window_settings.h"
18 #include "ash/scoped_animation_disabler.h"
19 #include "ash/screen_util.h"
20 #include "ash/session/session_controller_impl.h"
21 #include "ash/shell.h"
22 #include "ash/style/default_color_constants.h"
23 #include "ash/style/default_colors.h"
24 #include "ash/wm/desks/desks_controller.h"
25 #include "ash/wm/mru_window_tracker.h"
26 #include "ash/wm/overview/overview_controller.h"
27 #include "ash/wm/overview/overview_grid.h"
28 #include "ash/wm/overview/overview_item.h"
29 #include "ash/wm/overview/overview_utils.h"
30 #include "ash/wm/splitview/split_view_constants.h"
31 #include "ash/wm/splitview/split_view_divider.h"
32 #include "ash/wm/splitview/split_view_observer.h"
33 #include "ash/wm/splitview/split_view_utils.h"
34 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
35 #include "ash/wm/tablet_mode/tablet_mode_window_state.h"
36 #include "ash/wm/window_properties.h"
37 #include "ash/wm/window_resizer.h"
38 #include "ash/wm/window_state.h"
39 #include "ash/wm/window_transient_descendant_iterator.h"
40 #include "ash/wm/window_util.h"
41 #include "ash/wm/wm_event.h"
42 #include "base/containers/flat_set.h"
43 #include "base/metrics/histogram_macros.h"
44 #include "base/metrics/user_metrics.h"
45 #include "base/numerics/ranges.h"
46 #include "base/optional.h"
47 #include "base/stl_util.h"
48 #include "base/system/sys_info.h"
49 #include "ui/aura/client/aura_constants.h"
50 #include "ui/aura/window.h"
51 #include "ui/aura/window_delegate.h"
52 #include "ui/base/class_property.h"
53 #include "ui/base/hit_test.h"
54 #include "ui/compositor/layer.h"
55 #include "ui/compositor/throughput_tracker.h"
56 #include "ui/gfx/animation/slide_animation.h"
57 #include "ui/gfx/animation/tween.h"
58 #include "ui/gfx/transform_util.h"
59 #include "ui/views/animation/compositor_animation_runner.h"
60 #include "ui/views/widget/widget.h"
61 #include "ui/wm/core/coordinate_conversion.h"
62 #include "ui/wm/core/shadow_controller.h"
63 #include "ui/wm/core/window_util.h"
64 #include "ui/wm/public/activation_change_observer.h"
65 #include "ui/wm/public/activation_client.h"
66 
67 namespace ash {
68 
69 namespace {
70 
71 using ::chromeos::WindowStateType;
72 
73 // Three fixed position ratios of the divider, which means the divider can
74 // always be moved to these three positions.
75 constexpr float kFixedPositionRatios[] = {0.f, 0.5f, 1.0f};
76 
77 // Two optional position ratios of the divider. Whether the divider can be moved
78 // to these two positions depends on the minimum size of the snapped windows.
79 constexpr float kOneThirdPositionRatio = 0.33f;
80 constexpr float kTwoThirdPositionRatio = 0.67f;
81 
82 // The black scrim starts to fade in when the divider is moved past the two
83 // optional positions (kOneThirdPositionRatio, kTwoThirdPositionRatio) and
84 // reaches to its maximum opacity (kBlackScrimOpacity) after moving
85 // kBlackScrimFadeInRatio of the screen width. See https://crbug.com/827730 for
86 // details.
87 constexpr float kBlackScrimFadeInRatio = 0.1f;
88 constexpr float kBlackScrimOpacity = 0.4f;
89 
90 // Records the animation smoothness when the divider is released during a resize
91 // and animated to a fixed position ratio.
92 constexpr char kDividerAnimationSmoothness[] =
93     "Ash.SplitViewResize.AnimationSmoothness.DividerAnimation";
94 
95 // Histogram names that record presentation time of resize operation with
96 // following conditions, a) clamshell split view, empty overview grid,
97 // b) clamshell split view, nonempty overview grid, c) tablet split view, one
98 // snapped window, empty overview grid, d) tablet split view, two snapped
99 // windows, e) tablet split view, one snapped window, nonempty overview grid.
100 constexpr char kClamshellSplitViewResizeSingleHistogram[] =
101     "Ash.SplitViewResize.PresentationTime.ClamshellMode.SingleWindow";
102 constexpr char kClamshellSplitViewResizeWithOverviewHistogram[] =
103     "Ash.SplitViewResize.PresentationTime.ClamshellMode.WithOverview";
104 constexpr char kTabletSplitViewResizeSingleHistogram[] =
105     "Ash.SplitViewResize.PresentationTime.TabletMode.SingleWindow";
106 constexpr char kTabletSplitViewResizeMultiHistogram[] =
107     "Ash.SplitViewResize.PresentationTime.TabletMode.MultiWindow";
108 constexpr char kTabletSplitViewResizeWithOverviewHistogram[] =
109     "Ash.SplitViewResize.PresentationTime.TabletMode.WithOverview";
110 
111 constexpr char kClamshellSplitViewResizeSingleMaxLatencyHistogram[] =
112     "Ash.SplitViewResize.PresentationTime.MaxLatency.ClamshellMode."
113     "SingleWindow";
114 constexpr char kClamshellSplitViewResizeWithOverviewMaxLatencyHistogram[] =
115     "Ash.SplitViewResize.PresentationTime.MaxLatency.ClamshellMode."
116     "WithOverview";
117 constexpr char kTabletSplitViewResizeSingleMaxLatencyHistogram[] =
118     "Ash.SplitViewResize.PresentationTime.MaxLatency.TabletMode.SingleWindow";
119 constexpr char kTabletSplitViewResizeMultiMaxLatencyHistogram[] =
120     "Ash.SplitViewResize.PresentationTime.MaxLatency.TabletMode.MultiWindow";
121 constexpr char kTabletSplitViewResizeWithOverviewMaxLatencyHistogram[] =
122     "Ash.SplitViewResize.PresentationTime.MaxLatency.TabletMode.WithOverview";
123 
124 // The time when the number of roots in split view changes from one to two. Used
125 // for the purpose of metric collection.
126 base::Time g_multi_display_split_view_start_time;
127 
IsExactlyOneRootInSplitView()128 bool IsExactlyOneRootInSplitView() {
129   const aura::Window::Windows all_root_windows = Shell::GetAllRootWindows();
130   return 1 ==
131          std::count_if(
132              all_root_windows.begin(), all_root_windows.end(),
133              [](aura::Window* root_window) {
134                return SplitViewController::Get(root_window)->InSplitViewMode();
135              });
136 }
137 
GetBoundedPosition(const gfx::Point & location_in_screen,const gfx::Rect & bounds_in_screen)138 gfx::Point GetBoundedPosition(const gfx::Point& location_in_screen,
139                               const gfx::Rect& bounds_in_screen) {
140   return gfx::Point(
141       base::ClampToRange(location_in_screen.x(), bounds_in_screen.x(),
142                          bounds_in_screen.right() - 1),
143       base::ClampToRange(location_in_screen.y(), bounds_in_screen.y(),
144                          bounds_in_screen.bottom() - 1));
145 }
146 
GetStateTypeFromSnapPosition(SplitViewController::SnapPosition snap_position)147 WindowStateType GetStateTypeFromSnapPosition(
148     SplitViewController::SnapPosition snap_position) {
149   DCHECK(snap_position != SplitViewController::NONE);
150   if (snap_position == SplitViewController::LEFT)
151     return WindowStateType::kLeftSnapped;
152   if (snap_position == SplitViewController::RIGHT)
153     return WindowStateType::kRightSnapped;
154   NOTREACHED();
155   return WindowStateType::kDefault;
156 }
157 
158 // Returns the minimum size of the window according to the screen orientation.
GetMinimumWindowSize(aura::Window * window,bool horizontal)159 int GetMinimumWindowSize(aura::Window* window, bool horizontal) {
160   int minimum_width = 0;
161   if (window && window->delegate()) {
162     gfx::Size minimum_size = window->delegate()->GetMinimumSize();
163     minimum_width = horizontal ? minimum_size.width() : minimum_size.height();
164   }
165   return minimum_width;
166 }
167 
168 // Returns true if |window| is currently snapped.
IsSnapped(aura::Window * window)169 bool IsSnapped(aura::Window* window) {
170   if (!window)
171     return false;
172   return WindowState::Get(window)->IsSnapped();
173 }
174 
175 // Returns the overview session if overview mode is active, otherwise returns
176 // nullptr.
GetOverviewSession()177 OverviewSession* GetOverviewSession() {
178   return Shell::Get()->overview_controller()->InOverviewSession()
179              ? Shell::Get()->overview_controller()->overview_session()
180              : nullptr;
181 }
182 
RemoveSnappingWindowFromOverviewIfApplicable(OverviewSession * overview_session,aura::Window * window)183 void RemoveSnappingWindowFromOverviewIfApplicable(
184     OverviewSession* overview_session,
185     aura::Window* window) {
186   if (!overview_session)
187     return;
188   OverviewItem* item = overview_session->GetOverviewItemForWindow(window);
189   if (!item)
190     return;
191   // Remove it from overview. The transform will be reset later after the window
192   // is snapped. Note the remaining windows in overview don't need to be
193   // repositioned in this case as they have been positioned to the right place
194   // during dragging.
195   item->RestoreWindow(/*reset_transform=*/false);
196   overview_session->RemoveItem(item);
197 }
198 
199 }  // namespace
200 
201 // The window observer that observes the current tab-dragged window. When it's
202 // created, it observes the dragged window, and there are two possible results
203 // after the user finishes dragging: 1) the dragged window stays a new window
204 // and SplitViewController needs to decide where to put the window; 2) the
205 // dragged window's tabs are attached into another browser window and thus is
206 // destroyed.
207 class SplitViewController::TabDraggedWindowObserver
208     : public aura::WindowObserver {
209  public:
TabDraggedWindowObserver(SplitViewController * split_view_controller,aura::Window * dragged_window,SplitViewController::SnapPosition desired_snap_position,const gfx::Point & last_location_in_screen)210   TabDraggedWindowObserver(
211       SplitViewController* split_view_controller,
212       aura::Window* dragged_window,
213       SplitViewController::SnapPosition desired_snap_position,
214       const gfx::Point& last_location_in_screen)
215       : split_view_controller_(split_view_controller),
216         dragged_window_(dragged_window),
217         desired_snap_position_(desired_snap_position),
218         last_location_in_screen_(last_location_in_screen) {
219     DCHECK(window_util::IsDraggingTabs(dragged_window_));
220     dragged_window_->AddObserver(this);
221   }
222 
~TabDraggedWindowObserver()223   ~TabDraggedWindowObserver() override {
224     if (dragged_window_)
225       dragged_window_->RemoveObserver(this);
226   }
227 
228   // aura::WindowObserver:
OnWindowDestroying(aura::Window * window)229   void OnWindowDestroying(aura::Window* window) override {
230     // At this point we know the newly created dragged window is going to be
231     // destroyed due to all of its tabs are attaching into another window.
232     EndTabDragging(window, /*is_being_destroyed=*/true);
233   }
234 
OnWindowPropertyChanged(aura::Window * window,const void * key,intptr_t old)235   void OnWindowPropertyChanged(aura::Window* window,
236                                const void* key,
237                                intptr_t old) override {
238     DCHECK_EQ(window, dragged_window_);
239     if (key == kIsDraggingTabsKey && !window_util::IsDraggingTabs(window)) {
240       // At this point we know the newly created dragged window just finished
241       // dragging.
242       EndTabDragging(window, /*is_being_destroyed=*/false);
243     }
244   }
245 
246  private:
247   // Called after the tab dragging is ended, the dragged window is either
248   // destroyed because of merging into another window, or stays as a separate
249   // window.
EndTabDragging(aura::Window * window,bool is_being_destroyed)250   void EndTabDragging(aura::Window* window, bool is_being_destroyed) {
251     dragged_window_->RemoveObserver(this);
252     dragged_window_ = nullptr;
253     split_view_controller_->EndWindowDragImpl(window, is_being_destroyed,
254                                               desired_snap_position_,
255                                               last_location_in_screen_);
256 
257     // Update the source window's bounds if applicable.
258     UpdateSourceWindowBoundsAfterDragEnds(window);
259   }
260 
261   // The source window might have been scaled down during dragging, we should
262   // update its bounds to ensure it has the right bounds after the drag ends.
UpdateSourceWindowBoundsAfterDragEnds(aura::Window * window)263   void UpdateSourceWindowBoundsAfterDragEnds(aura::Window* window) {
264     aura::Window* source_window =
265         window->GetProperty(kTabDraggingSourceWindowKey);
266     if (source_window) {
267       TabletModeWindowState::UpdateWindowPosition(
268           WindowState::Get(source_window), /*animate=*/true);
269     }
270   }
271 
272   SplitViewController* split_view_controller_;
273   aura::Window* dragged_window_;
274   SplitViewController::SnapPosition desired_snap_position_;
275   gfx::Point last_location_in_screen_;
276 
277   DISALLOW_COPY_AND_ASSIGN(TabDraggedWindowObserver);
278 };
279 
280 // Animates the divider to its closest fixed position.
281 // SplitViewController::is_resizing_ is assumed to be already set to false
282 // before this animation starts, but some resizing logic is delayed until this
283 // animation ends.
284 class SplitViewController::DividerSnapAnimation
285     : public gfx::SlideAnimation,
286       public gfx::AnimationDelegate {
287  public:
DividerSnapAnimation(SplitViewController * split_view_controller,int starting_position,int ending_position)288   DividerSnapAnimation(SplitViewController* split_view_controller,
289                        int starting_position,
290                        int ending_position)
291       : gfx::SlideAnimation(this),
292         split_view_controller_(split_view_controller),
293         starting_position_(starting_position),
294         ending_position_(ending_position) {
295     // Before you change this value, read the comment on kIsWindowMovedTimeoutMs
296     // in tablet_mode_window_drag_delegate.cc.
297     SetSlideDuration(base::TimeDelta::FromMilliseconds(300));
298     SetTweenType(gfx::Tween::EASE_IN);
299 
300     aura::Window* window = split_view_controller->left_window()
301                                ? split_view_controller->left_window()
302                                : split_view_controller->right_window();
303     DCHECK(window);
304 
305     // |widget| may be null in tests. It will use the default animation
306     // container in this case.
307     views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
308     if (!widget)
309       return;
310 
311     gfx::AnimationContainer* container = new gfx::AnimationContainer();
312     container->SetAnimationRunner(
313         std::make_unique<views::CompositorAnimationRunner>(widget));
314     SetContainer(container);
315 
316     tracker_.emplace(widget->GetCompositor()->RequestNewThroughputTracker());
317     tracker_->Start(
318         metrics_util::ForSmoothness(base::BindRepeating([](int smoothness) {
319           UMA_HISTOGRAM_PERCENTAGE(kDividerAnimationSmoothness, smoothness);
320         })));
321   }
322   DividerSnapAnimation(const DividerSnapAnimation&) = delete;
323   DividerSnapAnimation& operator=(const DividerSnapAnimation&) = delete;
324   ~DividerSnapAnimation() override = default;
325 
ending_position() const326   int ending_position() const { return ending_position_; }
327 
328  private:
329   // gfx::AnimationDelegate:
AnimationEnded(const gfx::Animation * animation)330   void AnimationEnded(const gfx::Animation* animation) override {
331     DCHECK(split_view_controller_->InSplitViewMode());
332     DCHECK(!split_view_controller_->is_resizing_);
333     DCHECK_EQ(ending_position_, split_view_controller_->divider_position_);
334 
335     split_view_controller_->EndResizeImpl();
336     split_view_controller_->EndTabletSplitViewAfterResizingIfAppropriate();
337 
338     if (tracker_)
339       tracker_->Stop();
340   }
341 
AnimationProgressed(const gfx::Animation * animation)342   void AnimationProgressed(const gfx::Animation* animation) override {
343     DCHECK(split_view_controller_->InSplitViewMode());
344     DCHECK(!split_view_controller_->is_resizing_);
345 
346     split_view_controller_->divider_position_ =
347         CurrentValueBetween(starting_position_, ending_position_);
348     split_view_controller_->NotifyDividerPositionChanged();
349     split_view_controller_->UpdateSnappedWindowsAndDividerBounds();
350     // Updating the window may stop animation.
351     if (is_animating())
352       split_view_controller_->SetWindowsTransformDuringResizing();
353   }
354 
AnimationCanceled(const gfx::Animation * animation)355   void AnimationCanceled(const gfx::Animation* animation) override {
356     if (tracker_)
357       tracker_->Cancel();
358   }
359 
360   SplitViewController* split_view_controller_;
361   int starting_position_;
362   int ending_position_;
363   base::Optional<ui::ThroughputTracker> tracker_;
364 };
365 
366 // The controller that observes the window state and performs auto snapping
367 // for the window if needed. When it's created, it observes the root window
368 // and all windows in a current active desk. When 1) an observed window is
369 // activated or 2) changed to visible from minimized, this class performs
370 // auto snapping for the window if it's possible.
371 class SplitViewController::AutoSnapController
372     : public wm::ActivationChangeObserver,
373       public aura::WindowObserver {
374  public:
AutoSnapController(SplitViewController * split_view_controller)375   explicit AutoSnapController(SplitViewController* split_view_controller)
376       : split_view_controller_(split_view_controller) {
377     Shell::Get()->activation_client()->AddObserver(this);
378     AddWindow(split_view_controller->root_window());
379     for (auto* window :
380          Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
381       AddWindow(window);
382     }
383   }
384 
~AutoSnapController()385   ~AutoSnapController() override {
386     for (auto* window : observed_windows_)
387       window->RemoveObserver(this);
388     Shell::Get()->activation_client()->RemoveObserver(this);
389   }
390 
391   AutoSnapController(const AutoSnapController&) = delete;
392   AutoSnapController& operator=(const AutoSnapController&) = delete;
393 
394   // wm::ActivationChangeObserver:
OnWindowActivated(ActivationReason reason,aura::Window * gained_active,aura::Window * lost_active)395   void OnWindowActivated(ActivationReason reason,
396                          aura::Window* gained_active,
397                          aura::Window* lost_active) override {
398     if (!gained_active)
399       return;
400 
401     // If |gained_active| was activated as a side effect of a window disposition
402     // change, do nothing. For example, when a snapped window is closed, another
403     // window will be activated before OnWindowDestroying() is called. We should
404     // not try to snap another window in this case.
405     if (reason == ActivationReason::WINDOW_DISPOSITION_CHANGED)
406       return;
407 
408     AutoSnapWindowIfNeeded(gained_active);
409   }
410 
411   // aura::WindowObserver:
OnWindowVisibilityChanging(aura::Window * window,bool visible)412   void OnWindowVisibilityChanging(aura::Window* window, bool visible) override {
413     // When a minimized window's visibility changes from invisible to visible or
414     // is about to activate, it triggers an implicit un-minimizing (e.g.
415     // |WorkspaceLayoutManager::OnChildWindowVisibilityChanged| or
416     // |WorkspaceLayoutManager::OnWindowActivating|). This emits a window
417     // state change event but it is unnecessary for to-be-snapped windows
418     // because some clients (e.g. ARC app) handle a window state change
419     // asynchronously. So in the case, we here try to snap a window before
420     // other's handling to avoid the implicit un-minimizing.
421 
422     // Auto snapping is applicable for window changed to be visible.
423     if (!visible)
424       return;
425 
426     // Already un-minimized windows are not applicable for auto snapping.
427     if (!WindowState::Get(window) || !WindowState::Get(window)->IsMinimized())
428       return;
429 
430     // Visibility changes while restoring windows after dragged is transient
431     // hide & show operations so not applicable for auto snapping.
432     if (window->GetProperty(kHideDuringWindowDragging))
433       return;
434 
435     AutoSnapWindowIfNeeded(window);
436   }
437 
OnWindowAddedToRootWindow(aura::Window * window)438   void OnWindowAddedToRootWindow(aura::Window* window) override {
439     AddWindow(window);
440   }
441 
OnWindowRemovingFromRootWindow(aura::Window * window,aura::Window * new_root)442   void OnWindowRemovingFromRootWindow(aura::Window* window,
443                                       aura::Window* new_root) override {
444     RemoveWindow(window);
445   }
446 
OnWindowDestroying(aura::Window * window)447   void OnWindowDestroying(aura::Window* window) override {
448     RemoveWindow(window);
449   }
450 
451  private:
AutoSnapWindowIfNeeded(aura::Window * window)452   void AutoSnapWindowIfNeeded(aura::Window* window) {
453     DCHECK(window);
454 
455     if (window->GetRootWindow() != split_view_controller_->root_window())
456       return;
457 
458     // We perform an "auto" snapping only if split view mode is active.
459     if (!split_view_controller_->InSplitViewMode())
460       return;
461 
462     if (DesksController::Get()->AreDesksBeingModified()) {
463       // Activating a desk from its mini view will activate its most-recently
464       // used window, but this should not result in snapping and ending overview
465       // mode now. Overview will be ended explicitly as part of the desk
466       // activation animation.
467       return;
468     }
469 
470     // Only windows that are in the MRU list and are not already in split view
471     // can be auto-snapped.
472     if (split_view_controller_->IsWindowInSplitView(window) ||
473         !base::Contains(
474             Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk),
475             window)) {
476       return;
477     }
478 
479     // We do not auto snap windows in clamshell splitview mode if a new window
480     // is activated when clamshell splitview mode is active. In this case we'll
481     // just end overview mode which will then end splitview mode.
482     // TODO(xdai): Handle this logic in OverivewSession::OnWindowActivating().
483     if (split_view_controller_->InClamshellSplitViewMode()) {
484       Shell::Get()->overview_controller()->EndOverview();
485       return;
486     }
487 
488     DCHECK(split_view_controller_->InTabletSplitViewMode());
489 
490     // Do not snap the window if the activation change is caused by dragging a
491     // window, or by dragging a tab. Note the two values
492     // WindowState::is_dragged() and IsDraggingTabs() might not be exactly the
493     // same under certain circumstance, e.g., when a tab is dragged out from a
494     // browser window, a new browser window will be created for the dragged tab
495     // and then be activated, and at that time, IsDraggingTabs() is true, but
496     // WindowState::is_dragged() is still false. And later after the window drag
497     // starts, WindowState::is_dragged() will then be true.
498     if (WindowState::Get(window)->is_dragged() ||
499         window_util::IsDraggingTabs(window)) {
500       return;
501     }
502 
503     // If the divider is animating, then |window| cannot be snapped (and is
504     // not already snapped either, because then we would have bailed out by
505     // now). Then if |window| is user-positionable, we should end split view
506     // mode, but the cannot snap toast would be inappropriate because the user
507     // still might be able to snap |window|.
508     if (split_view_controller_->IsDividerAnimating()) {
509       if (WindowState::Get(window)->IsUserPositionable())
510         split_view_controller_->EndSplitView(
511             EndReason::kUnsnappableWindowActivated);
512       return;
513     }
514 
515     // If it's a user positionable window but can't be snapped, end split view
516     // mode and show the cannot snap toast.
517     if (!split_view_controller_->CanSnapWindow(window)) {
518       if (WindowState::Get(window)->IsUserPositionable()) {
519         split_view_controller_->EndSplitView(
520             EndReason::kUnsnappableWindowActivated);
521         ShowAppCannotSnapToast();
522       }
523       return;
524     }
525 
526     // Snap the window on the non-default side of the screen if split view mode
527     // is active.
528     split_view_controller_->SnapWindow(
529         window, (split_view_controller_->default_snap_position() == LEFT)
530                     ? RIGHT
531                     : LEFT);
532   }
533 
AddWindow(aura::Window * window)534   void AddWindow(aura::Window* window) {
535     if (split_view_controller_->root_window() != window->GetRootWindow())
536       return;
537 
538     if (!window->HasObserver(this))
539       window->AddObserver(this);
540     observed_windows_.insert(window);
541   }
542 
RemoveWindow(aura::Window * window)543   void RemoveWindow(aura::Window* window) {
544     window->RemoveObserver(this);
545     observed_windows_.erase(window);
546   }
547 
548   SplitViewController* split_view_controller_;
549 
550   // Tracks observed windows.
551   base::flat_set<aura::Window*> observed_windows_;
552 };
553 
554 // static
Get(const aura::Window * window)555 SplitViewController* SplitViewController::Get(const aura::Window* window) {
556   DCHECK(window);
557   DCHECK(window->GetRootWindow());
558   DCHECK(RootWindowController::ForWindow(window));
559   DCHECK(RootWindowController::ForWindow(Shell::GetPrimaryRootWindow()));
560   return RootWindowController::ForWindow(
561              AreMultiDisplayOverviewAndSplitViewEnabled()
562                  ? window
563                  : Shell::GetPrimaryRootWindow())
564       ->split_view_controller();
565 }
566 
567 // static
IsLayoutHorizontal()568 bool SplitViewController::IsLayoutHorizontal() {
569   TabletModeController* tablet_mode_controller =
570       Shell::Get()->tablet_mode_controller();
571   return !tablet_mode_controller || !tablet_mode_controller->InTabletMode() ||
572          IsCurrentScreenOrientationLandscape();
573 }
574 
575 // static
IsLayoutRightSideUp()576 bool SplitViewController::IsLayoutRightSideUp() {
577   TabletModeController* tablet_mode_controller =
578       Shell::Get()->tablet_mode_controller();
579   return !tablet_mode_controller || !tablet_mode_controller->InTabletMode() ||
580          IsCurrentScreenOrientationPrimary();
581 }
582 
583 // static
IsPhysicalLeftOrTop(SnapPosition position)584 bool SplitViewController::IsPhysicalLeftOrTop(SnapPosition position) {
585   DCHECK_NE(SplitViewController::NONE, position);
586   return position == (IsLayoutRightSideUp() ? LEFT : RIGHT);
587 }
588 
SplitViewController(aura::Window * root_window)589 SplitViewController::SplitViewController(aura::Window* root_window)
590     : root_window_(root_window) {
591   Shell::Get()->accessibility_controller()->AddObserver(this);
592   display::Screen::GetScreen()->AddObserver(this);
593   Shell::Get()->tablet_mode_controller()->AddObserver(this);
594   if (IsClamshellSplitViewModeEnabled()) {
595     split_view_type_ = Shell::Get()->tablet_mode_controller()->InTabletMode()
596                            ? SplitViewType::kTabletType
597                            : SplitViewType::kClamshellType;
598   }
599 }
600 
~SplitViewController()601 SplitViewController::~SplitViewController() {
602   if (Shell::Get()->tablet_mode_controller())
603     Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
604   display::Screen::GetScreen()->RemoveObserver(this);
605   if (Shell::Get()->accessibility_controller())
606     Shell::Get()->accessibility_controller()->RemoveObserver(this);
607   EndSplitView();
608 }
609 
InSplitViewMode() const610 bool SplitViewController::InSplitViewMode() const {
611   return state_ != State::kNoSnap;
612 }
613 
InClamshellSplitViewMode() const614 bool SplitViewController::InClamshellSplitViewMode() const {
615   return InSplitViewMode() && split_view_type_ == SplitViewType::kClamshellType;
616 }
617 
InTabletSplitViewMode() const618 bool SplitViewController::InTabletSplitViewMode() const {
619   return InSplitViewMode() && split_view_type_ == SplitViewType::kTabletType;
620 }
621 
CanSnapWindow(aura::Window * window) const622 bool SplitViewController::CanSnapWindow(aura::Window* window) const {
623   return ShouldAllowSplitView() && wm::CanActivateWindow(window) &&
624          WindowState::Get(window)->CanSnap() &&
625          GetMinimumWindowSize(window, IsLayoutHorizontal()) <=
626              GetDividerEndPosition() / 2 - kSplitviewDividerShortSideLength / 2;
627 }
628 
SnapWindow(aura::Window * window,SnapPosition snap_position,bool use_divider_spawn_animation)629 void SplitViewController::SnapWindow(aura::Window* window,
630                                      SnapPosition snap_position,
631                                      bool use_divider_spawn_animation) {
632   DCHECK(window && CanSnapWindow(window));
633   DCHECK_NE(snap_position, NONE);
634   DCHECK(!is_resizing_);
635   DCHECK(!IsDividerAnimating());
636 
637   // Save the transformed bounds in preparation for the snapping animation.
638   UpdateSnappingWindowTransformedBounds(window);
639 
640   OverviewSession* overview_session = GetOverviewSession();
641   // We can straightforwardly remove |window| from overview and then move it to
642   // |root_window_|, whereas if we move |window| to |root_window_| first, we
643   // will run into problems because |window| will be on the wrong overview grid.
644   RemoveSnappingWindowFromOverviewIfApplicable(overview_session, window);
645   if (root_window_ != window->GetRootWindow()) {
646     window_util::MoveWindowToDisplay(window,
647                                      display::Screen::GetScreen()
648                                          ->GetDisplayNearestWindow(root_window_)
649                                          .id());
650   }
651 
652   bool do_divider_spawn_animation = false;
653   if (state_ == State::kNoSnap) {
654     // Add observers when the split view mode starts.
655     Shell::Get()->AddShellObserver(this);
656     Shell::Get()->overview_controller()->AddObserver(this);
657 
658     auto_snap_controller_ = std::make_unique<AutoSnapController>(this);
659 
660     // If there is pre-set |divider_position_|, use it. It can happen during
661     // tablet <-> clamshell transition or multi-user transition.
662     divider_position_ = (divider_position_ < 0) ? GetDefaultDividerPosition()
663                                                 : divider_position_;
664     default_snap_position_ = snap_position;
665 
666     // There is no divider bar in clamshell splitview mode.
667     if (split_view_type_ == SplitViewType::kTabletType) {
668       split_view_divider_ = std::make_unique<SplitViewDivider>(this);
669       // The divider spawn animation adds a finishing touch to the |window|
670       // animation that generally accommodates snapping by dragging, but if
671       // |window| is currently minimized then it will undergo the unminimizing
672       // animation instead. Therefore skip the divider spawn animation if
673       // |window| is minimized.
674       if (use_divider_spawn_animation &&
675           !WindowState::Get(window)->IsMinimized()) {
676         // For the divider spawn animation, at the end of the delay, the divider
677         // shall be visually aligned with an edge of |window|. This effect will
678         // be more easily achieved after |window| has been snapped and the
679         // corresponding transform animation has begun. So for now, just set a
680         // flag to indicate that the divider spawn animation should be done.
681         do_divider_spawn_animation = true;
682       }
683     }
684 
685     splitview_start_time_ = base::Time::Now();
686     // We are about to enter split view on |root_window_|. If split view is
687     // already active on exactly one root, then |root_window_| will be the
688     // second root, and so multi-display split view begins now.
689     if (IsExactlyOneRootInSplitView()) {
690       base::RecordAction(
691           base::UserMetricsAction("SplitView_MultiDisplaySplitView"));
692       g_multi_display_split_view_start_time = splitview_start_time_;
693     }
694   }
695 
696   aura::Window* previous_snapped_window = nullptr;
697   if (snap_position == LEFT) {
698     if (left_window_ != window) {
699       previous_snapped_window = left_window_;
700       StopObserving(LEFT);
701       left_window_ = window;
702     }
703     if (right_window_ == window) {
704       right_window_ = nullptr;
705       default_snap_position_ = LEFT;
706     }
707   } else if (snap_position == RIGHT) {
708     if (right_window_ != window) {
709       previous_snapped_window = right_window_;
710       StopObserving(RIGHT);
711       right_window_ = window;
712     }
713     if (left_window_ == window) {
714       left_window_ = nullptr;
715       default_snap_position_ = RIGHT;
716     }
717   }
718   StartObserving(window);
719 
720   // Insert the previous snapped window to overview if overview is active.
721   DCHECK_EQ(overview_session, GetOverviewSession());
722   if (previous_snapped_window && overview_session) {
723     InsertWindowToOverview(previous_snapped_window);
724     // Ensure that the close icon will fade in. This part is redundant for
725     // dragging from overview, but necessary for dragging from the top. For
726     // dragging from overview, |OverviewItem::OnSelectorItemDragEnded| will be
727     // called on all overview items including the |previous_snapped_window|
728     // item anyway, whereas for dragging from the top,
729     // |OverviewItem::OnSelectorItemDragEnded| already was called on all
730     // overview items and |previous_snapped_window| was not yet among them.
731     overview_session->GetOverviewItemForWindow(previous_snapped_window)
732         ->OnSelectorItemDragEnded(/*snap=*/true);
733   }
734 
735   if (split_view_type_ == SplitViewType::kTabletType) {
736     divider_position_ = GetClosestFixedDividerPosition();
737     UpdateSnappedWindowsAndDividerBounds();
738   }
739 
740   // Disable the bounds change animation for a to-be-snapped window if the
741   // window has an un-identity transform. We'll do transform animation for the
742   // window in OnWindowSnapped() function.
743   std::unique_ptr<ScopedAnimationDisabler> animation_disabler;
744   auto iter = snapping_window_transformed_bounds_map_.find(window);
745   if (iter != snapping_window_transformed_bounds_map_.end())
746     animation_disabler = std::make_unique<ScopedAnimationDisabler>(window);
747 
748   if (WindowState::Get(window)->GetStateType() ==
749       GetStateTypeFromSnapPosition(snap_position)) {
750     // Update its snapped bounds as its bounds may not be the expected snapped
751     // bounds here.
752     const WMEvent event((snap_position == LEFT) ? WM_EVENT_SNAP_LEFT
753                                                 : WM_EVENT_SNAP_RIGHT);
754     WindowState::Get(window)->OnWMEvent(&event);
755 
756     OnWindowSnapped(window);
757   } else {
758     // Otherwise, try to snap it first. The split view state will be updated
759     // after the window is snapped.
760     const WMEvent event((snap_position == LEFT) ? WM_EVENT_SNAP_LEFT
761                                                 : WM_EVENT_SNAP_RIGHT);
762     WindowState::Get(window)->OnWMEvent(&event);
763   }
764 
765   if (do_divider_spawn_animation) {
766     DCHECK(window->layer()->GetAnimator()->GetTargetTransform().IsIdentity());
767 
768     const gfx::Rect bounds =
769         GetSnappedWindowBoundsInScreen(snap_position, window);
770     // Get one of the two corners of |window| that meet the divider.
771     gfx::Point p = IsPhysicalLeftOrTop(snap_position) ? bounds.bottom_right()
772                                                       : bounds.origin();
773     // Apply the transform that |window| will undergo when the divider spawns.
774     static const double value = gfx::Tween::CalculateValue(
775         gfx::Tween::FAST_OUT_SLOW_IN,
776         kSplitviewDividerSpawnDelay / kSplitviewWindowTransformDuration);
777     gfx::TransformAboutPivot(bounds.origin(),
778                              gfx::Tween::TransformValueBetween(
779                                  value, window->transform(), gfx::Transform()))
780         .TransformPoint(&p);
781     // Use a coordinate of the transformed |window| corner for spawn_position.
782     split_view_divider_->DoSpawningAnimation(IsLayoutHorizontal() ? p.x()
783                                                                   : p.y());
784   }
785 
786   base::RecordAction(base::UserMetricsAction("SplitView_SnapWindow"));
787 }
788 
SwapWindows()789 void SplitViewController::SwapWindows() {
790   DCHECK(InSplitViewMode());
791 
792   // Ignore |is_resizing_| because it will be true in case of double tapping
793   // (not double clicking) the divider without ever actually dragging it
794   // anywhere. Double tapping the divider triggers StartResize(), EndResize(),
795   // StartResize(), SwapWindows(), EndResize(). Double clicking the divider
796   // (possible by using the emulator or chrome://flags/#force-tablet-mode)
797   // triggers StartResize(), EndResize(), StartResize(), EndResize(),
798   // SwapWindows(). Those two sequences of function calls are what were mainly
799   // considered in writing the condition for bailing out here, to disallow
800   // swapping windows when the divider is being dragged or is animating.
801   if (IsDividerAnimating())
802     return;
803 
804   aura::Window* new_left_window = right_window_;
805   aura::Window* new_right_window = left_window_;
806   left_window_ = new_left_window;
807   right_window_ = new_right_window;
808 
809   // Update |default_snap_position_| if necessary.
810   if (!left_window_ || !right_window_)
811     default_snap_position_ = left_window_ ? LEFT : RIGHT;
812 
813   divider_position_ = GetClosestFixedDividerPosition();
814   UpdateSnappedWindowsAndDividerBounds();
815   UpdateStateAndNotifyObservers();
816 
817   base::RecordAction(
818       base::UserMetricsAction("SplitView_DoubleTapDividerSwapWindows"));
819 }
820 
821 SplitViewController::SnapPosition
GetPositionOfSnappedWindow(const aura::Window * window) const822 SplitViewController::GetPositionOfSnappedWindow(
823     const aura::Window* window) const {
824   DCHECK(IsWindowInSplitView(window));
825   return window == left_window_ ? LEFT : RIGHT;
826 }
827 
GetSnappedWindow(SnapPosition position)828 aura::Window* SplitViewController::GetSnappedWindow(SnapPosition position) {
829   DCHECK_NE(NONE, position);
830   return position == LEFT ? left_window_ : right_window_;
831 }
832 
GetDefaultSnappedWindow()833 aura::Window* SplitViewController::GetDefaultSnappedWindow() {
834   if (default_snap_position_ == LEFT)
835     return left_window_;
836   if (default_snap_position_ == RIGHT)
837     return right_window_;
838   return nullptr;
839 }
840 
GetSnappedWindowBoundsInParent(SnapPosition snap_position,aura::Window * window_for_minimum_size)841 gfx::Rect SplitViewController::GetSnappedWindowBoundsInParent(
842     SnapPosition snap_position,
843     aura::Window* window_for_minimum_size) {
844   gfx::Rect bounds =
845       GetSnappedWindowBoundsInScreen(snap_position, window_for_minimum_size);
846   ::wm::ConvertRectFromScreen(root_window_, &bounds);
847   return bounds;
848 }
849 
GetSnappedWindowBoundsInScreen(SnapPosition snap_position,aura::Window * window_for_minimum_size)850 gfx::Rect SplitViewController::GetSnappedWindowBoundsInScreen(
851     SnapPosition snap_position,
852     aura::Window* window_for_minimum_size) {
853   const gfx::Rect work_area_bounds_in_screen =
854       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
855           root_window_);
856   if (snap_position == NONE)
857     return work_area_bounds_in_screen;
858 
859   const bool horizontal = IsLayoutHorizontal();
860   const bool snap_left_or_top = IsPhysicalLeftOrTop(snap_position);
861   const bool in_tablet = Shell::Get()->tablet_mode_controller()->InTabletMode();
862   const int work_area_size = GetDividerEndPosition();
863   const int divider_position =
864       divider_position_ < 0 ? GetDefaultDividerPosition() : divider_position_;
865 
866   int window_size;
867   if (snap_left_or_top) {
868     window_size = divider_position;
869   } else {
870     window_size = work_area_size - divider_position;
871     // In tablet mode, there is a divider widget of which |divider_position|
872     // refers to the left or top, and so we should subtract the thickness.
873     if (in_tablet)
874       window_size -= kSplitviewDividerShortSideLength;
875   }
876 
877   const int minimum = GetMinimumWindowSize(window_for_minimum_size, horizontal);
878   DCHECK(window_for_minimum_size || minimum == 0);
879   if (window_size < minimum) {
880     if (in_tablet && !is_resizing_) {
881       // If |window_for_minimum_size| really gets snapped, then the divider will
882       // be adjusted to its default position. Compute |window_size| accordingly.
883       window_size = work_area_size / 2 - kSplitviewDividerShortSideLength / 2;
884       // If |work_area_size| is odd, then the default divider position is
885       // rounded down, toward the left or top, but then if |snap_left_or_top| is
886       // false, that means |window_size| should now be rounded up.
887       if (!snap_left_or_top && work_area_size % 2 == 1)
888         ++window_size;
889     } else {
890       window_size = minimum;
891     }
892   }
893 
894   // Get the parameter values for which |gfx::Rect::SetByBounds| would recreate
895   // |work_area_bounds_in_screen|.
896   int left = work_area_bounds_in_screen.x();
897   int top = work_area_bounds_in_screen.y();
898   int right = work_area_bounds_in_screen.right();
899   int bottom = work_area_bounds_in_screen.bottom();
900 
901   // Make |snapped_window_bounds_in_screen| by modifying one of the above four
902   // values: the one that represents the inner edge of the snapped bounds.
903   int& left_or_top = horizontal ? left : top;
904   int& right_or_bottom = horizontal ? right : bottom;
905   if (snap_left_or_top)
906     right_or_bottom = left_or_top + window_size;
907   else
908     left_or_top = right_or_bottom - window_size;
909 
910   gfx::Rect snapped_window_bounds_in_screen;
911   snapped_window_bounds_in_screen.SetByBounds(left, top, right, bottom);
912   return snapped_window_bounds_in_screen;
913 }
914 
GetDefaultDividerPosition() const915 int SplitViewController::GetDefaultDividerPosition() const {
916   int default_divider_position = GetDividerEndPosition() / 2;
917   if (split_view_type_ == SplitViewType::kTabletType)
918     default_divider_position -= kSplitviewDividerShortSideLength / 2;
919   return default_divider_position;
920 }
921 
IsDividerAnimating() const922 bool SplitViewController::IsDividerAnimating() const {
923   return divider_snap_animation_ && divider_snap_animation_->is_animating();
924 }
925 
StartResize(const gfx::Point & location_in_screen)926 void SplitViewController::StartResize(const gfx::Point& location_in_screen) {
927   DCHECK(InSplitViewMode());
928 
929   // |is_resizing_| may be true here, because you can start dragging the divider
930   // with a pointing device while already dragging it by touch, or vice versa.
931   // It is possible by using the emulator or chrome://flags/#force-tablet-mode.
932   // Bailing out here does not stop the user from dragging by touch and with a
933   // pointing device simultaneously; it just avoids duplicate calls to
934   // CreateDragDetails() and OnDragStarted(). We also bail out here if you try
935   // to start dragging the divider during its snap animation.
936   if (is_resizing_ || IsDividerAnimating())
937     return;
938 
939   is_resizing_ = true;
940   split_view_divider_->UpdateDividerBounds();
941   previous_event_location_ = location_in_screen;
942 
943   for (auto* window : {left_window_, right_window_}) {
944     if (window == nullptr)
945       continue;
946     WindowState* window_state = WindowState::Get(window);
947     gfx::Point location_in_parent(location_in_screen);
948     ::wm::ConvertPointFromScreen(window->parent(), &location_in_parent);
949     int window_component = GetWindowComponentForResize(window);
950     window_state->CreateDragDetails(gfx::PointF(location_in_parent),
951                                     window_component,
952                                     ::wm::WINDOW_MOVE_SOURCE_TOUCH);
953     window_state->OnDragStarted(window_component);
954   }
955 
956   base::RecordAction(base::UserMetricsAction("SplitView_ResizeWindows"));
957   if (state_ == State::kBothSnapped) {
958     presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
959         split_view_divider_->divider_widget()->GetCompositor(),
960         kTabletSplitViewResizeMultiHistogram,
961         kTabletSplitViewResizeMultiMaxLatencyHistogram);
962     return;
963   }
964   DCHECK(GetOverviewSession());
965   if (GetOverviewSession()->GetGridWithRootWindow(root_window_)->empty()) {
966     presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
967         split_view_divider_->divider_widget()->GetCompositor(),
968         kTabletSplitViewResizeSingleHistogram,
969         kTabletSplitViewResizeSingleMaxLatencyHistogram);
970   } else {
971     presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
972         split_view_divider_->divider_widget()->GetCompositor(),
973         kTabletSplitViewResizeWithOverviewHistogram,
974         kTabletSplitViewResizeWithOverviewMaxLatencyHistogram);
975   }
976 }
977 
Resize(const gfx::Point & location_in_screen)978 void SplitViewController::Resize(const gfx::Point& location_in_screen) {
979   DCHECK(InSplitViewMode());
980 
981   if (!is_resizing_)
982     return;
983   presentation_time_recorder_->RequestNext();
984   const gfx::Rect work_area_bounds =
985       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
986           root_window_);
987   gfx::Point modified_location_in_screen =
988       GetBoundedPosition(location_in_screen, work_area_bounds);
989 
990   // Update |divider_position_|.
991   UpdateDividerPosition(modified_location_in_screen);
992   NotifyDividerPositionChanged();
993 
994   // Update the black scrim layer's bounds and opacity.
995   UpdateBlackScrim(modified_location_in_screen);
996 
997   // Update the snapped window/windows and divider's position.
998   UpdateSnappedWindowsAndDividerBounds();
999 
1000   // Apply window transform if necessary.
1001   SetWindowsTransformDuringResizing();
1002 
1003   previous_event_location_ = modified_location_in_screen;
1004 }
1005 
EndResize(const gfx::Point & location_in_screen)1006 void SplitViewController::EndResize(const gfx::Point& location_in_screen) {
1007   presentation_time_recorder_.reset();
1008   DCHECK(InSplitViewMode());
1009   if (!is_resizing_)
1010     return;
1011   // TODO(xdai): Use fade out animation instead of just removing it.
1012   black_scrim_layer_.reset();
1013   is_resizing_ = false;
1014 
1015   const gfx::Rect work_area_bounds =
1016       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
1017           root_window_);
1018   gfx::Point modified_location_in_screen =
1019       GetBoundedPosition(location_in_screen, work_area_bounds);
1020   UpdateDividerPosition(modified_location_in_screen);
1021   NotifyDividerPositionChanged();
1022   // Need to update snapped windows bounds even if the split view mode may have
1023   // to exit. Otherwise it's possible for a snapped window stuck in the edge of
1024   // of the screen while overview mode is active.
1025   UpdateSnappedWindowsAndDividerBounds();
1026 
1027   const int target_divider_position = GetClosestFixedDividerPosition();
1028   if (divider_position_ == target_divider_position) {
1029     EndResizeImpl();
1030     EndTabletSplitViewAfterResizingIfAppropriate();
1031   } else {
1032     divider_snap_animation_ = std::make_unique<DividerSnapAnimation>(
1033         this, divider_position_, target_divider_position);
1034     divider_snap_animation_->Show();
1035   }
1036 }
1037 
EndSplitView(EndReason end_reason)1038 void SplitViewController::EndSplitView(EndReason end_reason) {
1039   if (!InSplitViewMode())
1040     return;
1041 
1042   end_reason_ = end_reason;
1043 
1044   // If we are currently in a resize but split view is ending, make sure to end
1045   // the resize. This can happen, for example, on the transition back to
1046   // clamshell mode or when a task is minimized during a resize. Likewise, if
1047   // split view is ending during the divider snap animation, then clean that up.
1048   const bool is_divider_animating = IsDividerAnimating();
1049   if (is_resizing_ || is_divider_animating) {
1050     is_resizing_ = false;
1051     if (is_divider_animating) {
1052       // Don't call StopAndShoveAnimatedDivider as it will call observers.
1053       divider_snap_animation_->Stop();
1054       divider_position_ = divider_snap_animation_->ending_position();
1055     }
1056     EndResizeImpl();
1057   }
1058 
1059   // There is at least one case where this line of code is needed: if the user
1060   // presses Ctrl+W while resizing a clamshell split view window.
1061   presentation_time_recorder_.reset();
1062 
1063   // Remove observers when the split view mode ends.
1064   Shell::Get()->RemoveShellObserver(this);
1065   Shell::Get()->overview_controller()->RemoveObserver(this);
1066 
1067   auto_snap_controller_.reset();
1068 
1069   StopObserving(LEFT);
1070   StopObserving(RIGHT);
1071   black_scrim_layer_.reset();
1072   default_snap_position_ = NONE;
1073   divider_position_ = -1;
1074   divider_closest_ratio_ = std::numeric_limits<float>::quiet_NaN();
1075   snapping_window_transformed_bounds_map_.clear();
1076 
1077   UpdateStateAndNotifyObservers();
1078   // Close splitview divider widget after updating state so that
1079   // OnDisplayMetricsChanged triggered by the widget closing correctly
1080   // finds out !InSplitViewMode().
1081   split_view_divider_.reset();
1082   base::RecordAction(base::UserMetricsAction("SplitView_EndSplitView"));
1083   const base::Time now = base::Time::Now();
1084   UMA_HISTOGRAM_LONG_TIMES("Ash.SplitView.TimeInSplitView",
1085                            now - splitview_start_time_);
1086   // We just ended split view on |root_window_|. If there is exactly one root
1087   // where split view is still active, then multi-display split view ends now.
1088   if (IsExactlyOneRootInSplitView()) {
1089     UMA_HISTOGRAM_LONG_TIMES("Ash.SplitView.TimeInMultiDisplaySplitView",
1090                              now - g_multi_display_split_view_start_time);
1091   }
1092 }
1093 
IsWindowInSplitView(const aura::Window * window) const1094 bool SplitViewController::IsWindowInSplitView(
1095     const aura::Window* window) const {
1096   return window && (window == left_window_ || window == right_window_);
1097 }
1098 
InitDividerPositionForTransition(int divider_position)1099 void SplitViewController::InitDividerPositionForTransition(
1100     int divider_position) {
1101   // This should only be called before the actual carry-over happens.
1102   DCHECK(!InSplitViewMode());
1103   DCHECK_EQ(divider_position_, -1);
1104   divider_position_ = divider_position;
1105 }
1106 
IsWindowInTransitionalState(const aura::Window * window) const1107 bool SplitViewController::IsWindowInTransitionalState(
1108     const aura::Window* window) const {
1109   if (!IsWindowInSplitView(window))
1110     return false;
1111   return WindowState::Get(window)->GetStateType() !=
1112          GetStateTypeFromSnapPosition(GetPositionOfSnappedWindow(window));
1113 }
1114 
OnOverviewButtonTrayLongPressed(const gfx::Point & event_location)1115 void SplitViewController::OnOverviewButtonTrayLongPressed(
1116     const gfx::Point& event_location) {
1117   // Do nothing if split view is not enabled.
1118   if (!ShouldAllowSplitView())
1119     return;
1120 
1121   // If in split view: The active snapped window becomes maximized. If overview
1122   // was seen alongside a snapped window, then overview mode ends.
1123   //
1124   // Otherwise: Enter split view iff the cycle list has at least one window, and
1125   // the first one is snappable.
1126 
1127   MruWindowTracker::WindowList mru_window_list =
1128       Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
1129   // Do nothing if there is one or less windows in the MRU list.
1130   if (mru_window_list.empty())
1131     return;
1132 
1133   auto* overview_controller = Shell::Get()->overview_controller();
1134   aura::Window* target_window = mru_window_list[0];
1135 
1136   // Exit split view mode if we are already in it.
1137   if (InSplitViewMode()) {
1138     DCHECK(IsWindowInSplitView(target_window));
1139     DCHECK(target_window);
1140     EndSplitView();
1141     overview_controller->EndOverview();
1142     MaximizeIfSnapped(target_window);
1143     wm::ActivateWindow(target_window);
1144     base::RecordAction(
1145         base::UserMetricsAction("Tablet_LongPressOverviewButtonExitSplitView"));
1146     return;
1147   }
1148 
1149   // Show a toast if the window cannot be snapped.
1150   if (!CanSnapWindow(target_window)) {
1151     ShowAppCannotSnapToast();
1152     return;
1153   }
1154 
1155   // Start overview mode if we aren't already in it.
1156   overview_controller->StartOverview(OverviewEnterExitType::kImmediateEnter);
1157 
1158   SnapWindow(target_window, SplitViewController::LEFT);
1159   wm::ActivateWindow(target_window);
1160   base::RecordAction(
1161       base::UserMetricsAction("Tablet_LongPressOverviewButtonEnterSplitView"));
1162 }
1163 
OnWindowDragStarted(aura::Window * dragged_window)1164 void SplitViewController::OnWindowDragStarted(aura::Window* dragged_window) {
1165   DCHECK(dragged_window);
1166   if (IsWindowInSplitView(dragged_window))
1167     OnSnappedWindowDetached(dragged_window, /*window_drag=*/true);
1168 
1169   // OnSnappedWindowDetached() may end split view mode.
1170   if (split_view_divider_)
1171     split_view_divider_->OnWindowDragStarted();
1172 }
1173 
OnWindowDragEnded(aura::Window * dragged_window,SnapPosition desired_snap_position,const gfx::Point & last_location_in_screen)1174 void SplitViewController::OnWindowDragEnded(
1175     aura::Window* dragged_window,
1176     SnapPosition desired_snap_position,
1177     const gfx::Point& last_location_in_screen) {
1178   if (window_util::IsDraggingTabs(dragged_window)) {
1179     dragged_window_observer_.reset(new TabDraggedWindowObserver(
1180         this, dragged_window, desired_snap_position, last_location_in_screen));
1181   } else {
1182     EndWindowDragImpl(dragged_window, /*is_being_destroyed=*/false,
1183                       desired_snap_position, last_location_in_screen);
1184   }
1185 }
1186 
OnWindowDragCanceled()1187 void SplitViewController::OnWindowDragCanceled() {
1188   if (split_view_divider_)
1189     split_view_divider_->OnWindowDragEnded();
1190 }
1191 
AddObserver(SplitViewObserver * observer)1192 void SplitViewController::AddObserver(SplitViewObserver* observer) {
1193   observers_.AddObserver(observer);
1194 }
1195 
RemoveObserver(SplitViewObserver * observer)1196 void SplitViewController::RemoveObserver(SplitViewObserver* observer) {
1197   observers_.RemoveObserver(observer);
1198 }
1199 
OnWindowPropertyChanged(aura::Window * window,const void * key,intptr_t old)1200 void SplitViewController::OnWindowPropertyChanged(aura::Window* window,
1201                                                   const void* key,
1202                                                   intptr_t old) {
1203   // If the window's resizibility property changes (must from resizable ->
1204   // unresizable), end the split view mode and also end overview mode if
1205   // overview mode is active at the moment.
1206   if (key == aura::client::kResizeBehaviorKey && !CanSnapWindow(window)) {
1207     EndSplitView();
1208     Shell::Get()->overview_controller()->EndOverview();
1209     ShowAppCannotSnapToast();
1210   }
1211 }
1212 
OnWindowBoundsChanged(aura::Window * window,const gfx::Rect & old_bounds,const gfx::Rect & new_bounds,ui::PropertyChangeReason reason)1213 void SplitViewController::OnWindowBoundsChanged(
1214     aura::Window* window,
1215     const gfx::Rect& old_bounds,
1216     const gfx::Rect& new_bounds,
1217     ui::PropertyChangeReason reason) {
1218   DCHECK_EQ(root_window_, window->GetRootWindow());
1219 
1220   if (!InClamshellSplitViewMode())
1221     return;
1222 
1223   WindowState* window_state = WindowState::Get(window);
1224   if (window_state->is_dragged()) {
1225     DCHECK_NE(WindowResizer::kBoundsChange_None,
1226               window_state->drag_details()->bounds_change);
1227     if (window_state->drag_details()->bounds_change ==
1228         WindowResizer::kBoundsChange_Repositions) {
1229       // Ending overview will also end clamshell split view.
1230       Shell::Get()->overview_controller()->EndOverview();
1231       return;
1232     }
1233     DCHECK(window_state->drag_details()->bounds_change &
1234            WindowResizer::kBoundsChange_Resizes);
1235     DCHECK(presentation_time_recorder_);
1236     presentation_time_recorder_->RequestNext();
1237   }
1238 
1239   const gfx::Rect work_area =
1240       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
1241           root_window_);
1242   divider_position_ = window == left_window_
1243                           ? new_bounds.width()
1244                           : work_area.width() - new_bounds.width();
1245   NotifyDividerPositionChanged();
1246 }
1247 
OnWindowDestroyed(aura::Window * window)1248 void SplitViewController::OnWindowDestroyed(aura::Window* window) {
1249   DCHECK(InSplitViewMode());
1250   DCHECK(IsWindowInSplitView(window));
1251   auto iter = snapping_window_transformed_bounds_map_.find(window);
1252   if (iter != snapping_window_transformed_bounds_map_.end())
1253     snapping_window_transformed_bounds_map_.erase(iter);
1254   OnSnappedWindowDetached(window, /*window_drag=*/false);
1255 }
1256 
OnResizeLoopStarted(aura::Window * window)1257 void SplitViewController::OnResizeLoopStarted(aura::Window* window) {
1258   if (!InClamshellSplitViewMode())
1259     return;
1260 
1261   // In clamshell mode, if splitview is active (which means overview is active
1262   // at the same time), only the resize that happens on the window edge that's
1263   // next to the overview grid will resize the window and overview grid at the
1264   // same time. For the resize that happens on the other part of the window,
1265   // we'll just end splitview and overview mode.
1266   if (WindowState::Get(window)->drag_details()->window_component !=
1267       GetWindowComponentForResize(window)) {
1268     // Ending overview will also end clamshell split view.
1269     Shell::Get()->overview_controller()->EndOverview();
1270     return;
1271   }
1272 
1273   DCHECK_NE(State::kBothSnapped, state_);
1274   DCHECK(GetOverviewSession());
1275   if (GetOverviewSession()->GetGridWithRootWindow(root_window_)->empty()) {
1276     presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
1277         window->layer()->GetCompositor(),
1278         kClamshellSplitViewResizeSingleHistogram,
1279         kClamshellSplitViewResizeSingleMaxLatencyHistogram);
1280   } else {
1281     presentation_time_recorder_ = CreatePresentationTimeHistogramRecorder(
1282         window->layer()->GetCompositor(),
1283         kClamshellSplitViewResizeWithOverviewHistogram,
1284         kClamshellSplitViewResizeWithOverviewMaxLatencyHistogram);
1285   }
1286 }
1287 
OnResizeLoopEnded(aura::Window * window)1288 void SplitViewController::OnResizeLoopEnded(aura::Window* window) {
1289   if (!InClamshellSplitViewMode())
1290     return;
1291 
1292   presentation_time_recorder_.reset();
1293 
1294   if (divider_position_ < GetDividerEndPosition() * kOneThirdPositionRatio ||
1295       divider_position_ > GetDividerEndPosition() * kTwoThirdPositionRatio) {
1296     // Ending overview will also end clamshell split view.
1297     Shell::Get()->overview_controller()->EndOverview();
1298     WindowState::Get(window)->Maximize();
1299   }
1300 }
1301 
OnPostWindowStateTypeChange(WindowState * window_state,WindowStateType old_type)1302 void SplitViewController::OnPostWindowStateTypeChange(
1303     WindowState* window_state,
1304     WindowStateType old_type) {
1305   DCHECK_EQ(
1306       window_state->GetDisplay().id(),
1307       display::Screen::GetScreen()->GetDisplayNearestWindow(root_window_).id());
1308 
1309   if (window_state->IsSnapped()) {
1310     OnWindowSnapped(window_state->window());
1311   } else if (window_state->IsNormalStateType() || window_state->IsMaximized() ||
1312              window_state->IsFullscreen()) {
1313     // End split view, and also overview if overview is active, in these cases:
1314     // 1. A left clamshell split view window gets unsnapped by Alt+[.
1315     // 2. A right clamshell split view window gets unsnapped by Alt+].
1316     // 3. A (clamshell or tablet) split view window gets maximized.
1317     // 4. A (clamshell or tablet) split view window becomes full screen.
1318     EndSplitView();
1319     Shell::Get()->overview_controller()->EndOverview();
1320   } else if (window_state->IsMinimized()) {
1321     OnSnappedWindowDetached(window_state->window(), /*window_drag=*/false);
1322 
1323     if (!InSplitViewMode()) {
1324       // We have different behaviors for a minimized window: in tablet splitview
1325       // mode, we'll insert the minimized window back to overview, as normally
1326       // the window is not supposed to be minmized in tablet mode. And in
1327       // clamshell splitview mode, we respect the minimization of the window
1328       // and end overview instead.
1329       if (split_view_type_ == SplitViewType::kTabletType)
1330         InsertWindowToOverview(window_state->window());
1331       else
1332         Shell::Get()->overview_controller()->EndOverview();
1333     }
1334   }
1335 }
1336 
OnPinnedStateChanged(aura::Window * pinned_window)1337 void SplitViewController::OnPinnedStateChanged(aura::Window* pinned_window) {
1338   // Disable split view for pinned windows.
1339   if (WindowState::Get(pinned_window)->IsPinned() && InSplitViewMode())
1340     EndSplitView(EndReason::kUnsnappableWindowActivated);
1341 }
1342 
OnOverviewModeStarting()1343 void SplitViewController::OnOverviewModeStarting() {
1344   DCHECK(InSplitViewMode());
1345 
1346   // If split view mode is active, reset |state_| to make it be able to select
1347   // another window from overview window grid.
1348   if (default_snap_position_ == LEFT) {
1349     StopObserving(RIGHT);
1350   } else if (default_snap_position_ == RIGHT) {
1351     StopObserving(LEFT);
1352   }
1353   UpdateStateAndNotifyObservers();
1354 }
1355 
OnOverviewModeEnding(OverviewSession * overview_session)1356 void SplitViewController::OnOverviewModeEnding(
1357     OverviewSession* overview_session) {
1358   DCHECK(InSplitViewMode());
1359 
1360   // If overview is ended because of a window getting snapped, suppress the
1361   // overview exiting animation.
1362   if (state_ == State::kBothSnapped)
1363     overview_session->SetWindowListNotAnimatedWhenExiting(root_window_);
1364 
1365   // If clamshell split view mode is active, bail out. |OnOverviewModeEnded|
1366   // will end split view. We do not end split view here, because that would mess
1367   // up histograms of overview exit animation smoothness.
1368   if (split_view_type_ == SplitViewType::kClamshellType)
1369     return;
1370 
1371   // Tablet split view mode is active. If it still only has one snapped window,
1372   // snap the first snappable window in the overview grid on the other side.
1373   if (state_ == State::kBothSnapped)
1374     return;
1375   OverviewGrid* current_grid =
1376       overview_session->GetGridWithRootWindow(root_window_);
1377   if (!current_grid || current_grid->empty())
1378     return;
1379   for (const auto& overview_item : current_grid->window_list()) {
1380     aura::Window* window = overview_item->GetWindow();
1381     if (CanSnapWindow(window) && window != GetDefaultSnappedWindow()) {
1382       // Remove the overview item before snapping because the overview session
1383       // is unavailable to retrieve outside this function after OnOverviewEnding
1384       // is notified.
1385       overview_item->RestoreWindow(/*reset_transform=*/false);
1386       overview_session->RemoveItem(overview_item.get());
1387       SnapWindow(window, (default_snap_position_ == LEFT) ? RIGHT : LEFT);
1388       // If ending overview causes a window to snap, also do not do exiting
1389       // overview animation.
1390       overview_session->SetWindowListNotAnimatedWhenExiting(root_window_);
1391       return;
1392     }
1393   }
1394 
1395   // The overview grid has at least one window, but has none that can be snapped
1396   // in split view. If overview is ending because of switching between virtual
1397   // desks, then there is no need to do anything here. Otherwise, end split view
1398   // and show the cannot snap toast.
1399   if (DesksController::Get()->AreDesksBeingModified())
1400     return;
1401   EndSplitView();
1402   ShowAppCannotSnapToast();
1403 }
1404 
OnOverviewModeEnded()1405 void SplitViewController::OnOverviewModeEnded() {
1406   DCHECK(InSplitViewMode());
1407   if (split_view_type_ == SplitViewType::kClamshellType)
1408     EndSplitView();
1409 }
1410 
OnDisplayRemoved(const display::Display & old_display)1411 void SplitViewController::OnDisplayRemoved(
1412     const display::Display& old_display) {
1413   // Display removal always triggers a window activation which ends overview,
1414   // and therefore ends clamshell split view, before |OnDisplayRemoved| is
1415   // called. In clamshell mode, |OverviewController::CanEndOverview| always
1416   // returns true, meaning that overview is guaranteed to end successfully.
1417   DCHECK(!InClamshellSplitViewMode());
1418   // If we are in tablet split view with only one snapped window, make sure we
1419   // are in overview (see https://crbug.com/1027179).
1420   if (state_ == State::kLeftSnapped || state_ == State::kRightSnapped) {
1421     Shell::Get()->overview_controller()->StartOverview(
1422         OverviewEnterExitType::kImmediateEnter);
1423   }
1424 }
1425 
OnDisplayMetricsChanged(const display::Display & display,uint32_t metrics)1426 void SplitViewController::OnDisplayMetricsChanged(
1427     const display::Display& display,
1428     uint32_t metrics) {
1429   // Avoid |ScreenAsh::GetDisplayNearestWindow|, which has a |DCHECK| that fails
1430   // if the display is being deleted. Use |GetRootWindowSettings| directly, and
1431   // if the display is being deleted, we will get |display::kInvalidDisplayId|.
1432   if (GetRootWindowSettings(root_window_)->display_id != display.id())
1433     return;
1434 
1435   // We need to update |is_previous_layout_right_side_up_| even if split view
1436   // mode is not active.
1437   const bool is_previous_layout_right_side_up =
1438       is_previous_layout_right_side_up_;
1439   is_previous_layout_right_side_up_ = IsLayoutRightSideUp();
1440 
1441   if (!InSplitViewMode())
1442     return;
1443 
1444   // If one of the snapped windows becomes unsnappable, end the split view mode
1445   // directly.
1446   if ((left_window_ && !CanSnapWindow(left_window_)) ||
1447       (right_window_ && !CanSnapWindow(right_window_))) {
1448     if (!Shell::Get()->session_controller()->IsUserSessionBlocked())
1449       EndSplitView();
1450     return;
1451   }
1452 
1453   // In clamshell split view mode, the divider position will be adjusted in
1454   // |OnWindowBoundsChanged|.
1455   if (split_view_type_ == SplitViewType::kClamshellType)
1456     return;
1457 
1458   // Before adjusting the divider position for the new display metrics, if the
1459   // divider is animating to a snap position, then stop it and shove it there.
1460   // Postpone EndTabletSplitViewAfterResizingIfAppropriate() until after the
1461   // adjustment, because the new display metrics will be used to compare the
1462   // divider position against the edges of the screen.
1463   if (IsDividerAnimating()) {
1464     StopAndShoveAnimatedDivider();
1465     EndResizeImpl();
1466   }
1467 
1468   if ((metrics & display::DisplayObserver::DISPLAY_METRIC_ROTATION) ||
1469       (metrics & display::DisplayObserver::DISPLAY_METRIC_WORK_AREA)) {
1470     // Set default |divider_closest_ratio_| to kFixedPositionRatios[1].
1471     if (std::isnan(divider_closest_ratio_))
1472       divider_closest_ratio_ = kFixedPositionRatios[1];
1473 
1474     // Reverse the position ratio if top/left window changes.
1475     if (is_previous_layout_right_side_up != IsLayoutRightSideUp())
1476       divider_closest_ratio_ = 1.f - divider_closest_ratio_;
1477     divider_position_ =
1478         static_cast<int>(divider_closest_ratio_ * GetDividerEndPosition()) -
1479         kSplitviewDividerShortSideLength / 2;
1480   }
1481 
1482   // For other display configuration changes, we only move the divider to the
1483   // closest fixed position.
1484   if (!is_resizing_)
1485     divider_position_ = GetClosestFixedDividerPosition();
1486 
1487   EndTabletSplitViewAfterResizingIfAppropriate();
1488   if (!InSplitViewMode())
1489     return;
1490 
1491   NotifyDividerPositionChanged();
1492   UpdateSnappedWindowsAndDividerBounds();
1493 }
1494 
OnTabletModeStarting()1495 void SplitViewController::OnTabletModeStarting() {
1496   split_view_type_ = SplitViewType::kTabletType;
1497 }
1498 
OnTabletModeStarted()1499 void SplitViewController::OnTabletModeStarted() {
1500   DCHECK_EQ(IsCurrentScreenOrientationPrimary(), IsLayoutRightSideUp());
1501   is_previous_layout_right_side_up_ = IsCurrentScreenOrientationPrimary();
1502   // If splitview is active when tablet mode is starting, do the clamshell mode
1503   // splitview to tablet mode splitview transition by adding the split view
1504   // divider bar and also adjust the |divider_position_| so that it's on one of
1505   // the three fixed positions.
1506   if (InSplitViewMode()) {
1507     divider_position_ = GetClosestFixedDividerPosition();
1508     split_view_divider_ = std::make_unique<SplitViewDivider>(this);
1509     UpdateSnappedWindowsAndDividerBounds();
1510     NotifyDividerPositionChanged();
1511   }
1512 }
1513 
OnTabletModeEnding()1514 void SplitViewController::OnTabletModeEnding() {
1515   if (IsClamshellSplitViewModeEnabled()) {
1516     split_view_type_ = SplitViewType::kClamshellType;
1517 
1518     // There is no divider in clamshell split view.
1519     const bool is_divider_animating = IsDividerAnimating();
1520     if (is_resizing_ || is_divider_animating) {
1521       is_resizing_ = false;
1522       if (is_divider_animating)
1523         StopAndShoveAnimatedDivider();
1524       EndResizeImpl();
1525     }
1526     split_view_divider_.reset();
1527   } else if (InSplitViewMode()) {
1528     // If clamshell splitview mode is not enabled, fall back to the old
1529     // behavior: end splitview and overivew and all windows will return to its
1530     // old window state before entering tablet mode.
1531     EndSplitView();
1532     Shell::Get()->overview_controller()->EndOverview();
1533   }
1534 }
1535 
OnTabletModeEnded()1536 void SplitViewController::OnTabletModeEnded() {
1537   DCHECK(IsLayoutRightSideUp());
1538   is_previous_layout_right_side_up_ = true;
1539 }
1540 
OnTabletControllerDestroyed()1541 void SplitViewController::OnTabletControllerDestroyed() {
1542   tablet_mode_observer_.RemoveAll();
1543 }
1544 
OnAccessibilityStatusChanged()1545 void SplitViewController::OnAccessibilityStatusChanged() {
1546   // TODO(crubg.com/853588): Exit split screen if ChromeVox is turned on until
1547   // they are compatible.
1548   if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled())
1549     EndSplitView();
1550 }
1551 
OnAccessibilityControllerShutdown()1552 void SplitViewController::OnAccessibilityControllerShutdown() {
1553   Shell::Get()->accessibility_controller()->RemoveObserver(this);
1554 }
1555 
GetPhysicalLeftOrTopWindow()1556 aura::Window* SplitViewController::GetPhysicalLeftOrTopWindow() {
1557   return IsLayoutRightSideUp() ? left_window_ : right_window_;
1558 }
1559 
GetPhysicalRightOrBottomWindow()1560 aura::Window* SplitViewController::GetPhysicalRightOrBottomWindow() {
1561   return IsLayoutRightSideUp() ? right_window_ : left_window_;
1562 }
1563 
StartObserving(aura::Window * window)1564 void SplitViewController::StartObserving(aura::Window* window) {
1565   if (window && !window->HasObserver(this)) {
1566     Shell::Get()->shadow_controller()->UpdateShadowForWindow(window);
1567     window->AddObserver(this);
1568     WindowState::Get(window)->AddObserver(this);
1569     if (split_view_divider_)
1570       split_view_divider_->AddObservedWindow(window);
1571   }
1572 }
1573 
StopObserving(SnapPosition snap_position)1574 void SplitViewController::StopObserving(SnapPosition snap_position) {
1575   aura::Window* window = GetSnappedWindow(snap_position);
1576   if (window == left_window_)
1577     left_window_ = nullptr;
1578   else
1579     right_window_ = nullptr;
1580 
1581   if (window && window->HasObserver(this)) {
1582     window->RemoveObserver(this);
1583     WindowState::Get(window)->RemoveObserver(this);
1584     if (split_view_divider_)
1585       split_view_divider_->RemoveObservedWindow(window);
1586     Shell::Get()->shadow_controller()->UpdateShadowForWindow(window);
1587 
1588     // It's possible that when we try to snap an ARC app window, while we are
1589     // waiting for its state/bounds to the expected state/bounds, another window
1590     // snap request comes in and causing the previous to-be-snapped window to
1591     // be un-observed, in this case we should restore the previous to-be-snapped
1592     // window's transform if it's unidentity.
1593     RestoreTransformIfApplicable(window);
1594   }
1595 }
1596 
UpdateStateAndNotifyObservers()1597 void SplitViewController::UpdateStateAndNotifyObservers() {
1598   State previous_state = state_;
1599   if (IsSnapped(left_window_) && IsSnapped(right_window_))
1600     state_ = State::kBothSnapped;
1601   else if (IsSnapped(left_window_))
1602     state_ = State::kLeftSnapped;
1603   else if (IsSnapped(right_window_))
1604     state_ = State::kRightSnapped;
1605   else
1606     state_ = State::kNoSnap;
1607 
1608   // We still notify observers even if |state_| doesn't change as it's possible
1609   // to snap a window to a position that already has a snapped window. However,
1610   // |previous_state| and |state_| cannot both be |State::kNoSnap|.
1611   // When |previous_state| is |State::kNoSnap|, it indicates to
1612   // observers that split view mode started. Likewise, when |state_| is
1613   // |State::kNoSnap|, it indicates to observers that split view mode
1614   // ended.
1615   DCHECK(previous_state != State::kNoSnap || state_ != State::kNoSnap);
1616   for (auto& observer : observers_)
1617     observer.OnSplitViewStateChanged(previous_state, state_);
1618 }
1619 
NotifyDividerPositionChanged()1620 void SplitViewController::NotifyDividerPositionChanged() {
1621   for (auto& observer : observers_)
1622     observer.OnSplitViewDividerPositionChanged();
1623 }
1624 
UpdateBlackScrim(const gfx::Point & location_in_screen)1625 void SplitViewController::UpdateBlackScrim(
1626     const gfx::Point& location_in_screen) {
1627   DCHECK(InSplitViewMode());
1628 
1629   if (!black_scrim_layer_) {
1630     // Create an invisible black scrim layer.
1631     black_scrim_layer_ = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
1632     black_scrim_layer_->SetColor(
1633         DeprecatedGetBackgroundColor(kSplitviewBlackScrimLayerColor));
1634     root_window_->layer()->Add(black_scrim_layer_.get());
1635     root_window_->layer()->StackAtTop(black_scrim_layer_.get());
1636   }
1637 
1638   // Decide where the black scrim should show and update its bounds.
1639   SnapPosition position = GetBlackScrimPosition(location_in_screen);
1640   if (position == NONE) {
1641     black_scrim_layer_.reset();
1642     return;
1643   }
1644   black_scrim_layer_->SetBounds(
1645       GetSnappedWindowBoundsInScreen(position, GetSnappedWindow(position)));
1646 
1647   // Update its opacity. The opacity increases as it gets closer to the edge of
1648   // the screen.
1649   const int location =
1650       IsLayoutHorizontal() ? location_in_screen.x() : location_in_screen.y();
1651   gfx::Rect work_area_bounds =
1652       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
1653           root_window_);
1654   if (!IsLayoutHorizontal())
1655     work_area_bounds.Transpose();
1656   float opacity = kBlackScrimOpacity;
1657   const float ratio = kOneThirdPositionRatio - kBlackScrimFadeInRatio;
1658   const int distance = std::min(std::abs(location - work_area_bounds.x()),
1659                                 std::abs(work_area_bounds.right() - location));
1660   if (distance > work_area_bounds.width() * ratio) {
1661     opacity -= kBlackScrimOpacity *
1662                (distance - work_area_bounds.width() * ratio) /
1663                (work_area_bounds.width() * kBlackScrimFadeInRatio);
1664     opacity = std::max(opacity, 0.f);
1665   }
1666   black_scrim_layer_->SetOpacity(opacity);
1667 }
1668 
UpdateSnappedWindowsAndDividerBounds()1669 void SplitViewController::UpdateSnappedWindowsAndDividerBounds() {
1670   // Update the snapped windows' bounds.
1671   if (IsSnapped(left_window_)) {
1672     const WMEvent left_window_event(WM_EVENT_SNAP_LEFT);
1673     WindowState::Get(left_window_)->OnWMEvent(&left_window_event);
1674   }
1675   if (IsSnapped(right_window_)) {
1676     const WMEvent right_window_event(WM_EVENT_SNAP_RIGHT);
1677     WindowState::Get(right_window_)->OnWMEvent(&right_window_event);
1678   }
1679 
1680   // Update divider's bounds.
1681   if (split_view_divider_)
1682     split_view_divider_->UpdateDividerBounds();
1683 }
1684 
GetBlackScrimPosition(const gfx::Point & location_in_screen)1685 SplitViewController::SnapPosition SplitViewController::GetBlackScrimPosition(
1686     const gfx::Point& location_in_screen) {
1687   const gfx::Rect work_area_bounds =
1688       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
1689           root_window_);
1690   if (!work_area_bounds.Contains(location_in_screen))
1691     return NONE;
1692 
1693   gfx::Size left_window_min_size, right_window_min_size;
1694   if (left_window_ && left_window_->delegate())
1695     left_window_min_size = left_window_->delegate()->GetMinimumSize();
1696   if (right_window_ && right_window_->delegate())
1697     right_window_min_size = right_window_->delegate()->GetMinimumSize();
1698 
1699   bool right_side_up = IsLayoutRightSideUp();
1700   int divider_end_position = GetDividerEndPosition();
1701   // The distance from the current resizing position to the left or right side
1702   // of the screen. Note: left or right side here means the side of the
1703   // |left_window_| or |right_window_|.
1704   int left_window_distance = 0, right_window_distance = 0;
1705   int min_left_length = 0, min_right_length = 0;
1706 
1707   if (IsLayoutHorizontal()) {
1708     int left_distance = location_in_screen.x() - work_area_bounds.x();
1709     int right_distance = work_area_bounds.right() - location_in_screen.x();
1710     left_window_distance = right_side_up ? left_distance : right_distance;
1711     right_window_distance = right_side_up ? right_distance : left_distance;
1712 
1713     min_left_length = left_window_min_size.width();
1714     min_right_length = right_window_min_size.width();
1715   } else {
1716     int top_distance = location_in_screen.y() - work_area_bounds.y();
1717     int bottom_distance = work_area_bounds.bottom() - location_in_screen.y();
1718     left_window_distance = right_side_up ? top_distance : bottom_distance;
1719     right_window_distance = right_side_up ? bottom_distance : top_distance;
1720 
1721     min_left_length = left_window_min_size.height();
1722     min_right_length = right_window_min_size.height();
1723   }
1724 
1725   if (left_window_distance < divider_end_position * kOneThirdPositionRatio ||
1726       left_window_distance < min_left_length) {
1727     return LEFT;
1728   }
1729   if (right_window_distance < divider_end_position * kOneThirdPositionRatio ||
1730       right_window_distance < min_right_length) {
1731     return RIGHT;
1732   }
1733 
1734   return NONE;
1735 }
1736 
UpdateDividerPosition(const gfx::Point & location_in_screen)1737 void SplitViewController::UpdateDividerPosition(
1738     const gfx::Point& location_in_screen) {
1739   if (IsLayoutHorizontal())
1740     divider_position_ += location_in_screen.x() - previous_event_location_.x();
1741   else
1742     divider_position_ += location_in_screen.y() - previous_event_location_.y();
1743   divider_position_ = std::max(0, divider_position_);
1744 }
1745 
GetClosestFixedDividerPosition()1746 int SplitViewController::GetClosestFixedDividerPosition() {
1747   // The values in |kFixedPositionRatios| represent the fixed position of the
1748   // center of the divider while |divider_position_| represent the origin of the
1749   // divider rectangle. So, before calling FindClosestFixedPositionRatio,
1750   // extract the center from |divider_position_|. The result will also be the
1751   // center of the divider, so extract the origin, unless the result is on of
1752   // the endpoints.
1753   int divider_end_position = GetDividerEndPosition();
1754   divider_closest_ratio_ = FindClosestPositionRatio(
1755       divider_position_ + kSplitviewDividerShortSideLength / 2,
1756       divider_end_position);
1757   int fix_position = divider_end_position * divider_closest_ratio_;
1758   if (divider_closest_ratio_ > 0.f && divider_closest_ratio_ < 1.f)
1759     fix_position -= kSplitviewDividerShortSideLength / 2;
1760   return fix_position;
1761 }
1762 
StopAndShoveAnimatedDivider()1763 void SplitViewController::StopAndShoveAnimatedDivider() {
1764   DCHECK(IsDividerAnimating());
1765 
1766   divider_snap_animation_->Stop();
1767   divider_position_ = divider_snap_animation_->ending_position();
1768   NotifyDividerPositionChanged();
1769   UpdateSnappedWindowsAndDividerBounds();
1770 }
1771 
ShouldEndTabletSplitViewAfterResizing()1772 bool SplitViewController::ShouldEndTabletSplitViewAfterResizing() {
1773   DCHECK(InTabletSplitViewMode());
1774 
1775   return divider_position_ == 0 || divider_position_ == GetDividerEndPosition();
1776 }
1777 
EndTabletSplitViewAfterResizingIfAppropriate()1778 void SplitViewController::EndTabletSplitViewAfterResizingIfAppropriate() {
1779   if (!ShouldEndTabletSplitViewAfterResizing())
1780     return;
1781 
1782   aura::Window* active_window = GetActiveWindowAfterResizingUponExit();
1783 
1784   // Track the window that needs to be put back into the overview list if we
1785   // remain in overview mode.
1786   aura::Window* insert_overview_window = nullptr;
1787   if (Shell::Get()->overview_controller()->InOverviewSession())
1788     insert_overview_window = GetDefaultSnappedWindow();
1789   EndSplitView();
1790   if (active_window) {
1791     Shell::Get()->overview_controller()->EndOverview();
1792     wm::ActivateWindow(active_window);
1793   } else if (insert_overview_window) {
1794     InsertWindowToOverview(insert_overview_window, /*animate=*/false);
1795   }
1796 }
1797 
GetActiveWindowAfterResizingUponExit()1798 aura::Window* SplitViewController::GetActiveWindowAfterResizingUponExit() {
1799   DCHECK(InSplitViewMode());
1800 
1801   if (!ShouldEndTabletSplitViewAfterResizing())
1802     return nullptr;
1803 
1804   return divider_position_ == 0 ? GetPhysicalRightOrBottomWindow()
1805                                 : GetPhysicalLeftOrTopWindow();
1806 }
1807 
GetDividerEndPosition() const1808 int SplitViewController::GetDividerEndPosition() const {
1809   const gfx::Rect work_area_bounds =
1810       screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
1811           root_window_);
1812   return IsLayoutHorizontal() ? work_area_bounds.width()
1813                               : work_area_bounds.height();
1814 }
1815 
OnWindowSnapped(aura::Window * window)1816 void SplitViewController::OnWindowSnapped(aura::Window* window) {
1817   RestoreTransformIfApplicable(window);
1818   UpdateStateAndNotifyObservers();
1819   UpdateWindowStackingAfterSnap(window);
1820 }
1821 
OnSnappedWindowDetached(aura::Window * window,bool window_drag)1822 void SplitViewController::OnSnappedWindowDetached(aura::Window* window,
1823                                                   bool window_drag) {
1824   StopObserving(GetPositionOfSnappedWindow(window));
1825 
1826   // Resizing (or the divider snap animation) may continue, but |window| will no
1827   // longer have anything to do with it.
1828   if (is_resizing_ || IsDividerAnimating())
1829     FinishWindowResizing(window);
1830 
1831   if (!left_window_ && !right_window_) {
1832     // If there is no snapped window at this moment, ends split view mode. Note
1833     // this will update overview window grid bounds if the overview mode is
1834     // active at the moment.
1835     EndSplitView(window_drag ? EndReason::kWindowDragStarted
1836                              : EndReason::kNormal);
1837   } else {
1838     DCHECK_EQ(split_view_type_, SplitViewType::kTabletType);
1839     // If there is still one snapped window after minimizing/closing one snapped
1840     // window, update its snap state and open overview window grid.
1841     default_snap_position_ = left_window_ ? LEFT : RIGHT;
1842     UpdateStateAndNotifyObservers();
1843     Shell::Get()->overview_controller()->StartOverview(
1844         window_drag ? OverviewEnterExitType::kImmediateEnter
1845                     : OverviewEnterExitType::kNormal);
1846   }
1847 }
1848 
FindClosestPositionRatio(float distance,float length)1849 float SplitViewController::FindClosestPositionRatio(float distance,
1850                                                     float length) {
1851   float current_ratio = distance / length;
1852   float closest_ratio = 0.f;
1853   std::vector<float> position_ratios(
1854       kFixedPositionRatios,
1855       kFixedPositionRatios + sizeof(kFixedPositionRatios) / sizeof(float));
1856   GetDividerOptionalPositionRatios(&position_ratios);
1857   for (float ratio : position_ratios) {
1858     if (std::abs(current_ratio - ratio) <
1859         std::abs(current_ratio - closest_ratio)) {
1860       closest_ratio = ratio;
1861     }
1862   }
1863   return closest_ratio;
1864 }
1865 
GetDividerOptionalPositionRatios(std::vector<float> * out_position_ratios)1866 void SplitViewController::GetDividerOptionalPositionRatios(
1867     std::vector<float>* out_position_ratios) {
1868   const bool landscape = IsCurrentScreenOrientationLandscape();
1869   const int min_left_size =
1870       GetMinimumWindowSize(GetPhysicalLeftOrTopWindow(), landscape);
1871   const int min_right_size =
1872       GetMinimumWindowSize(GetPhysicalRightOrBottomWindow(), landscape);
1873   const int divider_end_position = GetDividerEndPosition();
1874   const float min_size_left_ratio =
1875       static_cast<float>(min_left_size) / divider_end_position;
1876   const float min_size_right_ratio =
1877       static_cast<float>(min_right_size) / divider_end_position;
1878   if (min_size_left_ratio <= kOneThirdPositionRatio)
1879     out_position_ratios->push_back(kOneThirdPositionRatio);
1880   if (min_size_right_ratio <= kOneThirdPositionRatio)
1881     out_position_ratios->push_back(kTwoThirdPositionRatio);
1882 }
1883 
GetWindowComponentForResize(aura::Window * window)1884 int SplitViewController::GetWindowComponentForResize(aura::Window* window) {
1885   DCHECK(IsWindowInSplitView(window));
1886   return window == left_window_ ? HTRIGHT : HTLEFT;
1887 }
1888 
GetEndDragLocationInScreen(aura::Window * window,const gfx::Point & location_in_screen)1889 gfx::Point SplitViewController::GetEndDragLocationInScreen(
1890     aura::Window* window,
1891     const gfx::Point& location_in_screen) {
1892   gfx::Point end_location(location_in_screen);
1893   if (!IsWindowInSplitView(window))
1894     return end_location;
1895 
1896   const gfx::Rect bounds = GetSnappedWindowBoundsInScreen(
1897       GetPositionOfSnappedWindow(window), window);
1898   if (IsLayoutHorizontal()) {
1899     end_location.set_x(window == GetPhysicalLeftOrTopWindow() ? bounds.right()
1900                                                               : bounds.x());
1901   } else {
1902     end_location.set_y(window == GetPhysicalLeftOrTopWindow() ? bounds.bottom()
1903                                                               : bounds.y());
1904   }
1905   return end_location;
1906 }
1907 
RestoreTransformIfApplicable(aura::Window * window)1908 void SplitViewController::RestoreTransformIfApplicable(aura::Window* window) {
1909   // If the transform of the window has been changed, calculate a good starting
1910   // transform based on its transformed bounds before to be snapped.
1911   auto iter = snapping_window_transformed_bounds_map_.find(window);
1912   if (iter == snapping_window_transformed_bounds_map_.end())
1913     return;
1914 
1915   const gfx::Rect item_bounds = iter->second;
1916   snapping_window_transformed_bounds_map_.erase(iter);
1917 
1918   // Restore the window's transform first if it's not identity.
1919   if (!window->layer()->GetTargetTransform().IsIdentity()) {
1920     // Calculate the starting transform based on the window's expected snapped
1921     // bounds and its transformed bounds before to be snapped.
1922     const gfx::Rect snapped_bounds = GetSnappedWindowBoundsInScreen(
1923         GetPositionOfSnappedWindow(window), window);
1924     const gfx::Transform starting_transform = gfx::TransformBetweenRects(
1925         gfx::RectF(snapped_bounds), gfx::RectF(item_bounds));
1926     SetTransformWithAnimation(window, starting_transform, gfx::Transform());
1927   }
1928 }
1929 
UpdateWindowStackingAfterSnap(aura::Window * newly_snapped)1930 void SplitViewController::UpdateWindowStackingAfterSnap(
1931     aura::Window* newly_snapped) {
1932   if (split_view_divider_)
1933     split_view_divider_->SetAlwaysOnTop(true);
1934 
1935   aura::Window* other_snapped =
1936       newly_snapped == left_window_ ? right_window_ : left_window_;
1937   if (other_snapped) {
1938     DCHECK(newly_snapped == left_window_ || newly_snapped == right_window_);
1939     other_snapped->parent()->StackChildAtTop(other_snapped);
1940   }
1941 
1942   newly_snapped->parent()->StackChildAtTop(newly_snapped);
1943 }
1944 
SetWindowsTransformDuringResizing()1945 void SplitViewController::SetWindowsTransformDuringResizing() {
1946   DCHECK(InTabletSplitViewMode());
1947   DCHECK_GE(divider_position_, 0);
1948   const bool horizontal = IsLayoutHorizontal();
1949   aura::Window* left_or_top_window = GetPhysicalLeftOrTopWindow();
1950   aura::Window* right_or_bottom_window = GetPhysicalRightOrBottomWindow();
1951 
1952   gfx::Transform left_or_top_transform;
1953   if (left_or_top_window) {
1954     const int left_size = divider_position_;
1955     const int left_minimum_size =
1956         GetMinimumWindowSize(left_or_top_window, horizontal);
1957     const int distance = left_size - left_minimum_size;
1958     if (distance < 0) {
1959       left_or_top_transform.Translate(horizontal ? distance : 0,
1960                                       horizontal ? 0 : distance);
1961     }
1962     SetTransform(left_or_top_window, left_or_top_transform);
1963   }
1964 
1965   gfx::Transform right_or_bottom_transform;
1966   if (right_or_bottom_window) {
1967     const int right_size = GetDividerEndPosition() - divider_position_ -
1968                            kSplitviewDividerShortSideLength;
1969     const int right_minimum_size =
1970         GetMinimumWindowSize(right_or_bottom_window, horizontal);
1971     const int distance = right_size - right_minimum_size;
1972     if (distance < 0) {
1973       right_or_bottom_transform.Translate(horizontal ? -distance : 0,
1974                                           horizontal ? 0 : -distance);
1975     }
1976     SetTransform(right_or_bottom_window, right_or_bottom_transform);
1977   }
1978 
1979   if (black_scrim_layer_.get()) {
1980     black_scrim_layer_->SetTransform(left_or_top_transform.IsIdentity()
1981                                          ? right_or_bottom_transform
1982                                          : left_or_top_transform);
1983   }
1984 }
1985 
RestoreWindowsTransformAfterResizing()1986 void SplitViewController::RestoreWindowsTransformAfterResizing() {
1987   DCHECK(InSplitViewMode());
1988   if (left_window_)
1989     SetTransform(left_window_, gfx::Transform());
1990   if (right_window_)
1991     SetTransform(right_window_, gfx::Transform());
1992   if (black_scrim_layer_.get())
1993     black_scrim_layer_->SetTransform(gfx::Transform());
1994 }
1995 
SetTransformWithAnimation(aura::Window * window,const gfx::Transform & start_transform,const gfx::Transform & target_transform)1996 void SplitViewController::SetTransformWithAnimation(
1997     aura::Window* window,
1998     const gfx::Transform& start_transform,
1999     const gfx::Transform& target_transform) {
2000   gfx::Point target_origin =
2001       gfx::ToRoundedPoint(GetTargetBoundsInScreen(window).origin());
2002   for (auto* window_iter : GetTransientTreeIterator(window)) {
2003     // Adjust |start_transform| and |target_transform| for the transient child.
2004     aura::Window* parent_window = window_iter->parent();
2005     gfx::Rect original_bounds(window_iter->GetTargetBounds());
2006     ::wm::ConvertRectToScreen(parent_window, &original_bounds);
2007     gfx::Transform new_start_transform =
2008         TransformAboutPivot(gfx::Point(target_origin.x() - original_bounds.x(),
2009                                        target_origin.y() - original_bounds.y()),
2010                             start_transform);
2011     gfx::Transform new_target_transform =
2012         TransformAboutPivot(gfx::Point(target_origin.x() - original_bounds.x(),
2013                                        target_origin.y() - original_bounds.y()),
2014                             target_transform);
2015     if (new_start_transform != window_iter->layer()->GetTargetTransform())
2016       window_iter->SetTransform(new_start_transform);
2017 
2018     DoSplitviewTransformAnimation(
2019         window_iter->layer(), SPLITVIEW_ANIMATION_SET_WINDOW_TRANSFORM,
2020         new_target_transform,
2021         window_iter == window
2022             ? std::make_unique<WindowTransformAnimationObserver>(window)
2023             : nullptr);
2024   }
2025 }
2026 
UpdateSnappingWindowTransformedBounds(aura::Window * window)2027 void SplitViewController::UpdateSnappingWindowTransformedBounds(
2028     aura::Window* window) {
2029   if (!window->layer()->GetTargetTransform().IsIdentity()) {
2030     snapping_window_transformed_bounds_map_[window] = gfx::ToEnclosedRect(
2031         window_util::GetTransformedBounds(window, /*top_inset=*/0));
2032   }
2033 }
2034 
InsertWindowToOverview(aura::Window * window,bool animate)2035 void SplitViewController::InsertWindowToOverview(aura::Window* window,
2036                                                  bool animate) {
2037   if (!window || !GetOverviewSession())
2038     return;
2039   GetOverviewSession()->AddItemInMruOrder(window, /*reposition=*/true, animate,
2040                                           /*restack=*/true);
2041 }
2042 
FinishWindowResizing(aura::Window * window)2043 void SplitViewController::FinishWindowResizing(aura::Window* window) {
2044   if (window != nullptr) {
2045     WindowState* window_state = WindowState::Get(window);
2046     window_state->OnCompleteDrag(gfx::PointF(
2047         GetEndDragLocationInScreen(window, previous_event_location_)));
2048     window_state->DeleteDragDetails();
2049   }
2050 }
2051 
EndResizeImpl()2052 void SplitViewController::EndResizeImpl() {
2053   DCHECK(InSplitViewMode());
2054   DCHECK(!is_resizing_);
2055   // Resize may not end with |EndResize()|, so make sure to clear here too.
2056   presentation_time_recorder_.reset();
2057   RestoreWindowsTransformAfterResizing();
2058   FinishWindowResizing(left_window_);
2059   FinishWindowResizing(right_window_);
2060 }
2061 
EndWindowDragImpl(aura::Window * window,bool is_being_destroyed,SnapPosition desired_snap_position,const gfx::Point & last_location_in_screen)2062 void SplitViewController::EndWindowDragImpl(
2063     aura::Window* window,
2064     bool is_being_destroyed,
2065     SnapPosition desired_snap_position,
2066     const gfx::Point& last_location_in_screen) {
2067   if (split_view_divider_)
2068     split_view_divider_->OnWindowDragEnded();
2069 
2070   // If the dragged window is to be destroyed, do not try to snap it.
2071   if (is_being_destroyed)
2072     return;
2073 
2074   // If dragged window was in overview before or it has been added to overview
2075   // window by dropping on the new selector item, do nothing.
2076   if (GetOverviewSession() && GetOverviewSession()->IsWindowInOverview(window))
2077     return;
2078 
2079   DCHECK_EQ(root_window_, window->GetRootWindow());
2080 
2081   const bool was_splitview_active = InSplitViewMode();
2082   if (desired_snap_position == SplitViewController::NONE) {
2083     if (was_splitview_active) {
2084       // Even though |snap_position| equals |NONE|, the dragged window still
2085       // needs to be snapped if splitview mode is active at the moment.
2086       // Calculate the expected snap position based on the last event
2087       // location. Note if there is already a window at |desired_snap_postion|,
2088       // SnapWindow() will put the previous snapped window in overview.
2089       SnapWindow(window, ComputeSnapPosition(last_location_in_screen));
2090       wm::ActivateWindow(window);
2091     } else {
2092       // Restore the dragged window's transform first if it's not identity. It
2093       // needs to be called before the transformed window's bounds change so
2094       // that its transient children are layout'ed properly (the layout happens
2095       // when window's bounds change).
2096       SetTransformWithAnimation(window, window->layer()->GetTargetTransform(),
2097                                 gfx::Transform());
2098 
2099       OverviewSession* overview_session = GetOverviewSession();
2100       if (overview_session) {
2101         overview_session->SetWindowListNotAnimatedWhenExiting(root_window_);
2102         // Set the overview exit type to kImmediateExit to avoid update bounds
2103         // animation of the windows in overview grid.
2104         overview_session->set_enter_exit_overview_type(
2105             OverviewEnterExitType::kImmediateExit);
2106       }
2107       // Activate the dragged window and end the overview. The dragged window
2108       // will be restored back to its previous state before dragging.
2109       wm::ActivateWindow(window);
2110       Shell::Get()->overview_controller()->EndOverview();
2111 
2112       // Update the dragged window's bounds. It's possible that the dragged
2113       // window's bounds was changed during dragging. Update its bounds after
2114       // the drag ends to ensure it has the right bounds.
2115       TabletModeWindowState::UpdateWindowPosition(WindowState::Get(window),
2116                                                   /*animate=*/true);
2117     }
2118   } else {
2119     aura::Window* initiator_window =
2120         window->GetProperty(kTabDraggingSourceWindowKey);
2121     // Note SnapWindow() might put the previous window that was snapped at the
2122     // |desired_snap_position| in overview.
2123     SnapWindow(window, desired_snap_position,
2124                /*use_divider_spawn_animation=*/!initiator_window);
2125     wm::ActivateWindow(window);
2126 
2127     if (!was_splitview_active) {
2128       // If splitview mode was not active before snapping the dragged
2129       // window, snap the initiator window to the other side of the screen
2130       // if it's not the same window as the dragged window.
2131       if (initiator_window && initiator_window != window) {
2132         SnapWindow(initiator_window,
2133                    (desired_snap_position == SplitViewController::LEFT)
2134                        ? SplitViewController::RIGHT
2135                        : SplitViewController::LEFT);
2136       } else {
2137         // If overview is not active, open overview.
2138         Shell::Get()->overview_controller()->StartOverview();
2139       }
2140     }
2141   }
2142 }
2143 
ComputeSnapPosition(const gfx::Point & last_location_in_screen)2144 SplitViewController::SnapPosition SplitViewController::ComputeSnapPosition(
2145     const gfx::Point& last_location_in_screen) {
2146   const int divider_position = InSplitViewMode() ? this->divider_position()
2147                                                  : GetDefaultDividerPosition();
2148   const int position = IsLayoutHorizontal() ? last_location_in_screen.x()
2149                                             : last_location_in_screen.y();
2150   return (position <= divider_position) == IsLayoutRightSideUp()
2151              ? SplitViewController::LEFT
2152              : SplitViewController::RIGHT;
2153 }
2154 
2155 }  // namespace ash
2156