1 // Copyright 2019 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/collision_detection/collision_detection_utils.h"
6 
7 #include <memory>
8 
9 #include "ash/keyboard/ui/keyboard_ui_controller.h"
10 #include "ash/keyboard/ui/test/keyboard_test_util.h"
11 #include "ash/public/cpp/keyboard/keyboard_switches.h"
12 #include "ash/root_window_controller.h"
13 #include "ash/shelf/shelf.h"
14 #include "ash/shell.h"
15 #include "ash/system/unified/unified_system_tray.h"
16 #include "ash/test/ash_test_base.h"
17 #include "ash/wm/pip/pip_test_utils.h"
18 #include "ash/wm/window_state.h"
19 #include "ash/wm/wm_event.h"
20 #include "base/callback_helpers.h"
21 #include "ui/aura/window.h"
22 #include "ui/display/scoped_display_for_new_windows.h"
23 #include "ui/gfx/geometry/insets.h"
24 #include "ui/wm/core/coordinate_conversion.h"
25 
26 namespace ash {
27 
28 namespace {
29 
GetDisplayForWindow(aura::Window * window)30 display::Display GetDisplayForWindow(aura::Window* window) {
31   return display::Screen::GetScreen()->GetDisplayNearestWindow(window);
32 }
33 
ConvertToScreenForWindow(aura::Window * window,const gfx::Rect & bounds)34 gfx::Rect ConvertToScreenForWindow(aura::Window* window,
35                                    const gfx::Rect& bounds) {
36   gfx::Rect new_bounds = bounds;
37   ::wm::ConvertRectToScreen(window->GetRootWindow(), &new_bounds);
38   return new_bounds;
39 }
40 
ConvertPrimaryToScreen(const gfx::Rect & bounds)41 gfx::Rect ConvertPrimaryToScreen(const gfx::Rect& bounds) {
42   return ConvertToScreenForWindow(Shell::GetPrimaryRootWindow(), bounds);
43 }
44 
45 }  // namespace
46 
47 using CollisionDetectionUtilsTest = AshTestBase;
48 
TEST_F(CollisionDetectionUtilsTest,RestingPositionSnapsInDisplayWithLargeAspectRatio)49 TEST_F(CollisionDetectionUtilsTest,
50        RestingPositionSnapsInDisplayWithLargeAspectRatio) {
51   UpdateDisplay("1600x400");
52 
53   // Snap to the top edge instead of the far left edge.
54   EXPECT_EQ(ConvertPrimaryToScreen(gfx::Rect(500, 8, 100, 100)),
55             CollisionDetectionUtils::GetRestingPosition(
56                 GetPrimaryDisplay(),
57                 ConvertPrimaryToScreen(gfx::Rect(500, 100, 100, 100)),
58                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
59 }
60 
TEST_F(CollisionDetectionUtilsTest,AvoidObstaclesAvoidsUnifiedSystemTray)61 TEST_F(CollisionDetectionUtilsTest, AvoidObstaclesAvoidsUnifiedSystemTray) {
62   UpdateDisplay("1000x1000");
63   auto* unified_system_tray = GetPrimaryUnifiedSystemTray();
64   unified_system_tray->ShowBubble(/*show_by_click=*/false);
65 
66   auto display = GetPrimaryDisplay();
67   gfx::Rect area = CollisionDetectionUtils::GetMovementArea(display);
68   gfx::Rect bubble_bounds = unified_system_tray->GetBubbleBoundsInScreen();
69   gfx::Rect bounds = gfx::Rect(bubble_bounds.x(), bubble_bounds.y(), 100, 100);
70   gfx::Rect moved_bounds = CollisionDetectionUtils::GetRestingPosition(
71       display, bounds,
72       CollisionDetectionUtils::RelativePriority::kPictureInPicture);
73 
74   // Expect that the returned bounds don't intersect the unified system tray
75   // but also don't leave the PIP movement area.
76   EXPECT_FALSE(moved_bounds.Intersects(bubble_bounds));
77   EXPECT_TRUE(area.Contains(moved_bounds));
78 }
79 
80 class CollisionDetectionUtilsDisplayTest
81     : public AshTestBase,
82       public ::testing::WithParamInterface<
83           std::tuple<std::string, std::size_t>> {
84  public:
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     root_window_ = Shell::GetAllRootWindows()[root_window_index];
94     scoped_display_ =
95         std::make_unique<display::ScopedDisplayForNewWindows>(root_window_);
96     for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
97       auto* shelf = root_window_controller->shelf();
98       shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlwaysHidden);
99     }
100   }
101 
TearDown()102   void TearDown() override {
103     scoped_display_.reset();
104     AshTestBase::TearDown();
105   }
106 
107  protected:
GetDisplay()108   display::Display GetDisplay() { return GetDisplayForWindow(root_window_); }
109 
root_window()110   aura::Window* root_window() { return root_window_; }
111 
ConvertToScreen(const gfx::Rect & bounds)112   gfx::Rect ConvertToScreen(const gfx::Rect& bounds) {
113     return ConvertToScreenForWindow(root_window_, bounds);
114   }
115 
CallAvoidObstacles(const display::Display & display,gfx::Rect bounds,CollisionDetectionUtils::RelativePriority priority=CollisionDetectionUtils::RelativePriority::kPictureInPicture)116   gfx::Rect CallAvoidObstacles(
117       const display::Display& display,
118       gfx::Rect bounds,
119       CollisionDetectionUtils::RelativePriority priority =
120           CollisionDetectionUtils::RelativePriority::kPictureInPicture) {
121     return CollisionDetectionUtils::AvoidObstacles(display, bounds, priority);
122   }
123 
UpdateWorkArea(const std::string & bounds)124   void UpdateWorkArea(const std::string& bounds) {
125     UpdateDisplay(bounds);
126     for (aura::Window* root : Shell::GetAllRootWindows())
127       Shell::Get()->SetDisplayWorkAreaInsets(root, gfx::Insets());
128   }
129 
130  private:
131   std::unique_ptr<display::ScopedDisplayForNewWindows> scoped_display_;
132   aura::Window* root_window_;
133 };
134 
TEST_P(CollisionDetectionUtilsDisplayTest,MovementAreaIsInset)135 TEST_P(CollisionDetectionUtilsDisplayTest, MovementAreaIsInset) {
136   gfx::Rect area = CollisionDetectionUtils::GetMovementArea(GetDisplay());
137   EXPECT_EQ(ConvertToScreen(gfx::Rect(8, 8, 384, 384)), area);
138 }
139 
TEST_P(CollisionDetectionUtilsDisplayTest,MovementAreaIncludesKeyboardIfKeyboardIsShown)140 TEST_P(CollisionDetectionUtilsDisplayTest,
141        MovementAreaIncludesKeyboardIfKeyboardIsShown) {
142   auto* keyboard_controller = keyboard::KeyboardUIController::Get();
143   keyboard_controller->ShowKeyboardInDisplay(GetDisplay());
144   ASSERT_TRUE(keyboard::WaitUntilShown());
145   aura::Window* keyboard_window = keyboard_controller->GetKeyboardWindow();
146   keyboard_window->SetBounds(gfx::Rect(0, 300, 400, 100));
147 
148   gfx::Rect area = CollisionDetectionUtils::GetMovementArea(GetDisplay());
149   EXPECT_EQ(ConvertToScreen(gfx::Rect(8, 8, 384, 284)), area);
150 }
151 
TEST_P(CollisionDetectionUtilsDisplayTest,RestingPositionSnapsToClosestEdge)152 TEST_P(CollisionDetectionUtilsDisplayTest, RestingPositionSnapsToClosestEdge) {
153   auto display = GetDisplay();
154 
155   // Snap near top edge to top.
156   EXPECT_EQ(ConvertToScreen(gfx::Rect(100, 8, 100, 100)),
157             CollisionDetectionUtils::GetRestingPosition(
158                 display, ConvertToScreen(gfx::Rect(100, 50, 100, 100)),
159                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
160 
161   // Snap near bottom edge to bottom.
162   EXPECT_EQ(ConvertToScreen(gfx::Rect(100, 292, 100, 100)),
163             CollisionDetectionUtils::GetRestingPosition(
164                 display, ConvertToScreen(gfx::Rect(100, 250, 100, 100)),
165                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
166 
167   // Snap near left edge to left.
168   EXPECT_EQ(ConvertToScreen(gfx::Rect(8, 100, 100, 100)),
169             CollisionDetectionUtils::GetRestingPosition(
170                 display, ConvertToScreen(gfx::Rect(50, 100, 100, 100)),
171                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
172 
173   // Snap near right edge to right.
174   EXPECT_EQ(ConvertToScreen(gfx::Rect(292, 100, 100, 100)),
175             CollisionDetectionUtils::GetRestingPosition(
176                 display, ConvertToScreen(gfx::Rect(250, 100, 100, 100)),
177                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
178 }
179 
TEST_P(CollisionDetectionUtilsDisplayTest,RestingPositionSnapsInsideDisplay)180 TEST_P(CollisionDetectionUtilsDisplayTest, RestingPositionSnapsInsideDisplay) {
181   auto display = GetDisplay();
182 
183   // Snap near top edge outside movement area to top.
184   EXPECT_EQ(ConvertToScreen(gfx::Rect(100, 8, 100, 100)),
185             CollisionDetectionUtils::GetRestingPosition(
186                 display, ConvertToScreen(gfx::Rect(100, -50, 100, 100)),
187                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
188 
189   // Snap near bottom edge outside movement area to bottom.
190   EXPECT_EQ(ConvertToScreen(gfx::Rect(100, 292, 100, 100)),
191             CollisionDetectionUtils::GetRestingPosition(
192                 display, ConvertToScreen(gfx::Rect(100, 450, 100, 100)),
193                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
194 
195   // Snap near left edge outside movement area to left.
196   EXPECT_EQ(ConvertToScreen(gfx::Rect(8, 100, 100, 100)),
197             CollisionDetectionUtils::GetRestingPosition(
198                 display, ConvertToScreen(gfx::Rect(-50, 100, 100, 100)),
199                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
200 
201   // Snap near right edge outside movement area to right.
202   EXPECT_EQ(ConvertToScreen(gfx::Rect(292, 100, 100, 100)),
203             CollisionDetectionUtils::GetRestingPosition(
204                 display, ConvertToScreen(gfx::Rect(450, 100, 100, 100)),
205                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
206 }
207 
TEST_P(CollisionDetectionUtilsDisplayTest,RestingPositionWorksIfKeyboardIsDisabled)208 TEST_P(CollisionDetectionUtilsDisplayTest,
209        RestingPositionWorksIfKeyboardIsDisabled) {
210   SetVirtualKeyboardEnabled(false);
211   auto display = GetDisplay();
212 
213   // Snap near top edge to top.
214   EXPECT_EQ(ConvertToScreen(gfx::Rect(100, 8, 100, 100)),
215             CollisionDetectionUtils::GetRestingPosition(
216                 display, ConvertToScreen(gfx::Rect(100, 50, 100, 100)),
217                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
218 }
219 
TEST_P(CollisionDetectionUtilsDisplayTest,AvoidObstaclesAvoidsFloatingKeyboard)220 TEST_P(CollisionDetectionUtilsDisplayTest,
221        AvoidObstaclesAvoidsFloatingKeyboard) {
222   auto display = GetDisplay();
223 
224   auto* keyboard_controller = keyboard::KeyboardUIController::Get();
225   keyboard_controller->SetContainerType(keyboard::ContainerType::kFloating,
226                                         gfx::Rect(), base::DoNothing());
227   keyboard_controller->ShowKeyboardInDisplay(display);
228   ASSERT_TRUE(keyboard::WaitUntilShown());
229   aura::Window* keyboard_window = keyboard_controller->GetKeyboardWindow();
230   keyboard_window->SetBounds(gfx::Rect(0, 0, 100, 100));
231 
232   gfx::Rect area = CollisionDetectionUtils::GetMovementArea(display);
233   gfx::Rect moved_bounds =
234       CallAvoidObstacles(display, ConvertToScreen(gfx::Rect(8, 8, 100, 100)));
235 
236   // Expect that the returned bounds don't intersect the floating keyboard
237   // but also don't leave the movement area.
238   EXPECT_FALSE(moved_bounds.Intersects(keyboard_window->GetBoundsInScreen()));
239   EXPECT_TRUE(area.Contains(moved_bounds));
240 }
241 
TEST_P(CollisionDetectionUtilsDisplayTest,AvoidObstaclesDoesNotChangeBoundsIfThereIsNoCollision)242 TEST_P(CollisionDetectionUtilsDisplayTest,
243        AvoidObstaclesDoesNotChangeBoundsIfThereIsNoCollision) {
244   auto display = GetDisplay();
245   EXPECT_EQ(ConvertToScreen(gfx::Rect(100, 100, 100, 100)),
246             CallAvoidObstacles(display,
247                                ConvertToScreen(gfx::Rect(100, 100, 100, 100))));
248 }
249 
TEST_P(CollisionDetectionUtilsDisplayTest,AvoidObstaclesMovesForHigherPriorityWindow)250 TEST_P(CollisionDetectionUtilsDisplayTest,
251        AvoidObstaclesMovesForHigherPriorityWindow) {
252   auto display = GetDisplay();
253   aura::Window* accessibility_bubble_container = Shell::GetContainer(
254       root_window(), kShellWindowId_AccessibilityBubbleContainer);
255   std::unique_ptr<aura::Window> prioritized_window = CreateChildWindow(
256       accessibility_bubble_container, gfx::Rect(100, 100, 100, 10), 0);
257 
258   gfx::Rect position_before_collision_detection(100, 100, 100, 100);
259   gfx::Rect position_when_moved_by_collision_detection(100, 118, 100, 100);
260 
261   const struct {
262     CollisionDetectionUtils::RelativePriority window_priority;
263     CollisionDetectionUtils::RelativePriority avoid_obstacles_priority;
264     gfx::Rect expected_position;
265   } kTestCases[] = {
266       // If the fixed window is kDefault, all other windows should move.
267       {CollisionDetectionUtils::RelativePriority::kDefault,
268        CollisionDetectionUtils::RelativePriority::kPictureInPicture,
269        position_when_moved_by_collision_detection},
270       {CollisionDetectionUtils::RelativePriority::kDefault,
271        CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
272        position_when_moved_by_collision_detection},
273       {CollisionDetectionUtils::RelativePriority::kDefault,
274        CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
275        position_when_moved_by_collision_detection},
276       // If the fixed window is PIP, Autoclick or Switch Access should not move.
277       {CollisionDetectionUtils::RelativePriority::kPictureInPicture,
278        CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
279        position_before_collision_detection},
280       {CollisionDetectionUtils::RelativePriority::kPictureInPicture,
281        CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
282        position_before_collision_detection},
283       // The PIP should not move for itself.
284       {CollisionDetectionUtils::RelativePriority::kPictureInPicture,
285        CollisionDetectionUtils::RelativePriority::kPictureInPicture,
286        position_before_collision_detection},
287       // If the fixed window is the Switch Access menu, the PIP should move, but
288       // the Autoclick menu should not.
289       {CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
290        CollisionDetectionUtils::RelativePriority::kPictureInPicture,
291        position_when_moved_by_collision_detection},
292       {CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
293        CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
294        position_before_collision_detection},
295       // The Switch Access menu should not move for itself.
296       {CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
297        CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
298        position_before_collision_detection},
299       // If the fixed window is Automatic Clicks, both the PIP and the the
300       // Switch Access menu should move.
301       {CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
302        CollisionDetectionUtils::RelativePriority::kPictureInPicture,
303        position_when_moved_by_collision_detection},
304       {CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
305        CollisionDetectionUtils::RelativePriority::kSwitchAccessMenu,
306        position_when_moved_by_collision_detection},
307       // Autoclicks menu should not move for itself.
308       {CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
309        CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu,
310        position_before_collision_detection},
311   };
312 
313   for (const auto& test : kTestCases) {
314     CollisionDetectionUtils::MarkWindowPriorityForCollisionDetection(
315         prioritized_window.get(), test.window_priority);
316     EXPECT_EQ(ConvertToScreen(test.expected_position),
317               CallAvoidObstacles(
318                   display, ConvertToScreen(position_before_collision_detection),
319                   test.avoid_obstacles_priority));
320   }
321 }
322 
TEST_P(CollisionDetectionUtilsDisplayTest,GetRestingPositionAvoidsKeyboard)323 TEST_P(CollisionDetectionUtilsDisplayTest, GetRestingPositionAvoidsKeyboard) {
324   auto display = GetDisplay();
325 
326   auto* keyboard_controller = keyboard::KeyboardUIController::Get();
327   keyboard_controller->ShowKeyboardInDisplay(display);
328   ASSERT_TRUE(keyboard::WaitUntilShown());
329   aura::Window* keyboard_window = keyboard_controller->GetKeyboardWindow();
330   keyboard_window->SetBounds(gfx::Rect(0, 300, 400, 100));
331 
332   EXPECT_EQ(ConvertToScreen(gfx::Rect(8, 192, 100, 100)),
333             CollisionDetectionUtils::GetRestingPosition(
334                 display, ConvertToScreen(gfx::Rect(8, 300, 100, 100)),
335                 CollisionDetectionUtils::RelativePriority::kPictureInPicture));
336 }
337 
TEST_P(CollisionDetectionUtilsDisplayTest,AutoHideShownShelfAffectsWindow)338 TEST_P(CollisionDetectionUtilsDisplayTest, AutoHideShownShelfAffectsWindow) {
339   auto* shelf = Shelf::ForWindow(root_window());
340   shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
341   EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
342 
343   auto shelf_bounds = shelf->GetWindow()->GetBoundsInScreen();
344   // Use a smaller window so it is guaranteed to find a free space to move to.
345   auto bounds = CallAvoidObstacles(
346       GetDisplay(), gfx::Rect(shelf_bounds.CenterPoint(), gfx::Size(1, 1)));
347   EXPECT_FALSE(shelf_bounds.Intersects(bounds));
348 }
349 
350 // TODO: UpdateDisplay() doesn't support different layouts of multiple displays.
351 // We should add some way to try multiple layouts.
352 INSTANTIATE_TEST_SUITE_P(
353     /* no prefix */,
354     CollisionDetectionUtilsDisplayTest,
355     testing::Values(std::make_tuple("400x400", 0u),
356                     std::make_tuple("400x400/r", 0u),
357                     std::make_tuple("400x400/u", 0u),
358                     std::make_tuple("400x400/l", 0u),
359                     std::make_tuple("800x800*2", 0u),
360                     std::make_tuple("400x400,400x400", 0u),
361                     std::make_tuple("400x400,400x400", 1u)));
362 
363 class CollisionDetectionUtilsLogicTest : public ::testing::Test {
364  public:
CallAvoidObstaclesInternal(const gfx::Rect & work_area,const std::vector<gfx::Rect> & rects,const gfx::Rect & bounds,CollisionDetectionUtils::RelativePriority priority=CollisionDetectionUtils::RelativePriority::kPictureInPicture)365   gfx::Rect CallAvoidObstaclesInternal(
366       const gfx::Rect& work_area,
367       const std::vector<gfx::Rect>& rects,
368       const gfx::Rect& bounds,
369       CollisionDetectionUtils::RelativePriority priority =
370           CollisionDetectionUtils::RelativePriority::kPictureInPicture) {
371     return CollisionDetectionUtils::AvoidObstaclesInternal(work_area, rects,
372                                                            bounds, priority);
373   }
374 };
375 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesDoesNotMoveBoundsIfThereIsNoIntersection)376 TEST_F(CollisionDetectionUtilsLogicTest,
377        AvoidObstaclesDoesNotMoveBoundsIfThereIsNoIntersection) {
378   const gfx::Rect area(0, 0, 400, 400);
379 
380   // Check no collision with Rect.
381   EXPECT_EQ(gfx::Rect(200, 0, 100, 100),
382             CallAvoidObstaclesInternal(area, {gfx::Rect(0, 0, 100, 100)},
383                                        gfx::Rect(200, 0, 100, 100)));
384 
385   // Check no collision with edges of the work area. Provide an obstacle so
386   // it has something to stick to, to distinguish failure from correctly
387   // not moving the window bounds.
388   // Check corners:
389   EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
390             CallAvoidObstaclesInternal(area, {gfx::Rect(300, 300, 1, 1)},
391                                        gfx::Rect(0, 0, 100, 100)));
392   EXPECT_EQ(gfx::Rect(300, 0, 100, 100),
393             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
394                                        gfx::Rect(300, 0, 100, 100)));
395   EXPECT_EQ(gfx::Rect(0, 300, 100, 100),
396             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
397                                        gfx::Rect(0, 300, 100, 100)));
398   EXPECT_EQ(gfx::Rect(300, 300, 100, 100),
399             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
400                                        gfx::Rect(300, 300, 100, 100)));
401 
402   // Check edges:
403   EXPECT_EQ(gfx::Rect(100, 0, 100, 100),
404             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
405                                        gfx::Rect(100, 0, 100, 100)));
406   EXPECT_EQ(gfx::Rect(0, 100, 100, 100),
407             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
408                                        gfx::Rect(0, 100, 100, 100)));
409   EXPECT_EQ(gfx::Rect(300, 100, 100, 100),
410             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
411                                        gfx::Rect(300, 100, 100, 100)));
412   EXPECT_EQ(gfx::Rect(100, 300, 100, 100),
413             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
414                                        gfx::Rect(100, 300, 100, 100)));
415 }
416 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesOffByOneCases)417 TEST_F(CollisionDetectionUtilsLogicTest, AvoidObstaclesOffByOneCases) {
418   const gfx::Rect area(0, 0, 400, 400);
419 
420   // Test 1x1 window intersecting a 1x1 obstacle.
421   EXPECT_EQ(gfx::Rect(9, 10, 1, 1),
422             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
423                                        gfx::Rect(10, 10, 1, 1)));
424   // Test 1x1 window adjacent to a 1x1 obstacle.
425   EXPECT_EQ(gfx::Rect(9, 10, 1, 1),
426             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
427                                        gfx::Rect(9, 10, 1, 1)));
428   EXPECT_EQ(gfx::Rect(11, 10, 1, 1),
429             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
430                                        gfx::Rect(11, 10, 1, 1)));
431   EXPECT_EQ(gfx::Rect(10, 9, 1, 1),
432             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
433                                        gfx::Rect(10, 9, 1, 1)));
434   EXPECT_EQ(gfx::Rect(10, 11, 1, 1),
435             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
436                                        gfx::Rect(10, 11, 1, 1)));
437   EXPECT_EQ(gfx::Rect(9, 9, 1, 1),
438             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
439                                        gfx::Rect(9, 9, 1, 1)));
440   EXPECT_EQ(gfx::Rect(11, 11, 1, 1),
441             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
442                                        gfx::Rect(11, 11, 1, 1)));
443   EXPECT_EQ(gfx::Rect(11, 9, 1, 1),
444             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
445                                        gfx::Rect(11, 9, 1, 1)));
446   EXPECT_EQ(gfx::Rect(9, 11, 1, 1),
447             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 1, 1)},
448                                        gfx::Rect(9, 11, 1, 1)));
449 
450   // Test 1x1 window intersecting a 2x2 obstacle.
451   EXPECT_EQ(gfx::Rect(9, 10, 1, 1),
452             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
453                                        gfx::Rect(10, 10, 1, 1)));
454   EXPECT_EQ(gfx::Rect(9, 11, 1, 1),
455             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
456                                        gfx::Rect(10, 11, 1, 1)));
457   EXPECT_EQ(gfx::Rect(12, 10, 1, 1),
458             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
459                                        gfx::Rect(11, 10, 1, 1)));
460   EXPECT_EQ(gfx::Rect(12, 11, 1, 1),
461             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
462                                        gfx::Rect(11, 11, 1, 1)));
463 
464   // Test 1x1 window adjacent to a 2x2 obstacle.
465   EXPECT_EQ(gfx::Rect(9, 10, 1, 1),
466             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
467                                        gfx::Rect(9, 10, 1, 1)));
468   EXPECT_EQ(gfx::Rect(9, 11, 1, 1),
469             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
470                                        gfx::Rect(9, 11, 1, 1)));
471   EXPECT_EQ(gfx::Rect(9, 12, 1, 1),
472             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
473                                        gfx::Rect(9, 12, 1, 1)));
474   EXPECT_EQ(gfx::Rect(10, 12, 1, 1),
475             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
476                                        gfx::Rect(10, 12, 1, 1)));
477   EXPECT_EQ(gfx::Rect(11, 12, 1, 1),
478             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
479                                        gfx::Rect(11, 12, 1, 1)));
480   EXPECT_EQ(gfx::Rect(12, 12, 1, 1),
481             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
482                                        gfx::Rect(12, 12, 1, 1)));
483   EXPECT_EQ(gfx::Rect(12, 11, 1, 1),
484             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
485                                        gfx::Rect(12, 11, 1, 1)));
486   EXPECT_EQ(gfx::Rect(12, 10, 1, 1),
487             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
488                                        gfx::Rect(12, 10, 1, 1)));
489   EXPECT_EQ(gfx::Rect(12, 9, 1, 1),
490             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
491                                        gfx::Rect(12, 9, 1, 1)));
492   EXPECT_EQ(gfx::Rect(11, 9, 1, 1),
493             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
494                                        gfx::Rect(11, 9, 1, 1)));
495   EXPECT_EQ(gfx::Rect(10, 9, 1, 1),
496             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
497                                        gfx::Rect(10, 9, 1, 1)));
498   EXPECT_EQ(gfx::Rect(9, 9, 1, 1),
499             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
500                                        gfx::Rect(9, 9, 1, 1)));
501 
502   // Test 2x2 window intersecting a 2x2 obstacle.
503   EXPECT_EQ(gfx::Rect(8, 9, 2, 2),
504             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
505                                        gfx::Rect(9, 9, 2, 2)));
506   EXPECT_EQ(gfx::Rect(12, 9, 2, 2),
507             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
508                                        gfx::Rect(11, 9, 2, 2)));
509   EXPECT_EQ(gfx::Rect(12, 11, 2, 2),
510             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
511                                        gfx::Rect(11, 11, 2, 2)));
512   EXPECT_EQ(gfx::Rect(8, 11, 2, 2),
513             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
514                                        gfx::Rect(9, 11, 2, 2)));
515 
516   // Test 3x3 window intersecting a 2x2 obstacle.
517   EXPECT_EQ(gfx::Rect(7, 8, 3, 3),
518             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
519                                        gfx::Rect(8, 8, 3, 3)));
520   EXPECT_EQ(gfx::Rect(12, 8, 3, 3),
521             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
522                                        gfx::Rect(11, 8, 3, 3)));
523   EXPECT_EQ(gfx::Rect(12, 11, 3, 3),
524             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
525                                        gfx::Rect(11, 11, 3, 3)));
526   EXPECT_EQ(gfx::Rect(7, 11, 3, 3),
527             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
528                                        gfx::Rect(8, 11, 3, 3)));
529 
530   // Test 3x3 window adjacent to a 2x2 obstacle.
531   EXPECT_EQ(gfx::Rect(7, 10, 3, 3),
532             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
533                                        gfx::Rect(7, 10, 3, 3)));
534   EXPECT_EQ(gfx::Rect(12, 10, 3, 3),
535             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
536                                        gfx::Rect(12, 10, 3, 3)));
537   EXPECT_EQ(gfx::Rect(9, 7, 3, 3),
538             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
539                                        gfx::Rect(9, 7, 3, 3)));
540   EXPECT_EQ(gfx::Rect(9, 12, 3, 3),
541             CallAvoidObstaclesInternal(area, {gfx::Rect(10, 10, 2, 2)},
542                                        gfx::Rect(9, 12, 3, 3)));
543 }
544 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesNestedObstacle)545 TEST_F(CollisionDetectionUtilsLogicTest, AvoidObstaclesNestedObstacle) {
546   const gfx::Rect area(0, 0, 400, 400);
547   EXPECT_EQ(gfx::Rect(9, 16, 1, 1),
548             CallAvoidObstaclesInternal(
549                 area, {gfx::Rect(15, 15, 5, 5), gfx::Rect(10, 10, 15, 15)},
550                 gfx::Rect(16, 16, 1, 1)));
551 }
552 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesAvoidsTwoObstacles)553 TEST_F(CollisionDetectionUtilsLogicTest, AvoidObstaclesAvoidsTwoObstacles) {
554   const gfx::Rect area(0, 0, 400, 400);
555   const std::vector<gfx::Rect> obstacles = {gfx::Rect(4, 1, 4, 5),
556                                             gfx::Rect(2, 4, 4, 5)};
557 
558   // Test a 2x2 window in the intersection between the obstacles.
559   EXPECT_EQ(gfx::Rect(2, 2, 2, 2),
560             CallAvoidObstaclesInternal(area, obstacles, gfx::Rect(4, 4, 2, 2)));
561   // Test a 2x2 window in the lower obstacle.
562   EXPECT_EQ(gfx::Rect(0, 7, 2, 2),
563             CallAvoidObstaclesInternal(area, obstacles, gfx::Rect(2, 7, 2, 2)));
564   // Test a 2x2 window in the upper obstacle.
565   EXPECT_EQ(gfx::Rect(2, 1, 2, 2),
566             CallAvoidObstaclesInternal(area, obstacles, gfx::Rect(4, 1, 2, 2)));
567 }
568 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesAvoidsThreeObstacles)569 TEST_F(CollisionDetectionUtilsLogicTest, AvoidObstaclesAvoidsThreeObstacles) {
570   const gfx::Rect area(0, 0, 400, 400);
571   const std::vector<gfx::Rect> obstacles = {
572       gfx::Rect(4, 1, 4, 5), gfx::Rect(2, 4, 4, 5), gfx::Rect(2, 1, 3, 4)};
573 
574   // Test a 2x2 window intersecting the top two obstacles.
575   EXPECT_EQ(gfx::Rect(0, 2, 2, 2),
576             CallAvoidObstaclesInternal(area, obstacles, gfx::Rect(3, 2, 2, 2)));
577   // Test a 2x2 window intersecting all three obstacles.
578   EXPECT_EQ(gfx::Rect(0, 3, 2, 2),
579             CallAvoidObstaclesInternal(area, obstacles, gfx::Rect(3, 3, 2, 2)));
580 }
581 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesDoesNotPositionBoundsOutsideOfWorkArea)582 TEST_F(CollisionDetectionUtilsLogicTest,
583        AvoidObstaclesDoesNotPositionBoundsOutsideOfWorkArea) {
584   // Position the bounds such that moving it the least distance to stop
585   // intersecting |obstacle| would put it outside of |area|. It should go
586   // instead to the position of second least distance, which would be below
587   // |obstacle|.
588   const gfx::Rect area(0, 0, 400, 400);
589   const gfx::Rect obstacle(50, 0, 100, 100);
590   const gfx::Rect bounds(25, 0, 100, 100);
591   EXPECT_EQ(gfx::Rect(25, 100, 100, 100),
592             CallAvoidObstaclesInternal(area, {obstacle}, bounds));
593 }
594 
TEST_F(CollisionDetectionUtilsLogicTest,AvoidObstaclesPositionsBoundsWithLeastDisplacement)595 TEST_F(CollisionDetectionUtilsLogicTest,
596        AvoidObstaclesPositionsBoundsWithLeastDisplacement) {
597   const gfx::Rect area(0, 0, 400, 400);
598   const gfx::Rect obstacle(200, 200, 100, 100);
599 
600   // Intersecting slightly on the left.
601   EXPECT_EQ(gfx::Rect(100, 200, 100, 100),
602             CallAvoidObstaclesInternal(area, {obstacle},
603                                        gfx::Rect(150, 200, 100, 100)));
604 
605   // Intersecting slightly on the right.
606   EXPECT_EQ(gfx::Rect(300, 200, 100, 100),
607             CallAvoidObstaclesInternal(area, {obstacle},
608                                        gfx::Rect(250, 200, 100, 100)));
609 
610   // Intersecting slightly on the bottom.
611   EXPECT_EQ(gfx::Rect(200, 300, 100, 100),
612             CallAvoidObstaclesInternal(area, {obstacle},
613                                        gfx::Rect(200, 250, 100, 100)));
614 
615   // Intersecting slightly on the top.
616   EXPECT_EQ(gfx::Rect(200, 100, 100, 100),
617             CallAvoidObstaclesInternal(area, {obstacle},
618                                        gfx::Rect(200, 150, 100, 100)));
619 }
620 
621 }  // namespace ash
622