1 // Copyright 2018 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/pip/pip_window_resizer.h"
6 
7 #include <memory>
8 #include <string>
9 #include <tuple>
10 #include <utility>
11 
12 #include "ash/keyboard/ui/keyboard_ui_controller.h"
13 #include "ash/keyboard/ui/test/keyboard_test_util.h"
14 #include "ash/metrics/pip_uma.h"
15 #include "ash/public/cpp/keyboard/keyboard_switches.h"
16 #include "ash/shelf/shelf.h"
17 #include "ash/shell.h"
18 #include "ash/test/ash_test_base.h"
19 #include "ash/wm/pip/pip_positioner.h"
20 #include "ash/wm/pip/pip_test_utils.h"
21 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
22 #include "ash/wm/window_state.h"
23 #include "ash/wm/wm_event.h"
24 #include "base/callback_helpers.h"
25 #include "base/test/metrics/histogram_tester.h"
26 #include "ui/aura/client/aura_constants.h"
27 #include "ui/aura/window.h"
28 #include "ui/base/hit_test.h"
29 #include "ui/display/scoped_display_for_new_windows.h"
30 #include "ui/gfx/geometry/insets.h"
31 #include "ui/views/widget/widget.h"
32 #include "ui/wm/core/coordinate_conversion.h"
33 
34 namespace ash {
35 
36 namespace {
37 
38 using ::chromeos::WindowStateType;
39 
40 // WindowState based on a given initial state. Records the last resize bounds.
41 class FakeWindowState : public WindowState::State {
42  public:
FakeWindowState(WindowStateType initial_state_type)43   explicit FakeWindowState(WindowStateType initial_state_type)
44       : state_type_(initial_state_type) {}
45   ~FakeWindowState() override = default;
46 
47   // WindowState::State overrides:
OnWMEvent(WindowState * window_state,const WMEvent * event)48   void OnWMEvent(WindowState* window_state, const WMEvent* event) override {
49     if (event->IsBoundsEvent()) {
50       if (event->type() == WM_EVENT_SET_BOUNDS) {
51         const auto* set_bounds_event =
52             static_cast<const SetBoundsWMEvent*>(event);
53         last_bounds_ = set_bounds_event->requested_bounds();
54         last_window_state_ = window_state;
55       }
56     }
57   }
GetType() const58   WindowStateType GetType() const override { return state_type_; }
AttachState(WindowState * window_state,WindowState::State * previous_state)59   void AttachState(WindowState* window_state,
60                    WindowState::State* previous_state) override {}
DetachState(WindowState * window_state)61   void DetachState(WindowState* window_state) override {}
62 
last_bounds() const63   const gfx::Rect& last_bounds() const { return last_bounds_; }
last_window_state()64   WindowState* last_window_state() { return last_window_state_; }
65 
66  private:
67   WindowStateType state_type_;
68   gfx::Rect last_bounds_;
69   WindowState* last_window_state_ = nullptr;
70 
71   DISALLOW_COPY_AND_ASSIGN(FakeWindowState);
72 };
73 
74 }  // namespace
75 
76 using Sample = base::HistogramBase::Sample;
77 
78 class PipWindowResizerTest : public AshTestBase,
79                              public ::testing::WithParamInterface<
80                                  std::tuple<std::string, std::size_t>> {
81  public:
82   PipWindowResizerTest() = default;
83   ~PipWindowResizerTest() override = default;
84 
SetUp()85   void SetUp() override {
86     AshTestBase::SetUp();
87     SetVirtualKeyboardEnabled(true);
88 
89     const std::string& display_string = std::get<0>(GetParam());
90     const std::size_t root_window_index = std::get<1>(GetParam());
91     UpdateWorkArea(display_string);
92     ASSERT_LT(root_window_index, Shell::GetAllRootWindows().size());
93     scoped_display_ = std::make_unique<display::ScopedDisplayForNewWindows>(
94         Shell::GetAllRootWindows()[root_window_index]);
95     ForceHideShelvesForTest();
96   }
97 
TearDown()98   void TearDown() override {
99     scoped_display_.reset();
100     SetVirtualKeyboardEnabled(false);
101     AshTestBase::TearDown();
102   }
103 
104  protected:
widget()105   views::Widget* widget() { return widget_.get(); }
window()106   aura::Window* window() { return window_; }
test_state()107   FakeWindowState* test_state() { return test_state_; }
histograms()108   base::HistogramTester& histograms() { return histograms_; }
109 
CreateWidgetForTest(const gfx::Rect & bounds)110   std::unique_ptr<views::Widget> CreateWidgetForTest(const gfx::Rect& bounds) {
111     auto* root_window = Shell::GetRootWindowForNewWindows();
112     gfx::Rect screen_bounds = bounds;
113     ::wm::ConvertRectToScreen(root_window, &screen_bounds);
114 
115     std::unique_ptr<views::Widget> widget(new views::Widget);
116     views::Widget::InitParams params;
117     params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
118     params.bounds = screen_bounds;
119     params.z_order = ui::ZOrderLevel::kFloatingWindow;
120     params.context = root_window;
121     widget->Init(std::move(params));
122     widget->Show();
123     return widget;
124   }
125 
CreateResizerForTest(int window_component)126   PipWindowResizer* CreateResizerForTest(int window_component) {
127     return CreateResizerForTest(window_component, window(),
128                                 window()->bounds().CenterPoint());
129   }
130 
CreateResizerForTest(int window_component,const gfx::Point & point_in_parent)131   PipWindowResizer* CreateResizerForTest(int window_component,
132                                          const gfx::Point& point_in_parent) {
133     return CreateResizerForTest(window_component, window(), point_in_parent);
134   }
135 
CreateResizerForTest(int window_component,aura::Window * window,const gfx::Point & point_in_parent)136   PipWindowResizer* CreateResizerForTest(int window_component,
137                                          aura::Window* window,
138                                          const gfx::Point& point_in_parent) {
139     WindowState* window_state = WindowState::Get(window);
140     window_state->CreateDragDetails(gfx::PointF(point_in_parent),
141                                     window_component,
142                                     ::wm::WINDOW_MOVE_SOURCE_MOUSE);
143     return new PipWindowResizer(window_state);
144   }
145 
CalculateDragPoint(const WindowResizer & resizer,int delta_x,int delta_y) const146   gfx::PointF CalculateDragPoint(const WindowResizer& resizer,
147                                  int delta_x,
148                                  int delta_y) const {
149     gfx::PointF location = resizer.GetInitialLocation();
150     location.set_x(location.x() + delta_x);
151     location.set_y(location.y() + delta_y);
152     return location;
153   }
154 
Fling(std::unique_ptr<WindowResizer> resizer,float velocity_x,float velocity_y)155   void Fling(std::unique_ptr<WindowResizer> resizer,
156              float velocity_x,
157              float velocity_y) {
158     aura::Window* target_window = resizer->GetTarget();
159     base::TimeTicks timestamp = base::TimeTicks::Now();
160     ui::GestureEventDetails details = ui::GestureEventDetails(
161         ui::ET_SCROLL_FLING_START, velocity_x, velocity_y);
162     ui::GestureEvent event = ui::GestureEvent(
163         target_window->bounds().origin().x(),
164         target_window->bounds().origin().y(), ui::EF_NONE, timestamp, details);
165     ui::Event::DispatcherApi(&event).set_target(target_window);
166     resizer->FlingOrSwipe(&event);
167   }
168 
PreparePipWindow(const gfx::Rect & bounds)169   void PreparePipWindow(const gfx::Rect& bounds) {
170     widget_ = CreateWidgetForTest(bounds);
171     window_ = widget_->GetNativeWindow();
172     test_state_ = new FakeWindowState(WindowStateType::kPip);
173     WindowState::Get(window_)->SetStateObject(
174         std::unique_ptr<WindowState::State>(test_state_));
175   }
176 
177  private:
178   std::unique_ptr<views::Widget> widget_;
179   aura::Window* window_;
180   FakeWindowState* test_state_;
181   base::HistogramTester histograms_;
182   std::unique_ptr<display::ScopedDisplayForNewWindows> scoped_display_;
183 
UpdateWorkArea(const std::string & bounds)184   void UpdateWorkArea(const std::string& bounds) {
185     UpdateDisplay(bounds);
186     for (aura::Window* root : Shell::GetAllRootWindows())
187       Shell::Get()->SetDisplayWorkAreaInsets(root, gfx::Insets());
188   }
189 
190   DISALLOW_COPY_AND_ASSIGN(PipWindowResizerTest);
191 };
192 
TEST_P(PipWindowResizerTest,PipWindowCanDrag)193 TEST_P(PipWindowResizerTest, PipWindowCanDrag) {
194   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
195 
196   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
197   ASSERT_TRUE(resizer.get());
198 
199   resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
200   EXPECT_EQ(gfx::Rect(200, 210, 100, 100), test_state()->last_bounds());
201 }
202 
TEST_P(PipWindowResizerTest,PipWindowCanResize)203 TEST_P(PipWindowResizerTest, PipWindowCanResize) {
204   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
205   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTBOTTOM));
206   ASSERT_TRUE(resizer.get());
207 
208   resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
209   EXPECT_EQ(gfx::Rect(200, 200, 100, 110), test_state()->last_bounds());
210 }
211 
TEST_P(PipWindowResizerTest,PipWindowDragIsRestrictedToWorkArea)212 TEST_P(PipWindowResizerTest, PipWindowDragIsRestrictedToWorkArea) {
213   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
214   // Specify point in parent as center so the drag point does not leave the
215   // display. If the drag point is not in any display bounds, it causes the
216   // window to be moved to the default display.
217   std::unique_ptr<PipWindowResizer> resizer(
218       CreateResizerForTest(HTCAPTION, gfx::Point(250, 250)));
219   ASSERT_TRUE(resizer.get());
220 
221   // Drag to the right.
222   resizer->Drag(CalculateDragPoint(*resizer, 250, 0), 0);
223   EXPECT_EQ(gfx::Rect(292, 200, 100, 100), test_state()->last_bounds());
224 
225   // Drag down.
226   resizer->Drag(CalculateDragPoint(*resizer, 0, 250), 0);
227   EXPECT_EQ(gfx::Rect(200, 292, 100, 100), test_state()->last_bounds());
228 
229   // Drag to the left.
230   resizer->Drag(CalculateDragPoint(*resizer, -250, 0), 0);
231   EXPECT_EQ(gfx::Rect(8, 200, 100, 100), test_state()->last_bounds());
232 
233   // Drag up.
234   resizer->Drag(CalculateDragPoint(*resizer, 0, -250), 0);
235   EXPECT_EQ(gfx::Rect(200, 8, 100, 100), test_state()->last_bounds());
236 }
237 
TEST_P(PipWindowResizerTest,PipWindowCanBeDraggedInTabletMode)238 TEST_P(PipWindowResizerTest, PipWindowCanBeDraggedInTabletMode) {
239   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
240 
241   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
242   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
243   ASSERT_TRUE(resizer.get());
244 
245   resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
246   EXPECT_EQ(gfx::Rect(200, 210, 100, 100), test_state()->last_bounds());
247 }
248 
TEST_P(PipWindowResizerTest,PipWindowCanBeResizedInTabletMode)249 TEST_P(PipWindowResizerTest, PipWindowCanBeResizedInTabletMode) {
250   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
251 
252   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
253   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTBOTTOM));
254   ASSERT_TRUE(resizer.get());
255 
256   resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
257   EXPECT_EQ(gfx::Rect(200, 200, 100, 110), test_state()->last_bounds());
258 }
259 
TEST_P(PipWindowResizerTest,ResizingPipWindowDoesNotTriggerFling)260 TEST_P(PipWindowResizerTest, ResizingPipWindowDoesNotTriggerFling) {
261   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
262   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTBOTTOM));
263   ASSERT_TRUE(resizer.get());
264 
265   Fling(std::move(resizer), 0.f, 4000.f);
266 
267   // Ensure that the PIP window isn't flung to the bottom edge during resize.
268   EXPECT_EQ(gfx::Point(8, 8), test_state()->last_bounds().origin());
269 }
270 
TEST_P(PipWindowResizerTest,PipWindowCanBeSwipeDismissed)271 TEST_P(PipWindowResizerTest, PipWindowCanBeSwipeDismissed) {
272   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
273   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
274   ASSERT_TRUE(resizer.get());
275 
276   // Drag to the left.
277   resizer->Drag(CalculateDragPoint(*resizer, -100, 0), 0);
278 
279   // Should be dismissed when the drag completes.
280   resizer->CompleteDrag();
281   EXPECT_TRUE(widget()->IsClosed());
282 }
283 
TEST_P(PipWindowResizerTest,PipWindowPartiallySwipedDoesNotDismiss)284 TEST_P(PipWindowResizerTest, PipWindowPartiallySwipedDoesNotDismiss) {
285   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
286   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
287   ASSERT_TRUE(resizer.get());
288 
289   // Drag to the left, but only a little bit.
290   resizer->Drag(CalculateDragPoint(*resizer, -30, 0), 0);
291 
292   // Should not be dismissed when the drag completes.
293   resizer->CompleteDrag();
294   EXPECT_FALSE(widget()->IsClosed());
295   EXPECT_EQ(gfx::Rect(8, 8, 100, 100), test_state()->last_bounds());
296 }
297 
TEST_P(PipWindowResizerTest,PipWindowInSwipeToDismissGestureLocksToAxis)298 TEST_P(PipWindowResizerTest, PipWindowInSwipeToDismissGestureLocksToAxis) {
299   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
300   std::unique_ptr<PipWindowResizer> resizer(
301       CreateResizerForTest(HTCAPTION, gfx::Point(50, 50)));
302   ASSERT_TRUE(resizer.get());
303 
304   // Drag to the left, but only a little bit, to start a swipe-to-dismiss.
305   resizer->Drag(CalculateDragPoint(*resizer, -30, 0), 0);
306   EXPECT_EQ(gfx::Rect(-22, 8, 100, 100), test_state()->last_bounds());
307 
308   // Now try to drag down, it should be locked to the horizontal axis.
309   resizer->Drag(CalculateDragPoint(*resizer, -30, 30), 0);
310   EXPECT_EQ(gfx::Rect(-22, 8, 100, 100), test_state()->last_bounds());
311 }
312 
TEST_P(PipWindowResizerTest,PipWindowMovedAwayFromScreenEdgeNoLongerCanSwipeToDismiss)313 TEST_P(PipWindowResizerTest,
314        PipWindowMovedAwayFromScreenEdgeNoLongerCanSwipeToDismiss) {
315   PreparePipWindow(gfx::Rect(8, 16, 100, 100));
316   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
317   ASSERT_TRUE(resizer.get());
318 
319   // Drag to the right and up a bit.
320   resizer->Drag(CalculateDragPoint(*resizer, 30, -8), 0);
321   EXPECT_EQ(gfx::Rect(38, 8, 100, 100), test_state()->last_bounds());
322 
323   // Now try to drag to the left start a swipe-to-dismiss. It should stop
324   // at the edge of the work area.
325   resizer->Drag(CalculateDragPoint(*resizer, -30, -8), 0);
326   EXPECT_EQ(gfx::Rect(8, 8, 100, 100), test_state()->last_bounds());
327 }
328 
TEST_P(PipWindowResizerTest,PipWindowAtCornerLocksToOneAxisOnSwipeToDismiss)329 TEST_P(PipWindowResizerTest, PipWindowAtCornerLocksToOneAxisOnSwipeToDismiss) {
330   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
331   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
332   ASSERT_TRUE(resizer.get());
333 
334   // Try dragging up and to the left. It should lock onto the axis with the
335   // largest displacement.
336   resizer->Drag(CalculateDragPoint(*resizer, -30, -40), 0);
337   EXPECT_EQ(gfx::Rect(8, -32, 100, 100), test_state()->last_bounds());
338 }
339 
TEST_P(PipWindowResizerTest,PipWindowMustBeDraggedMostlyInDirectionOfDismissToInitiateSwipeToDismiss)340 TEST_P(
341     PipWindowResizerTest,
342     PipWindowMustBeDraggedMostlyInDirectionOfDismissToInitiateSwipeToDismiss) {
343   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
344   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
345   ASSERT_TRUE(resizer.get());
346 
347   // Try a lot downward and a bit to the left. Swiping should not be initiated.
348   resizer->Drag(CalculateDragPoint(*resizer, -30, 50), 0);
349   EXPECT_EQ(gfx::Rect(8, 58, 100, 100), test_state()->last_bounds());
350 }
351 
TEST_P(PipWindowResizerTest,PipWindowDoesNotMoveUntilStatusOfSwipeToDismissGestureIsKnown)352 TEST_P(PipWindowResizerTest,
353        PipWindowDoesNotMoveUntilStatusOfSwipeToDismissGestureIsKnown) {
354   PreparePipWindow(gfx::Rect(8, 8, 100, 100));
355   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
356   ASSERT_TRUE(resizer.get());
357 
358   // Move a small amount - this should not trigger any bounds change, since
359   // we don't know whether a swipe will start or not.
360   resizer->Drag(CalculateDragPoint(*resizer, -4, 0), 0);
361   EXPECT_TRUE(test_state()->last_bounds().IsEmpty());
362 }
363 
TEST_P(PipWindowResizerTest,PipWindowIsFlungToEdge)364 TEST_P(PipWindowResizerTest, PipWindowIsFlungToEdge) {
365   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
366 
367   {
368     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
369     ASSERT_TRUE(resizer.get());
370 
371     resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
372     Fling(std::move(resizer), 0.f, 4000.f);
373 
374     // Flung downwards.
375     EXPECT_EQ(gfx::Rect(200, 292, 100, 100), test_state()->last_bounds());
376   }
377 
378   {
379     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
380     ASSERT_TRUE(resizer.get());
381 
382     resizer->Drag(CalculateDragPoint(*resizer, 0, -10), 0);
383     Fling(std::move(resizer), 0.f, -4000.f);
384 
385     // Flung upwards.
386     EXPECT_EQ(gfx::Rect(200, 8, 100, 100), test_state()->last_bounds());
387   }
388 
389   {
390     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
391     ASSERT_TRUE(resizer.get());
392 
393     resizer->Drag(CalculateDragPoint(*resizer, 10, 0), 0);
394     Fling(std::move(resizer), 4000.f, 0.f);
395 
396     // Flung to the right.
397     EXPECT_EQ(gfx::Rect(292, 200, 100, 100), test_state()->last_bounds());
398   }
399 
400   {
401     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
402     ASSERT_TRUE(resizer.get());
403 
404     resizer->Drag(CalculateDragPoint(*resizer, -10, 0), 0);
405     Fling(std::move(resizer), -4000.f, 0.f);
406 
407     // Flung to the left.
408     EXPECT_EQ(gfx::Rect(8, 200, 100, 100), test_state()->last_bounds());
409   }
410 }
411 
TEST_P(PipWindowResizerTest,PipWindowIsFlungDiagonally)412 TEST_P(PipWindowResizerTest, PipWindowIsFlungDiagonally) {
413   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
414 
415   {
416     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
417     ASSERT_TRUE(resizer.get());
418 
419     resizer->Drag(CalculateDragPoint(*resizer, 10, 10), 0);
420     Fling(std::move(resizer), 3000.f, 3000.f);
421 
422     // Flung downward and to the right, into the corner.
423     EXPECT_EQ(gfx::Rect(292, 292, 100, 100), test_state()->last_bounds());
424   }
425 
426   {
427     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
428     ASSERT_TRUE(resizer.get());
429 
430     resizer->Drag(CalculateDragPoint(*resizer, 3, 4), 0);
431     Fling(std::move(resizer), 3000.f, 4000.f);
432 
433     // Flung downward and to the right, but reaching the bottom edge first.
434     EXPECT_EQ(gfx::Rect(269, 292, 100, 100), test_state()->last_bounds());
435   }
436 
437   {
438     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
439     ASSERT_TRUE(resizer.get());
440 
441     resizer->Drag(CalculateDragPoint(*resizer, 4, 3), 0);
442     Fling(std::move(resizer), 4000.f, 3000.f);
443 
444     // Flung downward and to the right, but reaching the right edge first.
445     EXPECT_EQ(gfx::Rect(292, 269, 100, 100), test_state()->last_bounds());
446   }
447 
448   {
449     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
450     ASSERT_TRUE(resizer.get());
451 
452     resizer->Drag(CalculateDragPoint(*resizer, -3, -4), 0);
453     Fling(std::move(resizer), -3000.f, -4000.f);
454 
455     // Flung upward and to the left, but reaching the top edge first.
456     EXPECT_EQ(gfx::Rect(56, 8, 100, 100), test_state()->last_bounds());
457   }
458 
459   {
460     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
461     ASSERT_TRUE(resizer.get());
462 
463     resizer->Drag(CalculateDragPoint(*resizer, -4, -3), 0);
464     Fling(std::move(resizer), -4000.f, -3000.f);
465 
466     // Flung upward and to the left, but reaching the left edge first.
467     EXPECT_EQ(gfx::Rect(8, 56, 100, 100), test_state()->last_bounds());
468   }
469 
470   {
471     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
472     ASSERT_TRUE(resizer.get());
473 
474     resizer->Drag(CalculateDragPoint(*resizer, 3, -9), 0);
475     Fling(std::move(resizer), 3000.f, -9000.f);
476 
477     // Flung upward and to the right, but reaching the top edge first.
478     EXPECT_EQ(gfx::Rect(264, 8, 100, 100), test_state()->last_bounds());
479   }
480 
481   {
482     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
483     ASSERT_TRUE(resizer.get());
484 
485     resizer->Drag(CalculateDragPoint(*resizer, 3, -3), 0);
486     Fling(std::move(resizer), 3000.f, -3000.f);
487 
488     // Flung upward and to the right, but reaching the right edge first.
489     EXPECT_EQ(gfx::Rect(292, 108, 100, 100), test_state()->last_bounds());
490   }
491 
492   {
493     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
494     ASSERT_TRUE(resizer.get());
495 
496     resizer->Drag(CalculateDragPoint(*resizer, -3, 3), 0);
497     Fling(std::move(resizer), -3000.f, 3000.f);
498 
499     // Flung downward and to the left, but reaching the bottom edge first.
500     EXPECT_EQ(gfx::Rect(108, 292, 100, 100), test_state()->last_bounds());
501   }
502 
503   {
504     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
505     ASSERT_TRUE(resizer.get());
506 
507     resizer->Drag(CalculateDragPoint(*resizer, -9, 3), 0);
508     Fling(std::move(resizer), -9000.f, 3000.f);
509 
510     // Flung downward and to the left, but reaching the left edge first.
511     EXPECT_EQ(gfx::Rect(8, 264, 100, 100), test_state()->last_bounds());
512   }
513 }
514 
TEST_P(PipWindowResizerTest,PipWindowFlungAvoidsFloatingKeyboard)515 TEST_P(PipWindowResizerTest, PipWindowFlungAvoidsFloatingKeyboard) {
516   PreparePipWindow(gfx::Rect(200, 200, 75, 75));
517 
518   auto* keyboard_controller = keyboard::KeyboardUIController::Get();
519   keyboard_controller->SetContainerType(keyboard::ContainerType::kFloating,
520                                         gfx::Rect(0, 0, 1, 1),
521                                         base::DoNothing());
522   keyboard_controller->ShowKeyboardInDisplay(
523       WindowState::Get(window())->GetDisplay());
524   ASSERT_TRUE(keyboard::WaitUntilShown());
525 
526   aura::Window* keyboard_window = keyboard_controller->GetKeyboardWindow();
527   keyboard_window->SetBounds(gfx::Rect(8, 150, 100, 100));
528 
529   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
530   ASSERT_TRUE(resizer.get());
531 
532   // Fling to the left - but don't intersect with the floating keyboard.
533   resizer->Drag(CalculateDragPoint(*resizer, -10, 0), 0);
534   Fling(std::move(resizer), -4000.f, 0.f);
535 
536   // Appear below the keyboard.
537   EXPECT_EQ(gfx::Rect(8, 258, 75, 75), test_state()->last_bounds());
538 }
539 
TEST_P(PipWindowResizerTest,PipWindowDoesNotChangeDisplayOnDrag)540 TEST_P(PipWindowResizerTest, PipWindowDoesNotChangeDisplayOnDrag) {
541   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
542   const display::Display display = WindowState::Get(window())->GetDisplay();
543   gfx::Rect rect_in_screen = window()->bounds();
544   ::wm::ConvertRectToScreen(window()->parent(), &rect_in_screen);
545   EXPECT_TRUE(display.bounds().Contains(rect_in_screen));
546 
547   // Drag inside the display.
548   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
549   ASSERT_TRUE(resizer.get());
550   resizer->Drag(CalculateDragPoint(*resizer, 10, 10), 0);
551 
552   // Ensure the position is still in the display.
553   EXPECT_EQ(gfx::Rect(210, 210, 100, 100), test_state()->last_bounds());
554   EXPECT_EQ(display.id(), test_state()->last_window_state()->GetDisplay().id());
555   rect_in_screen = window()->bounds();
556   ::wm::ConvertRectToScreen(window()->parent(), &rect_in_screen);
557   EXPECT_TRUE(display.bounds().Contains(rect_in_screen));
558 }
559 
TEST_P(PipWindowResizerTest,PipRestoreBoundsSetOnFling)560 TEST_P(PipWindowResizerTest, PipRestoreBoundsSetOnFling) {
561   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
562 
563   {
564     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
565     ASSERT_TRUE(resizer.get());
566 
567     resizer->Drag(CalculateDragPoint(*resizer, 10, 10), 0);
568     Fling(std::move(resizer), 3000.f, 3000.f);
569   }
570 
571   WindowState* window_state = WindowState::Get(window());
572   EXPECT_TRUE(PipPositioner::HasSnapFraction(window_state));
573 }
574 
TEST_P(PipWindowResizerTest,PipStartAndFinishFreeResizeUmaMetrics)575 TEST_P(PipWindowResizerTest, PipStartAndFinishFreeResizeUmaMetrics) {
576   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
577   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTBOTTOM));
578   ASSERT_TRUE(resizer.get());
579 
580   EXPECT_EQ(1, histograms().GetBucketCount(kAshPipEventsHistogramName,
581                                            Sample(AshPipEvents::FREE_RESIZE)));
582   histograms().ExpectTotalCount(kAshPipEventsHistogramName, 1);
583 
584   resizer->Drag(CalculateDragPoint(*resizer, 100, 0), 0);
585   resizer->CompleteDrag();
586 
587   histograms().ExpectTotalCount(kAshPipEventsHistogramName, 1);
588 }
589 
TEST_P(PipWindowResizerTest,PipFreeResizeAreaUmaMetrics)590 TEST_P(PipWindowResizerTest, PipFreeResizeAreaUmaMetrics) {
591   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
592   std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTBOTTOM));
593   ASSERT_TRUE(resizer.get());
594 
595   EXPECT_EQ(1, histograms().GetBucketCount(
596                    kAshPipFreeResizeInitialAreaHistogramName, Sample(6)));
597   histograms().ExpectTotalCount(kAshPipFreeResizeInitialAreaHistogramName, 1);
598 
599   window()->layer()->SetBounds(gfx::Rect(200, 200, 100, 190));
600   resizer->CompleteDrag();
601 
602   EXPECT_EQ(1, histograms().GetBucketCount(
603                    kAshPipFreeResizeFinishAreaHistogramName, Sample(12)));
604   histograms().ExpectTotalCount(kAshPipFreeResizeFinishAreaHistogramName, 1);
605 }
606 
TEST_P(PipWindowResizerTest,DragDetailsAreDestroyed)607 TEST_P(PipWindowResizerTest, DragDetailsAreDestroyed) {
608   PreparePipWindow(gfx::Rect(200, 200, 100, 100));
609   WindowState* window_state = WindowState::Get(window());
610 
611   {
612     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
613     ASSERT_TRUE(resizer.get());
614 
615     resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
616     EXPECT_NE(nullptr, window_state->drag_details());
617 
618     resizer->CompleteDrag();
619     EXPECT_NE(nullptr, window_state->drag_details());
620   }
621   EXPECT_EQ(nullptr, window_state->drag_details());
622 }
623 
624 // TODO: UpdateDisplay() doesn't support different layouts of multiple displays.
625 // We should add some way to try multiple layouts.
626 INSTANTIATE_TEST_SUITE_P(
627     All,
628     PipWindowResizerTest,
629     testing::Values(std::make_tuple("400x400", 0u),
630                     std::make_tuple("400x400/r", 0u),
631                     std::make_tuple("400x400/u", 0u),
632                     std::make_tuple("400x400/l", 0u),
633                     std::make_tuple("800x800*2", 0u),
634                     std::make_tuple("400x400,400x400", 0u),
635                     std::make_tuple("400x400,400x400", 1u)));
636 
637 using PipWindowResizerNonSquareAspectRatioTest = PipWindowResizerTest;
638 
TEST_P(PipWindowResizerNonSquareAspectRatioTest,PipPositionUmaMetrics)639 TEST_P(PipWindowResizerNonSquareAspectRatioTest, PipPositionUmaMetrics) {
640   histograms().ExpectTotalCount(kAshPipPositionHistogramName, 0);
641 
642   {
643     // Check TOP_LEFT.
644     PreparePipWindow(gfx::Rect(0, 0, 100, 100));
645     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
646     ASSERT_TRUE(resizer.get());
647     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
648     resizer->CompleteDrag();
649 
650     EXPECT_EQ(1, histograms().GetBucketCount(kAshPipPositionHistogramName,
651                                              Sample(AshPipPosition::TOP_LEFT)));
652     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 1);
653   }
654 
655   {
656     // Check TOP_MIDDLE.
657     PreparePipWindow(gfx::Rect(100, 0, 100, 100));
658     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
659     ASSERT_TRUE(resizer.get());
660     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
661     resizer->CompleteDrag();
662 
663     EXPECT_EQ(1,
664               histograms().GetBucketCount(kAshPipPositionHistogramName,
665                                           Sample(AshPipPosition::TOP_MIDDLE)));
666     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 2);
667   }
668 
669   {
670     // Check TOP_RIGHT.
671     PreparePipWindow(gfx::Rect(250, 0, 100, 100));
672     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
673     ASSERT_TRUE(resizer.get());
674     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
675     resizer->CompleteDrag();
676 
677     EXPECT_EQ(1,
678               histograms().GetBucketCount(kAshPipPositionHistogramName,
679                                           Sample(AshPipPosition::TOP_RIGHT)));
680     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 3);
681   }
682 
683   {
684     // Check MIDDLE_LEFT.
685     PreparePipWindow(gfx::Rect(0, 100, 100, 100));
686     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
687     ASSERT_TRUE(resizer.get());
688     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
689     resizer->CompleteDrag();
690 
691     EXPECT_EQ(1,
692               histograms().GetBucketCount(kAshPipPositionHistogramName,
693                                           Sample(AshPipPosition::MIDDLE_LEFT)));
694     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 4);
695   }
696 
697   {
698     // Check MIDDLE.
699     PreparePipWindow(gfx::Rect(100, 100, 100, 100));
700     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
701     ASSERT_TRUE(resizer.get());
702     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
703     resizer->CompleteDrag();
704 
705     EXPECT_EQ(1, histograms().GetBucketCount(kAshPipPositionHistogramName,
706                                              Sample(AshPipPosition::MIDDLE)));
707     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 5);
708   }
709 
710   {
711     // Check MIDDLE_RIGHT.
712     PreparePipWindow(gfx::Rect(250, 100, 100, 100));
713     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
714     ASSERT_TRUE(resizer.get());
715     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
716     resizer->CompleteDrag();
717 
718     EXPECT_EQ(
719         1, histograms().GetBucketCount(kAshPipPositionHistogramName,
720                                        Sample(AshPipPosition::MIDDLE_RIGHT)));
721     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 6);
722   }
723 
724   {
725     // Check BOTTOM_LEFT.
726     PreparePipWindow(gfx::Rect(0, 250, 100, 100));
727     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
728     ASSERT_TRUE(resizer.get());
729     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
730     resizer->CompleteDrag();
731 
732     EXPECT_EQ(1,
733               histograms().GetBucketCount(kAshPipPositionHistogramName,
734                                           Sample(AshPipPosition::BOTTOM_LEFT)));
735     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 7);
736   }
737 
738   {
739     // Check BOTTOM_MIDDLE.
740     PreparePipWindow(gfx::Rect(100, 250, 100, 100));
741     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
742     ASSERT_TRUE(resizer.get());
743     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
744     resizer->CompleteDrag();
745 
746     EXPECT_EQ(
747         1, histograms().GetBucketCount(kAshPipPositionHistogramName,
748                                        Sample(AshPipPosition::BOTTOM_MIDDLE)));
749     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 8);
750   }
751 
752   {
753     // Check BOTTOM_RIGHT.
754     PreparePipWindow(gfx::Rect(250, 250, 100, 100));
755     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
756     ASSERT_TRUE(resizer.get());
757     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
758     resizer->CompleteDrag();
759 
760     EXPECT_EQ(
761         1, histograms().GetBucketCount(kAshPipPositionHistogramName,
762                                        Sample(AshPipPosition::BOTTOM_RIGHT)));
763     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 9);
764   }
765 }
766 
TEST_P(PipWindowResizerNonSquareAspectRatioTest,PipPositionUmaMetricsCornerPriority)767 TEST_P(PipWindowResizerNonSquareAspectRatioTest,
768        PipPositionUmaMetricsCornerPriority) {
769   histograms().ExpectTotalCount(kAshPipPositionHistogramName, 0);
770 
771   // Check corners are priotised over edges and middle.
772   {
773     // Check TOP_LEFT.
774     PreparePipWindow(gfx::Rect(0, 0, 300, 210));
775     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
776     ASSERT_TRUE(resizer.get());
777     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
778     resizer->CompleteDrag();
779 
780     EXPECT_EQ(1, histograms().GetBucketCount(kAshPipPositionHistogramName,
781                                              Sample(AshPipPosition::TOP_LEFT)));
782     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 1);
783   }
784 
785   {
786     // Check TOP_RIGHT.
787     PreparePipWindow(gfx::Rect(100, 0, 300, 210));
788     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
789     ASSERT_TRUE(resizer.get());
790     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
791     resizer->CompleteDrag();
792 
793     EXPECT_EQ(1,
794               histograms().GetBucketCount(kAshPipPositionHistogramName,
795                                           Sample(AshPipPosition::TOP_RIGHT)));
796     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 2);
797   }
798 
799   {
800     // Check BOTTOM_LEFT.
801     PreparePipWindow(gfx::Rect(0, 190, 300, 210));
802     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
803     ASSERT_TRUE(resizer.get());
804     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
805     resizer->CompleteDrag();
806 
807     EXPECT_EQ(1,
808               histograms().GetBucketCount(kAshPipPositionHistogramName,
809                                           Sample(AshPipPosition::BOTTOM_LEFT)));
810     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 3);
811   }
812 
813   {
814     // Check BOTTOM_RIGHT.
815     PreparePipWindow(gfx::Rect(100, 190, 300, 210));
816     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
817     ASSERT_TRUE(resizer.get());
818     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
819     resizer->CompleteDrag();
820 
821     EXPECT_EQ(
822         1, histograms().GetBucketCount(kAshPipPositionHistogramName,
823                                        Sample(AshPipPosition::BOTTOM_RIGHT)));
824     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 4);
825   }
826 }
827 
TEST_P(PipWindowResizerNonSquareAspectRatioTest,PipPositionUmaMetricsEdgePriority)828 TEST_P(PipWindowResizerNonSquareAspectRatioTest,
829        PipPositionUmaMetricsEdgePriority) {
830   histograms().ExpectTotalCount(kAshPipPositionHistogramName, 0);
831 
832   // Test that edges are prioritised over middle.
833   {
834     // Check TOP_MIDDLE.
835     PreparePipWindow(gfx::Rect(100, 0, 200, 220));
836     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
837     ASSERT_TRUE(resizer.get());
838     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
839     resizer->CompleteDrag();
840 
841     EXPECT_EQ(1,
842               histograms().GetBucketCount(kAshPipPositionHistogramName,
843                                           Sample(AshPipPosition::TOP_MIDDLE)));
844     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 1);
845   }
846 
847   {
848     // Check BOTTOM_MIDDLE.
849     PreparePipWindow(gfx::Rect(100, 80, 200, 220));
850     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
851     ASSERT_TRUE(resizer.get());
852     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
853     resizer->CompleteDrag();
854 
855     EXPECT_EQ(
856         1, histograms().GetBucketCount(kAshPipPositionHistogramName,
857                                        Sample(AshPipPosition::BOTTOM_MIDDLE)));
858     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 2);
859   }
860 
861   {
862     // Check MIDDLE_LEFT.
863     PreparePipWindow(gfx::Rect(0, 90, 300, 120));
864     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
865     ASSERT_TRUE(resizer.get());
866     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
867     resizer->CompleteDrag();
868 
869     EXPECT_EQ(1,
870               histograms().GetBucketCount(kAshPipPositionHistogramName,
871                                           Sample(AshPipPosition::MIDDLE_LEFT)));
872     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 3);
873   }
874 
875   {
876     // Check MIDDLE_RIGHT.
877     PreparePipWindow(gfx::Rect(100, 90, 300, 120));
878     std::unique_ptr<PipWindowResizer> resizer(CreateResizerForTest(HTCAPTION));
879     ASSERT_TRUE(resizer.get());
880     resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
881     resizer->CompleteDrag();
882 
883     EXPECT_EQ(
884         1, histograms().GetBucketCount(kAshPipPositionHistogramName,
885                                        Sample(AshPipPosition::MIDDLE_RIGHT)));
886     histograms().ExpectTotalCount(kAshPipPositionHistogramName, 4);
887   }
888 }
889 
890 INSTANTIATE_TEST_SUITE_P(
891     All,
892     PipWindowResizerNonSquareAspectRatioTest,
893     testing::Values(std::make_tuple("400x300", 0u),
894                     std::make_tuple("400x300,4000x3000", 0u),
895                     std::make_tuple("4000x3000,400x300", 1u)));
896 
897 }  // namespace ash
898