1 // Copyright (c) 2012 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.
5 #include "ash/wm/system_gesture_event_filter.h"
7 #include "ash/accelerators/accelerator_controller.h"
8 #include "ash/ash_switches.h"
9 #include "ash/display/display_manager.h"
10 #include "ash/launcher/launcher.h"
11 #include "ash/launcher/launcher_model.h"
12 #include "ash/shell.h"
13 #include "ash/system/brightness_control_delegate.h"
14 #include "ash/system/tray/system_tray_delegate.h"
15 #include "ash/test/ash_test_base.h"
16 #include "ash/test/display_manager_test_api.h"
17 #include "ash/test/shell_test_api.h"
18 #include "ash/test/test_launcher_delegate.h"
19 #include "ash/volume_control_delegate.h"
20 #include "ash/wm/gestures/long_press_affordance_handler.h"
21 #include "ash/wm/window_state.h"
22 #include "ash/wm/window_util.h"
23 #include "base/command_line.h"
24 #include "base/time/time.h"
25 #include "base/timer/timer.h"
26 #include "ui/aura/root_window.h"
27 #include "ui/aura/test/event_generator.h"
28 #include "ui/aura/test/test_windows.h"
29 #include "ui/base/hit_test.h"
30 #include "ui/base/ui_base_switches.h"
31 #include "ui/events/event.h"
32 #include "ui/events/event_utils.h"
33 #include "ui/events/gestures/gesture_configuration.h"
34 #include "ui/gfx/screen.h"
35 #include "ui/gfx/size.h"
36 #include "ui/views/widget/widget_delegate.h"
43 class DelegatePercentTracker {
45 explicit DelegatePercentTracker()
46 : handle_percent_count_(0),
49 int handle_percent_count() const {
50 return handle_percent_count_;
52 double handle_percent() const {
53 return handle_percent_;
55 void SetPercent(double percent) {
56 handle_percent_ = percent;
57 handle_percent_count_++;
61 int handle_percent_count_;
64 DISALLOW_COPY_AND_ASSIGN(DelegatePercentTracker);
67 class DummyVolumeControlDelegate : public VolumeControlDelegate,
68 public DelegatePercentTracker {
70 explicit DummyVolumeControlDelegate() {}
71 virtual ~DummyVolumeControlDelegate() {}
73 virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) OVERRIDE {
76 virtual bool HandleVolumeDown(const ui::Accelerator& accelerator) OVERRIDE {
79 virtual bool HandleVolumeUp(const ui::Accelerator& accelerator) OVERRIDE {
84 DISALLOW_COPY_AND_ASSIGN(DummyVolumeControlDelegate);
87 class DummyBrightnessControlDelegate : public BrightnessControlDelegate,
88 public DelegatePercentTracker {
90 explicit DummyBrightnessControlDelegate() {}
91 virtual ~DummyBrightnessControlDelegate() {}
93 virtual bool HandleBrightnessDown(
94 const ui::Accelerator& accelerator) OVERRIDE { return true; }
95 virtual bool HandleBrightnessUp(
96 const ui::Accelerator& accelerator) OVERRIDE { return true; }
97 virtual void SetBrightnessPercent(double percent, bool gradual) OVERRIDE {
100 virtual void GetBrightnessPercent(
101 const base::Callback<void(double)>& callback) OVERRIDE {
106 DISALLOW_COPY_AND_ASSIGN(DummyBrightnessControlDelegate);
109 class ResizableWidgetDelegate : public views::WidgetDelegateView {
111 ResizableWidgetDelegate() {}
112 virtual ~ResizableWidgetDelegate() {}
115 virtual bool CanResize() const OVERRIDE { return true; }
116 virtual bool CanMaximize() const OVERRIDE { return true; }
117 virtual void DeleteDelegate() OVERRIDE { delete this; }
119 DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate);
122 // Support class for testing windows with a maximum size.
123 class MaxSizeNCFV : public views::NonClientFrameView {
127 virtual gfx::Size GetMaximumSize() OVERRIDE {
128 return gfx::Size(200, 200);
130 virtual gfx::Rect GetBoundsForClientView() const OVERRIDE {
134 virtual gfx::Rect GetWindowBoundsForClientBounds(
135 const gfx::Rect& client_bounds) const OVERRIDE {
139 // This function must ask the ClientView to do a hittest. We don't do this in
140 // the parent NonClientView because that makes it more difficult to calculate
141 // hittests for regions that are partially obscured by the ClientView, e.g.
143 virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
146 virtual void GetWindowMask(const gfx::Size& size,
147 gfx::Path* window_mask) OVERRIDE {}
148 virtual void ResetWindowControls() OVERRIDE {}
149 virtual void UpdateWindowIcon() OVERRIDE {}
150 virtual void UpdateWindowTitle() OVERRIDE {}
152 DISALLOW_COPY_AND_ASSIGN(MaxSizeNCFV);
155 class MaxSizeWidgetDelegate : public views::WidgetDelegateView {
157 MaxSizeWidgetDelegate() {}
158 virtual ~MaxSizeWidgetDelegate() {}
161 virtual bool CanResize() const OVERRIDE { return true; }
162 virtual bool CanMaximize() const OVERRIDE { return false; }
163 virtual void DeleteDelegate() OVERRIDE { delete this; }
164 virtual views::NonClientFrameView* CreateNonClientFrameView(
165 views::Widget* widget) OVERRIDE {
166 return new MaxSizeNCFV;
169 DISALLOW_COPY_AND_ASSIGN(MaxSizeWidgetDelegate);
174 class SystemGestureEventFilterTest
175 : public AshTestBase,
176 public testing::WithParamInterface<bool> {
178 SystemGestureEventFilterTest() : AshTestBase(), docked_enabled_(GetParam()) {}
179 virtual ~SystemGestureEventFilterTest() {}
181 internal::LongPressAffordanceHandler* GetLongPressAffordance() {
182 ShellTestApi shell_test(Shell::GetInstance());
183 return shell_test.system_gesture_event_filter()->
184 long_press_affordance_.get();
187 base::OneShotTimer<internal::LongPressAffordanceHandler>*
188 GetLongPressAffordanceTimer() {
189 return &GetLongPressAffordance()->timer_;
192 int GetLongPressAffordanceTouchId() {
193 return GetLongPressAffordance()->tap_down_touch_id_;
196 views::View* GetLongPressAffordanceView() {
197 return reinterpret_cast<views::View*>(
198 GetLongPressAffordance()->view_.get());
201 // Overridden from AshTestBase:
202 virtual void SetUp() OVERRIDE {
203 CommandLine::ForCurrentProcess()->AppendSwitch(
204 ash::switches::kAshEnableAdvancedGestures);
205 if (docked_enabled_) {
206 CommandLine::ForCurrentProcess()->AppendSwitch(
207 ash::switches::kAshEnableDockedWindows);
209 test::AshTestBase::SetUp();
210 // Enable brightness key.
211 test::DisplayManagerTestApi(Shell::GetInstance()->display_manager()).
212 SetFirstDisplayAsInternalDisplay();
216 // true if docked windows are enabled with a flag.
217 bool docked_enabled_;
219 DISALLOW_COPY_AND_ASSIGN(SystemGestureEventFilterTest);
222 ui::GestureEvent* CreateGesture(ui::EventType type,
228 return new ui::GestureEvent(type, x, y, 0,
229 base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000),
230 ui::GestureEventDetails(type, delta_x, delta_y), 1 << touch_id);
233 void MoveToDeviceControlBezelStartPosition(
234 aura::RootWindow* root_window,
235 DelegatePercentTracker* delegate,
236 double expected_value,
241 // Get a target for kTouchId
242 ui::TouchEvent press1(ui::ET_TOUCH_PRESSED,
243 gfx::Point(-10, ypos + ypos_half),
245 ui::EventTimeForNow());
246 root_window->GetDispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(
249 // There is a noise filter which will require several calls before it
250 // allows the touch event through.
251 int initial_count = delegate->handle_percent_count();
253 // Position the initial touch down slightly underneath the position of
254 // interest to avoid a conflict with the noise filter.
255 scoped_ptr<ui::GestureEvent> event1(CreateGesture(
256 ui::ET_GESTURE_SCROLL_BEGIN, xpos, ypos + ypos_half - 10,
258 bool consumed = root_window->DispatchGestureEvent(event1.get());
260 EXPECT_TRUE(consumed);
261 EXPECT_EQ(initial_count, delegate->handle_percent_count());
263 // No move at the beginning will produce no events.
264 scoped_ptr<ui::GestureEvent> event2(CreateGesture(
265 ui::ET_GESTURE_SCROLL_UPDATE,
266 xpos, ypos + ypos_half - 10, 0, 0, touch_id));
267 consumed = root_window->DispatchGestureEvent(event2.get());
269 EXPECT_TRUE(consumed);
270 EXPECT_EQ(initial_count, delegate->handle_percent_count());
272 // A move to a new Y location will produce an event.
273 scoped_ptr<ui::GestureEvent> event3(CreateGesture(
274 ui::ET_GESTURE_SCROLL_UPDATE, xpos, ypos + ypos_half,
277 int count = initial_count;
278 int loop_counter = 0;
279 while (count == initial_count && loop_counter++ < 100) {
280 EXPECT_TRUE(root_window->DispatchGestureEvent(event3.get()));
281 count = delegate->handle_percent_count();
283 EXPECT_TRUE(loop_counter && loop_counter < 100);
284 EXPECT_EQ(initial_count + 1, count);
285 EXPECT_EQ(expected_value, delegate->handle_percent());
288 TEST_P(SystemGestureEventFilterTest, LongPressAffordanceStateOnCaptureLoss) {
289 aura::Window* root_window = Shell::GetPrimaryRootWindow();
291 aura::test::TestWindowDelegate delegate;
292 scoped_ptr<aura::Window> window0(
293 aura::test::CreateTestWindowWithDelegate(
294 &delegate, 9, gfx::Rect(0, 0, 100, 100), root_window));
295 scoped_ptr<aura::Window> window1(
296 aura::test::CreateTestWindowWithDelegate(
297 &delegate, 10, gfx::Rect(0, 0, 100, 50), window0.get()));
298 scoped_ptr<aura::Window> window2(
299 aura::test::CreateTestWindowWithDelegate(
300 &delegate, 11, gfx::Rect(0, 50, 100, 50), window0.get()));
302 const int kTouchId = 5;
304 // Capture first window.
305 window1->SetCapture();
306 EXPECT_TRUE(window1->HasCapture());
308 // Send touch event to first window.
309 ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
312 ui::EventTimeForNow());
313 root_window->GetDispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(
315 EXPECT_TRUE(window1->HasCapture());
317 base::OneShotTimer<internal::LongPressAffordanceHandler>* timer =
318 GetLongPressAffordanceTimer();
319 EXPECT_TRUE(timer->IsRunning());
320 EXPECT_EQ(kTouchId, GetLongPressAffordanceTouchId());
322 // Force timeout so that the affordance animation can start.
323 timer->user_task().Run();
325 EXPECT_TRUE(GetLongPressAffordance()->is_animating());
328 window2->SetCapture();
329 EXPECT_TRUE(window2->HasCapture());
331 EXPECT_TRUE(GetLongPressAffordance()->is_animating());
332 EXPECT_EQ(kTouchId, GetLongPressAffordanceTouchId());
334 // Animate to completion.
335 GetLongPressAffordance()->End(); // end grow animation.
336 // Force timeout to start shrink animation.
337 EXPECT_TRUE(timer->IsRunning());
338 timer->user_task().Run();
340 EXPECT_TRUE(GetLongPressAffordance()->is_animating());
341 GetLongPressAffordance()->End(); // end shrink animation.
343 // Check if state has reset.
344 EXPECT_EQ(-1, GetLongPressAffordanceTouchId());
345 EXPECT_EQ(NULL, GetLongPressAffordanceView());
348 TEST_P(SystemGestureEventFilterTest, MultiFingerSwipeGestures) {
349 aura::Window* root_window = Shell::GetPrimaryRootWindow();
350 views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
351 new ResizableWidgetDelegate, root_window, gfx::Rect(0, 0, 600, 600));
354 const int kSteps = 15;
355 const int kTouchPoints = 4;
356 gfx::Point points[kTouchPoints] = {
357 gfx::Point(250, 250),
358 gfx::Point(250, 350),
359 gfx::Point(350, 250),
363 aura::test::EventGenerator generator(root_window,
364 toplevel->GetNativeWindow());
366 // Swipe down to minimize.
367 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
369 wm::WindowState* toplevel_state =
370 wm::GetWindowState(toplevel->GetNativeWindow());
371 EXPECT_TRUE(toplevel_state->IsMinimized());
375 // Swipe up to maximize.
376 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
377 EXPECT_TRUE(toplevel_state->IsMaximized());
381 // Swipe right to snap.
382 gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
383 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
384 gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
385 EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
387 // Swipe left to snap.
388 gfx::Point left_points[kTouchPoints];
389 for (int i = 0; i < kTouchPoints; ++i) {
390 left_points[i] = points[i];
391 left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
393 generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
395 gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
396 EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
397 EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
399 // Swipe right again.
400 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
401 gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
402 EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
403 EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
406 TEST_P(SystemGestureEventFilterTest, TwoFingerDrag) {
407 gfx::Rect bounds(0, 0, 600, 600);
408 aura::Window* root_window = Shell::GetPrimaryRootWindow();
409 views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
410 new ResizableWidgetDelegate, root_window, bounds);
413 const int kSteps = 15;
414 const int kTouchPoints = 2;
415 gfx::Point points[kTouchPoints] = {
416 gfx::Point(250, 250),
417 gfx::Point(350, 350),
420 aura::test::EventGenerator generator(root_window,
421 toplevel->GetNativeWindow());
423 wm::WindowState* toplevel_state =
424 wm::GetWindowState(toplevel->GetNativeWindow());
425 // Swipe down to minimize.
426 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
427 EXPECT_TRUE(toplevel_state->IsMinimized());
430 toplevel->GetNativeWindow()->SetBounds(bounds);
432 // Swipe up to maximize.
433 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
434 EXPECT_TRUE(toplevel_state->IsMaximized());
437 toplevel->GetNativeWindow()->SetBounds(bounds);
439 // Swipe right to snap.
440 gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
441 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
442 gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
443 EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
445 // Swipe left to snap.
446 gfx::Point left_points[kTouchPoints];
447 for (int i = 0; i < kTouchPoints; ++i) {
448 left_points[i] = points[i];
449 left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
451 generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
453 gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
454 EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
455 EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
457 // Swipe right again.
458 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
459 gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
460 EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
461 EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
464 TEST_P(SystemGestureEventFilterTest, TwoFingerDragTwoWindows) {
465 aura::Window* root_window = Shell::GetPrimaryRootWindow();
466 ui::GestureConfiguration::set_max_separation_for_gesture_touches_in_pixels(0);
467 views::Widget* first = views::Widget::CreateWindowWithContextAndBounds(
468 new ResizableWidgetDelegate, root_window, gfx::Rect(0, 0, 50, 100));
470 views::Widget* second = views::Widget::CreateWindowWithContextAndBounds(
471 new ResizableWidgetDelegate, root_window, gfx::Rect(100, 0, 100, 100));
474 // Start a two-finger drag on |first|, and then try to use another two-finger
475 // drag to move |second|. The attempt to move |second| should fail.
476 const gfx::Rect& first_bounds = first->GetWindowBoundsInScreen();
477 const gfx::Rect& second_bounds = second->GetWindowBoundsInScreen();
478 const int kSteps = 15;
479 const int kTouchPoints = 4;
480 gfx::Point points[kTouchPoints] = {
481 first_bounds.origin() + gfx::Vector2d(5, 5),
482 first_bounds.origin() + gfx::Vector2d(30, 10),
483 second_bounds.origin() + gfx::Vector2d(5, 5),
484 second_bounds.origin() + gfx::Vector2d(40, 20)
487 aura::test::EventGenerator generator(root_window);
488 // Do not drag too fast to avoid fling.
489 generator.GestureMultiFingerScroll(kTouchPoints, points,
492 EXPECT_NE(first_bounds.ToString(),
493 first->GetWindowBoundsInScreen().ToString());
494 EXPECT_EQ(second_bounds.ToString(),
495 second->GetWindowBoundsInScreen().ToString());
498 TEST_P(SystemGestureEventFilterTest, WindowsWithMaxSizeDontSnap) {
499 gfx::Rect bounds(150, 150, 100, 100);
500 aura::Window* root_window = Shell::GetPrimaryRootWindow();
501 views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
502 new MaxSizeWidgetDelegate, root_window, bounds);
505 const int kSteps = 15;
506 const int kTouchPoints = 2;
507 gfx::Point points[kTouchPoints] = {
508 gfx::Point(150+10, 150+30),
509 gfx::Point(150+30, 150+20),
512 aura::test::EventGenerator generator(root_window,
513 toplevel->GetNativeWindow());
515 // Swipe down to minimize.
516 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
517 wm::WindowState* toplevel_state =
518 wm::GetWindowState(toplevel->GetNativeWindow());
519 EXPECT_TRUE(toplevel_state->IsMinimized());
522 toplevel->GetNativeWindow()->SetBounds(bounds);
524 // Check that swiping up doesn't maximize.
525 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
526 EXPECT_FALSE(toplevel_state->IsMaximized());
529 toplevel->GetNativeWindow()->SetBounds(bounds);
531 // Check that swiping right doesn't snap.
532 gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
533 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
534 normal_bounds.set_x(normal_bounds.x() + 150);
535 EXPECT_EQ(normal_bounds.ToString(),
536 toplevel->GetWindowBoundsInScreen().ToString());
538 toplevel->GetNativeWindow()->SetBounds(bounds);
540 // Check that swiping left doesn't snap.
541 normal_bounds = toplevel->GetWindowBoundsInScreen();
542 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, -150, 0);
543 normal_bounds.set_x(normal_bounds.x() - 150);
544 EXPECT_EQ(normal_bounds.ToString(),
545 toplevel->GetWindowBoundsInScreen().ToString());
547 toplevel->GetNativeWindow()->SetBounds(bounds);
549 // Swipe right again, make sure the window still doesn't snap.
550 normal_bounds = toplevel->GetWindowBoundsInScreen();
551 normal_bounds.set_x(normal_bounds.x() + 150);
552 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
553 EXPECT_EQ(normal_bounds.ToString(),
554 toplevel->GetWindowBoundsInScreen().ToString());
557 TEST_P(SystemGestureEventFilterTest, TwoFingerDragEdge) {
558 gfx::Rect bounds(0, 0, 100, 100);
559 aura::Window* root_window = Shell::GetPrimaryRootWindow();
560 views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
561 new ResizableWidgetDelegate, root_window, bounds);
564 const int kSteps = 15;
565 const int kTouchPoints = 2;
566 gfx::Point points[kTouchPoints] = {
567 gfx::Point(30, 20), // Caption
568 gfx::Point(0, 40), // Left edge
571 EXPECT_EQ(HTLEFT, toplevel->GetNativeWindow()->delegate()->
572 GetNonClientComponent(points[1]));
574 aura::test::EventGenerator generator(root_window,
575 toplevel->GetNativeWindow());
577 bounds = toplevel->GetNativeWindow()->bounds();
578 // Swipe down. Nothing should happen.
579 generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
580 EXPECT_EQ(bounds.ToString(),
581 toplevel->GetNativeWindow()->bounds().ToString());
584 TEST_P(SystemGestureEventFilterTest, TwoFingerDragDelayed) {
585 gfx::Rect bounds(0, 0, 100, 100);
586 aura::Window* root_window = Shell::GetPrimaryRootWindow();
587 views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
588 new ResizableWidgetDelegate, root_window, bounds);
591 const int kSteps = 15;
592 const int kTouchPoints = 2;
593 gfx::Point points[kTouchPoints] = {
594 gfx::Point(30, 20), // Caption
595 gfx::Point(34, 20), // Caption
597 int delays[kTouchPoints] = {0, 120};
599 EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
600 GetNonClientComponent(points[0]));
601 EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
602 GetNonClientComponent(points[1]));
604 aura::test::EventGenerator generator(root_window,
605 toplevel->GetNativeWindow());
607 bounds = toplevel->GetNativeWindow()->bounds();
608 // Swipe right and down starting with one finger.
609 // Add another finger after 120ms and continue dragging.
610 // The window should move and the drag should be determined by the center
611 // point between the fingers.
612 generator.GestureMultiFingerScrollWithDelays(
613 kTouchPoints, points, delays, 15, kSteps, 150, 150);
614 bounds += gfx::Vector2d(150 + (points[1].x() - points[0].x()) / 2, 150);
615 EXPECT_EQ(bounds.ToString(),
616 toplevel->GetNativeWindow()->bounds().ToString());
619 TEST_P(SystemGestureEventFilterTest, ThreeFingerGestureStopsDrag) {
620 gfx::Rect bounds(0, 0, 100, 100);
621 aura::Window* root_window = Shell::GetPrimaryRootWindow();
622 views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
623 new ResizableWidgetDelegate, root_window, bounds);
626 const int kSteps = 10;
627 const int kTouchPoints = 3;
628 gfx::Point points[kTouchPoints] = {
629 gfx::Point(30, 20), // Caption
630 gfx::Point(34, 20), // Caption
631 gfx::Point(38, 20), // Caption
633 int delays[kTouchPoints] = {0, 0, 120};
635 EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
636 GetNonClientComponent(points[0]));
637 EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
638 GetNonClientComponent(points[1]));
640 aura::test::EventGenerator generator(root_window,
641 toplevel->GetNativeWindow());
643 bounds = toplevel->GetNativeWindow()->bounds();
644 // Swipe right and down starting with two fingers.
645 // Add third finger after 120ms and continue dragging.
646 // The window should start moving but stop when the 3rd finger touches down.
647 const int kEventSeparation = 15;
648 generator.GestureMultiFingerScrollWithDelays(
649 kTouchPoints, points, delays, kEventSeparation, kSteps, 150, 150);
650 int expected_drag = 150 / kSteps * 120 / kEventSeparation;
651 bounds += gfx::Vector2d(expected_drag, expected_drag);
652 EXPECT_EQ(bounds.ToString(),
653 toplevel->GetNativeWindow()->bounds().ToString());
656 // Tests run twice - with docked windows disabled or enabled.
657 INSTANTIATE_TEST_CASE_P(DockedWindowsDisabledOrEnabled,
658 SystemGestureEventFilterTest,