1 // Copyright 2014 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 "ui/touch_selection/longpress_drag_selector.h"
6 
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "ui/events/test/motion_event_test_utils.h"
9 
10 using ui::test::MockMotionEvent;
11 
12 namespace ui {
13 namespace {
14 
15 const double kSlop = 10.;
16 
17 }  // namespace
18 
19 class LongPressDragSelectorTest : public testing::Test,
20                                   public LongPressDragSelectorClient {
21  public:
LongPressDragSelectorTest()22   LongPressDragSelectorTest()
23       : dragging_(false), active_state_changed_(false) {}
24 
~LongPressDragSelectorTest()25   ~LongPressDragSelectorTest() override {}
26 
SetSelection(const gfx::PointF & start,const gfx::PointF & end)27   void SetSelection(const gfx::PointF& start, const gfx::PointF& end) {
28     selection_start_ = start;
29     selection_end_ = end;
30   }
31 
GetAndResetActiveStateChanged()32   bool GetAndResetActiveStateChanged() {
33     bool active_state_changed = active_state_changed_;
34     active_state_changed_ = false;
35     return active_state_changed;
36   }
37 
IsDragging() const38   bool IsDragging() const { return dragging_; }
DragPosition() const39   const gfx::PointF& DragPosition() const { return drag_position_; }
40 
41   // LongPressDragSelectorClient implementation.
OnDragBegin(const TouchSelectionDraggable & handler,const gfx::PointF & drag_position)42   void OnDragBegin(const TouchSelectionDraggable& handler,
43                    const gfx::PointF& drag_position) override {
44     dragging_ = true;
45     drag_position_ = drag_position;
46   }
47 
OnDragUpdate(const TouchSelectionDraggable & handler,const gfx::PointF & drag_position)48   void OnDragUpdate(const TouchSelectionDraggable& handler,
49                     const gfx::PointF& drag_position) override {
50     drag_position_ = drag_position;
51   }
52 
OnDragEnd(const TouchSelectionDraggable & handler)53   void OnDragEnd(const TouchSelectionDraggable& handler) override {
54     dragging_ = false;
55   }
56 
IsWithinTapSlop(const gfx::Vector2dF & delta) const57   bool IsWithinTapSlop(const gfx::Vector2dF& delta) const override {
58     return delta.LengthSquared() < (kSlop * kSlop);
59   }
60 
OnLongPressDragActiveStateChanged()61   void OnLongPressDragActiveStateChanged() override {
62     active_state_changed_ = true;
63   }
64 
GetSelectionStart() const65   gfx::PointF GetSelectionStart() const override { return selection_start_; }
66 
GetSelectionEnd() const67   gfx::PointF GetSelectionEnd() const override { return selection_end_; }
68 
69  private:
70   bool dragging_;
71   bool active_state_changed_;
72   gfx::PointF drag_position_;
73 
74   gfx::PointF selection_start_;
75   gfx::PointF selection_end_;
76 };
77 
TEST_F(LongPressDragSelectorTest,BasicDrag)78 TEST_F(LongPressDragSelectorTest, BasicDrag) {
79   LongPressDragSelector selector(this);
80   MockMotionEvent event;
81 
82   // Start a touch sequence.
83   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
84   EXPECT_FALSE(GetAndResetActiveStateChanged());
85 
86   // Activate a longpress-triggered selection.
87   gfx::PointF selection_start(0, 10);
88   gfx::PointF selection_end(10, 10);
89   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
90   EXPECT_FALSE(GetAndResetActiveStateChanged());
91 
92   // Motion should not be consumed until a selection is detected.
93   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
94   SetSelection(selection_start, selection_end);
95   selector.OnSelectionActivated();
96   EXPECT_TRUE(GetAndResetActiveStateChanged());
97   EXPECT_FALSE(IsDragging());
98 
99   // Initiate drag motion.  Note that the first move event after activation is
100   // used to initialize the drag start anchor.
101   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
102   EXPECT_FALSE(IsDragging());
103 
104   // The first slop exceeding motion will start the drag. As the motion is
105   // downward, the end selection point should be moved.
106   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
107   EXPECT_TRUE(IsDragging());
108   EXPECT_EQ(selection_end, DragPosition());
109 
110   // Subsequent motion will extend the selection.
111   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
112   EXPECT_TRUE(IsDragging());
113   EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop), DragPosition());
114   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 3)));
115   EXPECT_TRUE(IsDragging());
116   EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop * 2), DragPosition());
117 
118   // Release the touch sequence, ending the drag. The selector will never
119   // consume the start/end events, only move events after a longpress.
120   EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
121   EXPECT_FALSE(IsDragging());
122   EXPECT_TRUE(GetAndResetActiveStateChanged());
123 }
124 
TEST_F(LongPressDragSelectorTest,BasicReverseDrag)125 TEST_F(LongPressDragSelectorTest, BasicReverseDrag) {
126   LongPressDragSelector selector(this);
127   MockMotionEvent event;
128 
129   // Start a touch sequence.
130   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
131   EXPECT_FALSE(GetAndResetActiveStateChanged());
132 
133   // Activate a longpress-triggered selection.
134   gfx::PointF selection_start(0, 10);
135   gfx::PointF selection_end(10, 10);
136   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
137   EXPECT_FALSE(GetAndResetActiveStateChanged());
138   SetSelection(selection_start, selection_end);
139   selector.OnSelectionActivated();
140   EXPECT_TRUE(GetAndResetActiveStateChanged());
141   EXPECT_FALSE(IsDragging());
142 
143   // Initiate drag motion.
144   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 5, 0)));
145   EXPECT_FALSE(IsDragging());
146 
147   // As the initial motion is leftward, toward the selection start, the
148   // selection start should be the drag point.
149   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, -kSlop, 0)));
150   EXPECT_TRUE(IsDragging());
151   EXPECT_EQ(selection_start, DragPosition());
152 
153   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop)));
154   EXPECT_TRUE(IsDragging());
155   EXPECT_EQ(selection_start + gfx::Vector2dF(kSlop, -kSlop), DragPosition());
156 
157   // Release the touch sequence, ending the drag.
158   EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
159   EXPECT_FALSE(IsDragging());
160   EXPECT_TRUE(GetAndResetActiveStateChanged());
161 }
162 
TEST_F(LongPressDragSelectorTest,NoActiveTouch)163 TEST_F(LongPressDragSelectorTest, NoActiveTouch) {
164   LongPressDragSelector selector(this);
165   MockMotionEvent event;
166 
167   // Activate a longpress-triggered selection.
168   gfx::PointF selection_start(0, 10);
169   gfx::PointF selection_end(10, 10);
170   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
171   SetSelection(selection_start, selection_end);
172   selector.OnSelectionActivated();
173   EXPECT_FALSE(GetAndResetActiveStateChanged());
174   EXPECT_FALSE(IsDragging());
175 
176   // Start a new touch sequence; it shouldn't initiate selection drag as there
177   // was no active touch sequence when the longpress selection started.
178   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
179   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
180   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
181   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
182   EXPECT_FALSE(IsDragging());
183   EXPECT_EQ(gfx::PointF(), DragPosition());
184 }
185 
TEST_F(LongPressDragSelectorTest,NoLongPress)186 TEST_F(LongPressDragSelectorTest, NoLongPress) {
187   LongPressDragSelector selector(this);
188   MockMotionEvent event;
189 
190   // Start a touch sequence.
191   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
192   EXPECT_FALSE(GetAndResetActiveStateChanged());
193 
194   // Activate a selection without a preceding longpress.
195   gfx::PointF selection_start(0, 10);
196   gfx::PointF selection_end(10, 10);
197   SetSelection(selection_start, selection_end);
198   selector.OnSelectionActivated();
199   EXPECT_FALSE(GetAndResetActiveStateChanged());
200   EXPECT_FALSE(IsDragging());
201 
202   // Touch movement should not initiate selection drag.
203   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
204   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
205   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
206   EXPECT_FALSE(IsDragging());
207   EXPECT_EQ(gfx::PointF(), DragPosition());
208 }
209 
TEST_F(LongPressDragSelectorTest,NoValidLongPress)210 TEST_F(LongPressDragSelectorTest, NoValidLongPress) {
211   LongPressDragSelector selector(this);
212   MockMotionEvent event;
213 
214   // Start a touch sequence.
215   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
216   EXPECT_FALSE(GetAndResetActiveStateChanged());
217 
218   gfx::PointF selection_start(0, 10);
219   gfx::PointF selection_end(10, 10);
220   SetSelection(selection_start, selection_end);
221 
222   // Activate a longpress-triggered selection, but at a time before the current
223   // touch down event.
224   selector.OnLongPressEvent(
225       event.GetEventTime() - base::TimeDelta::FromSeconds(1), gfx::PointF());
226   selector.OnSelectionActivated();
227   EXPECT_FALSE(GetAndResetActiveStateChanged());
228   EXPECT_FALSE(IsDragging());
229 
230   // Activate a longpress-triggered selection, but at a place different than the
231   // current touch down event.
232   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF(kSlop, 0));
233   selector.OnSelectionActivated();
234   EXPECT_FALSE(GetAndResetActiveStateChanged());
235   EXPECT_FALSE(IsDragging());
236 
237   // Touch movement should not initiate selection drag.
238   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
239   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
240   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
241   EXPECT_FALSE(IsDragging());
242   EXPECT_EQ(gfx::PointF(), DragPosition());
243 }
244 
TEST_F(LongPressDragSelectorTest,NoSelection)245 TEST_F(LongPressDragSelectorTest, NoSelection) {
246   LongPressDragSelector selector(this);
247   MockMotionEvent event;
248 
249   // Start a touch sequence.
250   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
251   EXPECT_FALSE(GetAndResetActiveStateChanged());
252 
253   // Trigger a longpress.
254   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
255   EXPECT_FALSE(GetAndResetActiveStateChanged());
256   EXPECT_FALSE(IsDragging());
257 
258   // Touch movement should not initiate selection drag, as there is no active
259   // selection.
260   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
261   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
262   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
263   EXPECT_FALSE(IsDragging());
264   EXPECT_EQ(gfx::PointF(), DragPosition());
265 
266   EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
267 }
268 
TEST_F(LongPressDragSelectorTest,NoDragMotion)269 TEST_F(LongPressDragSelectorTest, NoDragMotion) {
270   LongPressDragSelector selector(this);
271   MockMotionEvent event;
272 
273   // Start a touch sequence.
274   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
275   EXPECT_FALSE(GetAndResetActiveStateChanged());
276 
277   // Activate a longpress-triggered selection.
278   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
279   EXPECT_FALSE(GetAndResetActiveStateChanged());
280   gfx::PointF selection_start(0, 10);
281   gfx::PointF selection_end(10, 10);
282   SetSelection(selection_start, selection_end);
283   selector.OnSelectionActivated();
284   EXPECT_TRUE(GetAndResetActiveStateChanged());
285   EXPECT_FALSE(IsDragging());
286 
287   // Touch movement within the slop region should not initiate selection drag.
288   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
289   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop / 2)));
290   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop / 2)));
291   EXPECT_FALSE(IsDragging());
292   EXPECT_EQ(gfx::PointF(), DragPosition());
293 
294   EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
295   EXPECT_TRUE(GetAndResetActiveStateChanged());
296 }
297 
TEST_F(LongPressDragSelectorTest,SelectionDeactivated)298 TEST_F(LongPressDragSelectorTest, SelectionDeactivated) {
299   LongPressDragSelector selector(this);
300   MockMotionEvent event;
301 
302   // Start a touch sequence.
303   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
304   EXPECT_FALSE(GetAndResetActiveStateChanged());
305 
306   // Activate a longpress-triggered selection.
307   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
308   EXPECT_FALSE(GetAndResetActiveStateChanged());
309   gfx::PointF selection_start(0, 10);
310   gfx::PointF selection_end(10, 10);
311   SetSelection(selection_start, selection_end);
312   selector.OnSelectionActivated();
313   EXPECT_TRUE(GetAndResetActiveStateChanged());
314   EXPECT_FALSE(IsDragging());
315 
316   // Start a drag selection.
317   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
318   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
319   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
320   EXPECT_TRUE(IsDragging());
321 
322   // Clearing the selection should force an end to the drag.
323   selector.OnSelectionDeactivated();
324   EXPECT_TRUE(GetAndResetActiveStateChanged());
325   EXPECT_FALSE(IsDragging());
326 
327   // Subsequent motion should not be consumed.
328   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
329   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
330   EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
331   EXPECT_FALSE(IsDragging());
332   EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
333 }
334 
TEST_F(LongPressDragSelectorTest,DragFast)335 TEST_F(LongPressDragSelectorTest, DragFast) {
336   LongPressDragSelector selector(this);
337   MockMotionEvent event;
338 
339   // Start a touch sequence.
340   EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
341   EXPECT_FALSE(GetAndResetActiveStateChanged());
342 
343   // Activate a longpress-triggered selection.
344   gfx::PointF selection_start(0, 10);
345   gfx::PointF selection_end(10, 10);
346   selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
347   EXPECT_FALSE(GetAndResetActiveStateChanged());
348   SetSelection(selection_start, selection_end);
349   selector.OnSelectionActivated();
350   EXPECT_TRUE(GetAndResetActiveStateChanged());
351   EXPECT_FALSE(IsDragging());
352 
353   // Initiate drag motion.
354   EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 15, 5)));
355   EXPECT_FALSE(IsDragging());
356 
357   // As the initial motion exceeds both endpoints, the closer bound should
358   // be used for dragging, in this case the selection end.
359   EXPECT_TRUE(selector.WillHandleTouchEvent(
360       event.MovePoint(0, 15.f + kSlop * 2.f, 5.f + kSlop)));
361   EXPECT_TRUE(IsDragging());
362   EXPECT_EQ(selection_end, DragPosition());
363 
364   // Release the touch sequence, ending the drag.
365   EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
366   EXPECT_FALSE(IsDragging());
367   EXPECT_TRUE(GetAndResetActiveStateChanged());
368 }
369 
TEST_F(LongPressDragSelectorTest,ScrollAfterLongPress)370 TEST_F(LongPressDragSelectorTest, ScrollAfterLongPress) {
371   LongPressDragSelector selector(this);
372   MockMotionEvent event;
373   gfx::PointF touch_point(0, 0);
374 
375   // Start a touch sequence.
376   EXPECT_FALSE(selector.WillHandleTouchEvent(
377       event.PressPoint(touch_point.x(), touch_point.y())));
378 
379   // Long-press and hold down.
380   selector.OnLongPressEvent(event.GetEventTime(), touch_point);
381 
382   // Scroll the page. This should cancel long-press drag gesture.
383   touch_point.Offset(0, 2 * kSlop);
384   EXPECT_FALSE(selector.WillHandleTouchEvent(
385       event.MovePoint(0, touch_point.x(), touch_point.y())));
386   selector.OnScrollBeginEvent();
387 
388   // Now if the selection is activated, because long-press drag gesture was
389   // canceled, active state of the long-press selector should not change.
390   selector.OnSelectionActivated();
391   EXPECT_FALSE(GetAndResetActiveStateChanged());
392 
393   // Release the touch sequence.
394   selector.WillHandleTouchEvent(event.ReleasePoint());
395 }
396 
397 }  // namespace ui
398