system_gesture_event_filter_unittest.cc revision f2477e01787aa58f445919b809d89e252beef54f
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.
4
5#include "ash/wm/system_gesture_event_filter.h"
6
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/shelf/shelf_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 "ash/wm/workspace/snap_sizer.h"
24#include "base/command_line.h"
25#include "base/time/time.h"
26#include "base/timer/timer.h"
27#include "ui/aura/root_window.h"
28#include "ui/aura/test/event_generator.h"
29#include "ui/aura/test/test_windows.h"
30#include "ui/base/hit_test.h"
31#include "ui/base/ui_base_switches.h"
32#include "ui/events/event.h"
33#include "ui/events/event_utils.h"
34#include "ui/events/gestures/gesture_configuration.h"
35#include "ui/gfx/screen.h"
36#include "ui/gfx/size.h"
37#include "ui/views/widget/widget_delegate.h"
38
39namespace ash {
40namespace test {
41
42namespace {
43
44class DelegatePercentTracker {
45 public:
46  explicit DelegatePercentTracker()
47      : handle_percent_count_(0),
48        handle_percent_(0){
49  }
50  int handle_percent_count() const {
51    return handle_percent_count_;
52  }
53  double handle_percent() const {
54    return handle_percent_;
55  }
56  void SetPercent(double percent) {
57    handle_percent_ = percent;
58    handle_percent_count_++;
59  }
60
61 private:
62  int handle_percent_count_;
63  int handle_percent_;
64
65  DISALLOW_COPY_AND_ASSIGN(DelegatePercentTracker);
66};
67
68class DummyVolumeControlDelegate : public VolumeControlDelegate,
69                                   public DelegatePercentTracker {
70 public:
71  explicit DummyVolumeControlDelegate() {}
72  virtual ~DummyVolumeControlDelegate() {}
73
74  virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) OVERRIDE {
75    return true;
76  }
77  virtual bool HandleVolumeDown(const ui::Accelerator& accelerator) OVERRIDE {
78    return true;
79  }
80  virtual bool HandleVolumeUp(const ui::Accelerator& accelerator) OVERRIDE {
81    return true;
82  }
83
84 private:
85  DISALLOW_COPY_AND_ASSIGN(DummyVolumeControlDelegate);
86};
87
88class DummyBrightnessControlDelegate : public BrightnessControlDelegate,
89                                       public DelegatePercentTracker {
90 public:
91  explicit DummyBrightnessControlDelegate() {}
92  virtual ~DummyBrightnessControlDelegate() {}
93
94  virtual bool HandleBrightnessDown(
95      const ui::Accelerator& accelerator) OVERRIDE { return true; }
96  virtual bool HandleBrightnessUp(
97      const ui::Accelerator& accelerator) OVERRIDE { return true; }
98  virtual void SetBrightnessPercent(double percent, bool gradual) OVERRIDE {
99    SetPercent(percent);
100  }
101  virtual void GetBrightnessPercent(
102      const base::Callback<void(double)>& callback) OVERRIDE {
103    callback.Run(100.0);
104  }
105
106 private:
107  DISALLOW_COPY_AND_ASSIGN(DummyBrightnessControlDelegate);
108};
109
110class ResizableWidgetDelegate : public views::WidgetDelegateView {
111 public:
112  ResizableWidgetDelegate() {}
113  virtual ~ResizableWidgetDelegate() {}
114
115 private:
116  virtual bool CanResize() const OVERRIDE { return true; }
117  virtual bool CanMaximize() const OVERRIDE { return true; }
118  virtual void DeleteDelegate() OVERRIDE { delete this; }
119
120  DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate);
121};
122
123// Support class for testing windows with a maximum size.
124class MaxSizeNCFV : public views::NonClientFrameView {
125 public:
126  MaxSizeNCFV() {}
127 private:
128  virtual gfx::Size GetMaximumSize() OVERRIDE {
129    return gfx::Size(200, 200);
130  }
131  virtual gfx::Rect GetBoundsForClientView() const OVERRIDE {
132    return gfx::Rect();
133  };
134
135  virtual gfx::Rect GetWindowBoundsForClientBounds(
136      const gfx::Rect& client_bounds) const OVERRIDE {
137    return gfx::Rect();
138  };
139
140  // This function must ask the ClientView to do a hittest.  We don't do this in
141  // the parent NonClientView because that makes it more difficult to calculate
142  // hittests for regions that are partially obscured by the ClientView, e.g.
143  // HTSYSMENU.
144  virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
145    return HTNOWHERE;
146  }
147  virtual void GetWindowMask(const gfx::Size& size,
148                             gfx::Path* window_mask) OVERRIDE {}
149  virtual void ResetWindowControls() OVERRIDE {}
150  virtual void UpdateWindowIcon() OVERRIDE {}
151  virtual void UpdateWindowTitle() OVERRIDE {}
152
153  DISALLOW_COPY_AND_ASSIGN(MaxSizeNCFV);
154};
155
156class MaxSizeWidgetDelegate : public views::WidgetDelegateView {
157 public:
158  MaxSizeWidgetDelegate() {}
159  virtual ~MaxSizeWidgetDelegate() {}
160
161 private:
162  virtual bool CanResize() const OVERRIDE { return true; }
163  virtual bool CanMaximize() const OVERRIDE { return false; }
164  virtual void DeleteDelegate() OVERRIDE { delete this; }
165  virtual views::NonClientFrameView* CreateNonClientFrameView(
166      views::Widget* widget) OVERRIDE {
167    return new MaxSizeNCFV;
168  }
169
170  DISALLOW_COPY_AND_ASSIGN(MaxSizeWidgetDelegate);
171};
172
173} // namespace
174
175class SystemGestureEventFilterTest
176    : public AshTestBase,
177      public testing::WithParamInterface<bool> {
178 public:
179  SystemGestureEventFilterTest() : AshTestBase(), docked_enabled_(GetParam()) {}
180  virtual ~SystemGestureEventFilterTest() {}
181
182  internal::LongPressAffordanceHandler* GetLongPressAffordance() {
183    ShellTestApi shell_test(Shell::GetInstance());
184    return shell_test.system_gesture_event_filter()->
185        long_press_affordance_.get();
186  }
187
188  base::OneShotTimer<internal::LongPressAffordanceHandler>*
189      GetLongPressAffordanceTimer() {
190    return &GetLongPressAffordance()->timer_;
191  }
192
193  aura::Window* GetLongPressAffordanceTarget() {
194    return GetLongPressAffordance()->tap_down_target_;
195  }
196
197  views::View* GetLongPressAffordanceView() {
198    return reinterpret_cast<views::View*>(
199        GetLongPressAffordance()->view_.get());
200  }
201
202  // Overridden from AshTestBase:
203  virtual void SetUp() OVERRIDE {
204    CommandLine::ForCurrentProcess()->AppendSwitch(
205        ash::switches::kAshEnableAdvancedGestures);
206    if (!docked_enabled_) {
207      CommandLine::ForCurrentProcess()->AppendSwitch(
208          ash::switches::kAshDisableDockedWindows);
209    }
210    test::AshTestBase::SetUp();
211    // Enable brightness key.
212    test::DisplayManagerTestApi(Shell::GetInstance()->display_manager()).
213        SetFirstDisplayAsInternalDisplay();
214  }
215
216 private:
217  // true if docked windows are enabled with a flag.
218  bool docked_enabled_;
219
220  DISALLOW_COPY_AND_ASSIGN(SystemGestureEventFilterTest);
221};
222
223ui::GestureEvent* CreateGesture(ui::EventType type,
224                                    int x,
225                                    int y,
226                                    float delta_x,
227                                    float delta_y,
228                                    int touch_id) {
229  return new ui::GestureEvent(type, x, y, 0,
230      base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000),
231      ui::GestureEventDetails(type, delta_x, delta_y), 1 << touch_id);
232}
233
234TEST_P(SystemGestureEventFilterTest, LongPressAffordanceStateOnCaptureLoss) {
235  aura::Window* root_window = Shell::GetPrimaryRootWindow();
236
237  aura::test::TestWindowDelegate delegate;
238  scoped_ptr<aura::Window> window0(
239      aura::test::CreateTestWindowWithDelegate(
240          &delegate, 9, gfx::Rect(0, 0, 100, 100), root_window));
241  scoped_ptr<aura::Window> window1(
242      aura::test::CreateTestWindowWithDelegate(
243          &delegate, 10, gfx::Rect(0, 0, 100, 50), window0.get()));
244  scoped_ptr<aura::Window> window2(
245      aura::test::CreateTestWindowWithDelegate(
246          &delegate, 11, gfx::Rect(0, 50, 100, 50), window0.get()));
247
248  const int kTouchId = 5;
249
250  // Capture first window.
251  window1->SetCapture();
252  EXPECT_TRUE(window1->HasCapture());
253
254  // Send touch event to first window.
255  ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
256                       gfx::Point(10, 10),
257                       kTouchId,
258                       ui::EventTimeForNow());
259  root_window->GetDispatcher()->AsRootWindowHostDelegate()->OnHostTouchEvent(
260      &press);
261  EXPECT_TRUE(window1->HasCapture());
262
263  base::OneShotTimer<internal::LongPressAffordanceHandler>* timer =
264      GetLongPressAffordanceTimer();
265  EXPECT_TRUE(timer->IsRunning());
266  EXPECT_EQ(window1, GetLongPressAffordanceTarget());
267
268  // Force timeout so that the affordance animation can start.
269  timer->user_task().Run();
270  timer->Stop();
271  EXPECT_TRUE(GetLongPressAffordance()->is_animating());
272
273  // Change capture.
274  window2->SetCapture();
275  EXPECT_TRUE(window2->HasCapture());
276
277  EXPECT_TRUE(GetLongPressAffordance()->is_animating());
278  EXPECT_EQ(window1, GetLongPressAffordanceTarget());
279
280  // Animate to completion.
281  GetLongPressAffordance()->End();  // end grow animation.
282  // Force timeout to start shrink animation.
283  EXPECT_TRUE(timer->IsRunning());
284  timer->user_task().Run();
285  timer->Stop();
286  EXPECT_TRUE(GetLongPressAffordance()->is_animating());
287  GetLongPressAffordance()->End();  // end shrink animation.
288
289  // Check if state has reset.
290  EXPECT_EQ(NULL, GetLongPressAffordanceTarget());
291  EXPECT_EQ(NULL, GetLongPressAffordanceView());
292}
293
294TEST_P(SystemGestureEventFilterTest, MultiFingerSwipeGestures) {
295  aura::Window* root_window = Shell::GetPrimaryRootWindow();
296  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
297      new ResizableWidgetDelegate, root_window, gfx::Rect(0, 0, 600, 600));
298  toplevel->Show();
299
300  const int kSteps = 15;
301  const int kTouchPoints = 4;
302  gfx::Point points[kTouchPoints] = {
303    gfx::Point(250, 250),
304    gfx::Point(250, 350),
305    gfx::Point(350, 250),
306    gfx::Point(350, 350)
307  };
308
309  aura::test::EventGenerator generator(root_window,
310                                       toplevel->GetNativeWindow());
311
312  // Swipe down to minimize.
313  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
314
315  wm::WindowState* toplevel_state =
316      wm::GetWindowState(toplevel->GetNativeWindow());
317  EXPECT_TRUE(toplevel_state->IsMinimized());
318
319  toplevel->Restore();
320
321  // Swipe up to maximize.
322  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
323  EXPECT_TRUE(toplevel_state->IsMaximized());
324
325  toplevel->Restore();
326
327  // Swipe right to snap.
328  gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
329  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
330  gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
331  EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
332
333  // Swipe left to snap.
334  gfx::Point left_points[kTouchPoints];
335  for (int i = 0; i < kTouchPoints; ++i) {
336    left_points[i] = points[i];
337    left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
338  }
339  generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
340      -150, 0);
341  gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
342  EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
343  EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
344
345  // Swipe right again.
346  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
347  gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
348  EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
349  EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
350}
351
352TEST_P(SystemGestureEventFilterTest, TwoFingerDrag) {
353  gfx::Rect bounds(0, 0, 600, 600);
354  aura::Window* root_window = Shell::GetPrimaryRootWindow();
355  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
356      new ResizableWidgetDelegate, root_window, bounds);
357  toplevel->Show();
358
359  const int kSteps = 15;
360  const int kTouchPoints = 2;
361  gfx::Point points[kTouchPoints] = {
362    gfx::Point(250, 250),
363    gfx::Point(350, 350),
364  };
365
366  aura::test::EventGenerator generator(root_window,
367                                       toplevel->GetNativeWindow());
368
369  wm::WindowState* toplevel_state =
370      wm::GetWindowState(toplevel->GetNativeWindow());
371  // Swipe down to minimize.
372  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
373  EXPECT_TRUE(toplevel_state->IsMinimized());
374
375  toplevel->Restore();
376  toplevel->GetNativeWindow()->SetBounds(bounds);
377
378  // Swipe up to maximize.
379  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
380  EXPECT_TRUE(toplevel_state->IsMaximized());
381
382  toplevel->Restore();
383  toplevel->GetNativeWindow()->SetBounds(bounds);
384
385  // Swipe right to snap.
386  gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
387  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
388  gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
389  EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
390
391  // Swipe left to snap.
392  gfx::Point left_points[kTouchPoints];
393  for (int i = 0; i < kTouchPoints; ++i) {
394    left_points[i] = points[i];
395    left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
396  }
397  generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
398      -150, 0);
399  gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
400  EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
401  EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
402
403  // Swipe right again.
404  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
405  gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
406  EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
407  EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
408}
409
410TEST_P(SystemGestureEventFilterTest, TwoFingerDragTwoWindows) {
411  aura::Window* root_window = Shell::GetPrimaryRootWindow();
412  ui::GestureConfiguration::set_max_separation_for_gesture_touches_in_pixels(0);
413  views::Widget* first = views::Widget::CreateWindowWithContextAndBounds(
414      new ResizableWidgetDelegate, root_window, gfx::Rect(0, 0, 50, 100));
415  first->Show();
416  views::Widget* second = views::Widget::CreateWindowWithContextAndBounds(
417      new ResizableWidgetDelegate, root_window, gfx::Rect(100, 0, 100, 100));
418  second->Show();
419
420  // Start a two-finger drag on |first|, and then try to use another two-finger
421  // drag to move |second|. The attempt to move |second| should fail.
422  const gfx::Rect& first_bounds = first->GetWindowBoundsInScreen();
423  const gfx::Rect& second_bounds = second->GetWindowBoundsInScreen();
424  const int kSteps = 15;
425  const int kTouchPoints = 4;
426  gfx::Point points[kTouchPoints] = {
427    first_bounds.origin() + gfx::Vector2d(5, 5),
428    first_bounds.origin() + gfx::Vector2d(30, 10),
429    second_bounds.origin() + gfx::Vector2d(5, 5),
430    second_bounds.origin() + gfx::Vector2d(40, 20)
431  };
432
433  aura::test::EventGenerator generator(root_window);
434  // Do not drag too fast to avoid fling.
435  generator.GestureMultiFingerScroll(kTouchPoints, points,
436      50, kSteps, 0, 150);
437
438  EXPECT_NE(first_bounds.ToString(),
439            first->GetWindowBoundsInScreen().ToString());
440  EXPECT_EQ(second_bounds.ToString(),
441            second->GetWindowBoundsInScreen().ToString());
442}
443
444TEST_P(SystemGestureEventFilterTest, WindowsWithMaxSizeDontSnap) {
445  gfx::Rect bounds(250, 150, 100, 100);
446  aura::Window* root_window = Shell::GetPrimaryRootWindow();
447  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
448      new MaxSizeWidgetDelegate, root_window, bounds);
449  toplevel->Show();
450
451  const int kSteps = 15;
452  const int kTouchPoints = 2;
453  gfx::Point points[kTouchPoints] = {
454    gfx::Point(bounds.x() + 10, bounds.y() + 30),
455    gfx::Point(bounds.x() + 30, bounds.y() + 20),
456  };
457
458  aura::test::EventGenerator generator(root_window,
459                                       toplevel->GetNativeWindow());
460
461  // Swipe down to minimize.
462  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
463  wm::WindowState* toplevel_state =
464      wm::GetWindowState(toplevel->GetNativeWindow());
465  EXPECT_TRUE(toplevel_state->IsMinimized());
466
467  toplevel->Restore();
468  toplevel->GetNativeWindow()->SetBounds(bounds);
469
470  // Check that swiping up doesn't maximize.
471  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
472  EXPECT_FALSE(toplevel_state->IsMaximized());
473
474  toplevel->Restore();
475  toplevel->GetNativeWindow()->SetBounds(bounds);
476
477  // Check that swiping right doesn't snap.
478  gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
479  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
480  normal_bounds.set_x(normal_bounds.x() + 150);
481  EXPECT_EQ(normal_bounds.ToString(),
482      toplevel->GetWindowBoundsInScreen().ToString());
483
484  toplevel->GetNativeWindow()->SetBounds(bounds);
485
486  // Check that swiping left doesn't snap.
487  normal_bounds = toplevel->GetWindowBoundsInScreen();
488  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, -150, 0);
489  normal_bounds.set_x(normal_bounds.x() - 150);
490  EXPECT_EQ(normal_bounds.ToString(),
491      toplevel->GetWindowBoundsInScreen().ToString());
492
493  toplevel->GetNativeWindow()->SetBounds(bounds);
494
495  // Swipe right again, make sure the window still doesn't snap.
496  normal_bounds = toplevel->GetWindowBoundsInScreen();
497  normal_bounds.set_x(normal_bounds.x() + 150);
498  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
499  EXPECT_EQ(normal_bounds.ToString(),
500      toplevel->GetWindowBoundsInScreen().ToString());
501}
502
503TEST_P(SystemGestureEventFilterTest, TwoFingerDragEdge) {
504  gfx::Rect bounds(0, 0, 100, 100);
505  aura::Window* root_window = Shell::GetPrimaryRootWindow();
506  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
507      new ResizableWidgetDelegate, root_window, bounds);
508  toplevel->Show();
509
510  const int kSteps = 15;
511  const int kTouchPoints = 2;
512  gfx::Point points[kTouchPoints] = {
513    gfx::Point(30, 20),  // Caption
514    gfx::Point(0, 40),   // Left edge
515  };
516
517  EXPECT_EQ(HTLEFT, toplevel->GetNativeWindow()->delegate()->
518        GetNonClientComponent(points[1]));
519
520  aura::test::EventGenerator generator(root_window,
521                                       toplevel->GetNativeWindow());
522
523  bounds = toplevel->GetNativeWindow()->bounds();
524  // Swipe down. Nothing should happen.
525  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
526  EXPECT_EQ(bounds.ToString(),
527            toplevel->GetNativeWindow()->bounds().ToString());
528}
529
530TEST_P(SystemGestureEventFilterTest, TwoFingerDragDelayed) {
531  gfx::Rect bounds(0, 0, 100, 100);
532  aura::Window* root_window = Shell::GetPrimaryRootWindow();
533  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
534      new ResizableWidgetDelegate, root_window, bounds);
535  toplevel->Show();
536
537  const int kSteps = 15;
538  const int kTouchPoints = 2;
539  gfx::Point points[kTouchPoints] = {
540    gfx::Point(30, 20),  // Caption
541    gfx::Point(34, 20),  // Caption
542  };
543  int delays[kTouchPoints] = {0, 120};
544
545  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
546        GetNonClientComponent(points[0]));
547  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
548        GetNonClientComponent(points[1]));
549
550  aura::test::EventGenerator generator(root_window,
551                                       toplevel->GetNativeWindow());
552
553  bounds = toplevel->GetNativeWindow()->bounds();
554  // Swipe right and down starting with one finger.
555  // Add another finger after 120ms and continue dragging.
556  // The window should move and the drag should be determined by the center
557  // point between the fingers.
558  generator.GestureMultiFingerScrollWithDelays(
559      kTouchPoints, points, delays, 15, kSteps, 150, 150);
560  bounds += gfx::Vector2d(150 + (points[1].x() - points[0].x()) / 2, 150);
561  EXPECT_EQ(bounds.ToString(),
562            toplevel->GetNativeWindow()->bounds().ToString());
563}
564
565TEST_P(SystemGestureEventFilterTest, ThreeFingerGestureStopsDrag) {
566  gfx::Rect bounds(0, 0, 100, 100);
567  aura::Window* root_window = Shell::GetPrimaryRootWindow();
568  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
569      new ResizableWidgetDelegate, root_window, bounds);
570  toplevel->Show();
571
572  const int kSteps = 10;
573  const int kTouchPoints = 3;
574  gfx::Point points[kTouchPoints] = {
575    gfx::Point(30, 20),  // Caption
576    gfx::Point(34, 20),  // Caption
577    gfx::Point(38, 20),  // Caption
578  };
579  int delays[kTouchPoints] = {0, 0, 120};
580
581  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
582        GetNonClientComponent(points[0]));
583  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
584        GetNonClientComponent(points[1]));
585
586  aura::test::EventGenerator generator(root_window,
587                                       toplevel->GetNativeWindow());
588
589  bounds = toplevel->GetNativeWindow()->bounds();
590  // Swipe right and down starting with two fingers.
591  // Add third finger after 120ms and continue dragging.
592  // The window should start moving but stop when the 3rd finger touches down.
593  const int kEventSeparation = 15;
594  generator.GestureMultiFingerScrollWithDelays(
595      kTouchPoints, points, delays, kEventSeparation, kSteps, 150, 150);
596  int expected_drag = 150 / kSteps * 120 / kEventSeparation;
597  bounds += gfx::Vector2d(expected_drag, expected_drag);
598  EXPECT_EQ(bounds.ToString(),
599            toplevel->GetNativeWindow()->bounds().ToString());
600}
601
602TEST_P(SystemGestureEventFilterTest, DragLeftNearEdgeSnaps) {
603  gfx::Rect bounds(200, 150, 400, 100);
604  aura::Window* root_window = Shell::GetPrimaryRootWindow();
605  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
606      new ResizableWidgetDelegate, root_window, bounds);
607  toplevel->Show();
608
609  const int kSteps = 15;
610  const int kTouchPoints = 2;
611  gfx::Point points[kTouchPoints] = {
612    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
613    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
614  };
615  aura::test::EventGenerator generator(root_window,
616                                       toplevel->GetNativeWindow());
617
618  // Check that dragging left snaps before reaching the screen edge.
619  gfx::Rect work_area =
620      Shell::GetScreen()->GetDisplayNearestWindow(root_window).work_area();
621  int drag_x = work_area.x() + 20 - points[0].x();
622  generator.GestureMultiFingerScroll(
623      kTouchPoints, points, 120, kSteps, drag_x, 0);
624
625  internal::SnapSizer snap_sizer(
626      wm::GetWindowState(toplevel->GetNativeWindow()),
627      gfx::Point(),
628      internal::SnapSizer::LEFT_EDGE,
629      internal::SnapSizer::OTHER_INPUT);
630  gfx::Rect expected_bounds(snap_sizer.target_bounds());
631  EXPECT_EQ(expected_bounds.ToString(),
632            toplevel->GetWindowBoundsInScreen().ToString());
633}
634
635TEST_P(SystemGestureEventFilterTest, DragRightNearEdgeSnaps) {
636  gfx::Rect bounds(200, 150, 400, 100);
637  aura::Window* root_window = Shell::GetPrimaryRootWindow();
638  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
639      new ResizableWidgetDelegate, root_window, bounds);
640  toplevel->Show();
641
642  const int kSteps = 15;
643  const int kTouchPoints = 2;
644  gfx::Point points[kTouchPoints] = {
645    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
646    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
647  };
648  aura::test::EventGenerator generator(root_window,
649                                       toplevel->GetNativeWindow());
650
651  // Check that dragging right snaps before reaching the screen edge.
652  gfx::Rect work_area =
653      Shell::GetScreen()->GetDisplayNearestWindow(root_window).work_area();
654  int drag_x = work_area.right() - 20 - points[0].x();
655  generator.GestureMultiFingerScroll(
656      kTouchPoints, points, 120, kSteps, drag_x, 0);
657  internal::SnapSizer snap_sizer(
658      wm::GetWindowState(toplevel->GetNativeWindow()),
659      gfx::Point(),
660      internal::SnapSizer::RIGHT_EDGE,
661      internal::SnapSizer::OTHER_INPUT);
662  gfx::Rect expected_bounds(snap_sizer.target_bounds());
663  EXPECT_EQ(expected_bounds.ToString(),
664            toplevel->GetWindowBoundsInScreen().ToString());
665}
666
667// Tests run twice - with docked windows disabled or enabled.
668INSTANTIATE_TEST_CASE_P(DockedWindowsDisabledOrEnabled,
669                        SystemGestureEventFilterTest,
670                        testing::Bool());
671
672}  // namespace test
673}  // namespace ash
674