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