1 // Copyright 2017 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/highlighter/highlighter_controller.h"
6 
7 #include <memory>
8 
9 #include "ash/assistant/test/assistant_ash_test_base.h"
10 #include "ash/fast_ink/fast_ink_points.h"
11 #include "ash/highlighter/highlighter_controller_test_api.h"
12 #include "ash/shell.h"
13 #include "ash/system/palette/mock_palette_tool_delegate.h"
14 #include "ash/system/palette/palette_tool.h"
15 #include "ash/system/palette/tools/metalayer_mode.h"
16 #include "base/strings/stringprintf.h"
17 #include "ui/aura/window_tree_host.h"
18 #include "ui/compositor/test/draw_waiter_for_test.h"
19 #include "ui/events/test/event_generator.h"
20 
21 namespace ash {
22 namespace {
23 
24 class TestHighlighterObserver : public HighlighterController::Observer {
25  public:
26   TestHighlighterObserver() = default;
27   ~TestHighlighterObserver() override = default;
28 
29   // HighlighterController::Observer:
OnHighlighterEnabledChanged(HighlighterEnabledState state)30   void OnHighlighterEnabledChanged(HighlighterEnabledState state) override {
31     switch (state) {
32       case HighlighterEnabledState::kEnabled:
33         ++enabled_count_;
34         break;
35       case HighlighterEnabledState::kDisabledByUser:
36         ++disabled_by_user_count_;
37         break;
38       case HighlighterEnabledState::kDisabledBySessionAbort:
39         ++disabled_by_session_abort_;
40         break;
41       case HighlighterEnabledState::kDisabledBySessionComplete:
42         ++disabled_by_session_complete_;
43         break;
44     }
45   }
46 
OnHighlighterSelectionRecognized(const gfx::Rect & rect)47   void OnHighlighterSelectionRecognized(const gfx::Rect& rect) override {
48     last_recognized_rect_ = rect;
49   }
50 
51   int enabled_count_ = 0;
52   int disabled_by_user_count_ = 0;
53   int disabled_by_session_abort_ = 0;
54   int disabled_by_session_complete_ = 0;
55   gfx::Rect last_recognized_rect_;
56 
57  private:
58   DISALLOW_COPY_AND_ASSIGN(TestHighlighterObserver);
59 };
60 
61 class HighlighterControllerTest : public AssistantAshTestBase {
62  public:
63   HighlighterControllerTest() = default;
64   ~HighlighterControllerTest() override = default;
65 
SetUp()66   void SetUp() override {
67     AssistantAshTestBase::SetUp();
68     controller_ = Shell::Get()->highlighter_controller();
69     controller_test_api_ =
70         std::make_unique<HighlighterControllerTestApi>(controller_);
71 
72     palette_tool_delegate_ = std::make_unique<MockPaletteToolDelegate>();
73     tool_ = std::make_unique<MetalayerMode>(palette_tool_delegate_.get());
74   }
75 
TearDown()76   void TearDown() override {
77     tool_.reset();
78     // This needs to be called first to reset the controller state before the
79     // shell instance gets torn down.
80     controller_test_api_.reset();
81     AssistantAshTestBase::TearDown();
82   }
83 
UpdateDisplayAndWaitForCompositingEnded(const std::string & display_specs)84   void UpdateDisplayAndWaitForCompositingEnded(
85       const std::string& display_specs) {
86     UpdateDisplay(display_specs);
87     ui::DrawWaiterForTest::WaitForCompositingEnded(
88         Shell::GetPrimaryRootWindow()->GetHost()->compositor());
89   }
90 
91  protected:
TraceRect(const gfx::Rect & rect)92   void TraceRect(const gfx::Rect& rect) {
93     ui::test::EventGenerator* event_generator = GetEventGenerator();
94     event_generator->MoveTouch(gfx::Point(rect.x(), rect.y()));
95     event_generator->PressTouch();
96     event_generator->MoveTouch(gfx::Point(rect.right(), rect.y()));
97     event_generator->MoveTouch(gfx::Point(rect.right(), rect.bottom()));
98     event_generator->MoveTouch(gfx::Point(rect.x(), rect.bottom()));
99     event_generator->MoveTouch(gfx::Point(rect.x(), rect.y()));
100     event_generator->ReleaseTouch();
101 
102     // The the events above will trigger a frame, so wait until a new
103     // CompositorFrame is generated before terminating.
104     ui::DrawWaiterForTest::WaitForCompositingEnded(
105         Shell::GetPrimaryRootWindow()->GetHost()->compositor());
106   }
107 
108   std::unique_ptr<HighlighterControllerTestApi> controller_test_api_;
109   std::unique_ptr<MockPaletteToolDelegate> palette_tool_delegate_;
110   std::unique_ptr<PaletteTool> tool_;
111 
112   HighlighterController* controller_ = nullptr;  // Not owned.
113 
114  private:
115   DISALLOW_COPY_AND_ASSIGN(HighlighterControllerTest);
116 };
117 
118 }  // namespace
119 
120 // Test to ensure the class responsible for drawing the highlighter pointer
121 // receives points from stylus movements as expected.
TEST_F(HighlighterControllerTest,HighlighterRenderer)122 TEST_F(HighlighterControllerTest, HighlighterRenderer) {
123   // The highlighter pointer mode only works with stylus.
124   ui::test::EventGenerator* event_generator = GetEventGenerator();
125   event_generator->EnterPenPointerMode();
126 
127   // When disabled the highlighter pointer should not be showing.
128   event_generator->MoveTouch(gfx::Point(1, 1));
129   EXPECT_FALSE(controller_test_api_->IsShowingHighlighter());
130 
131   // Verify that by enabling the mode, the highlighter pointer should still not
132   // be showing.
133   controller_test_api_->SetEnabled(true);
134   EXPECT_FALSE(controller_test_api_->IsShowingHighlighter());
135 
136   // Verify moving the stylus 4 times will not display the highlighter pointer.
137   event_generator->MoveTouch(gfx::Point(2, 2));
138   event_generator->MoveTouch(gfx::Point(3, 3));
139   event_generator->MoveTouch(gfx::Point(4, 4));
140   event_generator->MoveTouch(gfx::Point(5, 5));
141   EXPECT_FALSE(controller_test_api_->IsShowingHighlighter());
142 
143   // Verify pressing the stylus will show the highlighter pointer and add a
144   // point but will not activate fading out.
145   event_generator->PressTouch();
146   EXPECT_TRUE(controller_test_api_->IsShowingHighlighter());
147   EXPECT_FALSE(controller_test_api_->IsFadingAway());
148   EXPECT_EQ(1, controller_test_api_->points().GetNumberOfPoints());
149 
150   // Verify dragging the stylus 2 times will add 2 more points.
151   event_generator->MoveTouch(gfx::Point(6, 6));
152   event_generator->MoveTouch(gfx::Point(7, 7));
153   EXPECT_EQ(3, controller_test_api_->points().GetNumberOfPoints());
154 
155   // Verify releasing the stylus still shows the highlighter pointer, which is
156   // fading away.
157   event_generator->ReleaseTouch();
158   EXPECT_TRUE(controller_test_api_->IsShowingHighlighter());
159   EXPECT_TRUE(controller_test_api_->IsFadingAway());
160 
161   // Verify that disabling the mode right after the gesture completion does not
162   // hide the highlighter pointer immediately but lets it play out the
163   // animation.
164   controller_test_api_->SetEnabled(false);
165   EXPECT_TRUE(controller_test_api_->IsShowingHighlighter());
166   EXPECT_TRUE(controller_test_api_->IsFadingAway());
167 
168   // Verify that disabling the mode mid-gesture hides the highlighter pointer
169   // immediately.
170   controller_test_api_->DestroyPointerView();
171   controller_test_api_->SetEnabled(true);
172   event_generator->PressTouch();
173   event_generator->MoveTouch(gfx::Point(6, 6));
174   EXPECT_TRUE(controller_test_api_->IsShowingHighlighter());
175   controller_test_api_->SetEnabled(false);
176   EXPECT_FALSE(controller_test_api_->IsShowingHighlighter());
177 
178   // Verify that the highlighter pointer does not add points while disabled.
179   event_generator->PressTouch();
180   event_generator->MoveTouch(gfx::Point(8, 8));
181   event_generator->ReleaseTouch();
182   event_generator->MoveTouch(gfx::Point(9, 9));
183   EXPECT_FALSE(controller_test_api_->IsShowingHighlighter());
184 
185   // Verify that the highlighter pointer does not get shown if points are not
186   // coming from the stylus, even when enabled.
187   event_generator->ExitPenPointerMode();
188   controller_test_api_->SetEnabled(true);
189   event_generator->PressTouch();
190   event_generator->MoveTouch(gfx::Point(10, 10));
191   event_generator->MoveTouch(gfx::Point(11, 11));
192   EXPECT_FALSE(controller_test_api_->IsShowingHighlighter());
193   event_generator->ReleaseTouch();
194 }
195 
196 // Test to ensure the class responsible for drawing the highlighter pointer
197 // handles prediction as expected when it receives points from stylus movements.
TEST_F(HighlighterControllerTest,HighlighterPrediction)198 TEST_F(HighlighterControllerTest, HighlighterPrediction) {
199   controller_test_api_->SetEnabled(true);
200   // The highlighter pointer mode only works with stylus.
201   ui::test::EventGenerator* event_generator = GetEventGenerator();
202   event_generator->EnterPenPointerMode();
203   event_generator->PressTouch();
204   EXPECT_TRUE(controller_test_api_->IsShowingHighlighter());
205 
206   EXPECT_EQ(1, controller_test_api_->points().GetNumberOfPoints());
207   // Initial press event shouldn't generate any predicted points as there's no
208   // history to use for prediction.
209   EXPECT_EQ(0, controller_test_api_->predicted_points().GetNumberOfPoints());
210 
211   // Verify dragging the stylus 3 times will add some predicted points.
212   event_generator->MoveTouch(gfx::Point(10, 10));
213   event_generator->MoveTouch(gfx::Point(20, 20));
214   event_generator->MoveTouch(gfx::Point(30, 30));
215   EXPECT_NE(0, controller_test_api_->predicted_points().GetNumberOfPoints());
216   // Verify predicted points are in the right direction.
217   for (const auto& point : controller_test_api_->predicted_points().points()) {
218     EXPECT_LT(30, point.location.x());
219     EXPECT_LT(30, point.location.y());
220   }
221 }
222 
223 // Test that stylus gestures are correctly recognized by HighlighterController.
TEST_F(HighlighterControllerTest,HighlighterGestures)224 TEST_F(HighlighterControllerTest, HighlighterGestures) {
225   controller_test_api_->SetEnabled(true);
226   ui::test::EventGenerator* event_generator = GetEventGenerator();
227   event_generator->EnterPenPointerMode();
228 
229   TestHighlighterObserver observer;
230   controller_->AddObserver(&observer);
231 
232   // A non-horizontal stroke is not recognized
233   controller_test_api_->ResetSelection();
234   event_generator->MoveTouch(gfx::Point(100, 100));
235   event_generator->PressTouch();
236   event_generator->MoveTouch(gfx::Point(200, 200));
237   event_generator->ReleaseTouch();
238   EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
239 
240   // An almost horizontal stroke is recognized
241   controller_test_api_->ResetSelection();
242   event_generator->MoveTouch(gfx::Point(100, 100));
243   event_generator->PressTouch();
244   event_generator->MoveTouch(gfx::Point(300, 102));
245   event_generator->ReleaseTouch();
246   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
247 
248   // Horizontal stroke selection rectangle should:
249   //   have the same horizontal center line as the stroke bounding box,
250   //   be 4dp wider than the stroke bounding box,
251   //   be exactly 14dp high.
252   gfx::Rect expected_rect(98, 94, 204, 14);
253   EXPECT_EQ(expected_rect, controller_test_api_->selection());
254   EXPECT_EQ(expected_rect, observer.last_recognized_rect_);
255 
256   // An insufficiently closed C-like shape is not recognized
257   controller_test_api_->ResetSelection();
258   event_generator->MoveTouch(gfx::Point(100, 0));
259   event_generator->PressTouch();
260   event_generator->MoveTouch(gfx::Point(0, 0));
261   event_generator->MoveTouch(gfx::Point(0, 100));
262   event_generator->MoveTouch(gfx::Point(100, 100));
263   event_generator->ReleaseTouch();
264   EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
265 
266   // An almost closed G-like shape is recognized
267   controller_test_api_->ResetSelection();
268   event_generator->MoveTouch(gfx::Point(200, 0));
269   event_generator->PressTouch();
270   event_generator->MoveTouch(gfx::Point(0, 0));
271   event_generator->MoveTouch(gfx::Point(0, 100));
272   event_generator->MoveTouch(gfx::Point(200, 100));
273   event_generator->MoveTouch(gfx::Point(200, 20));
274   event_generator->ReleaseTouch();
275   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
276   expected_rect = gfx::Rect(0, 0, 200, 100);
277   EXPECT_EQ(expected_rect, controller_test_api_->selection());
278   EXPECT_EQ(expected_rect, observer.last_recognized_rect_);
279 
280   // A closed diamond shape is recognized
281   controller_test_api_->ResetSelection();
282   event_generator->MoveTouch(gfx::Point(100, 50));
283   event_generator->PressTouch();
284   event_generator->MoveTouch(gfx::Point(200, 150));
285   event_generator->MoveTouch(gfx::Point(100, 250));
286   event_generator->MoveTouch(gfx::Point(0, 150));
287   event_generator->MoveTouch(gfx::Point(100, 50));
288   event_generator->ReleaseTouch();
289   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
290   expected_rect = gfx::Rect(0, 50, 200, 200);
291   EXPECT_EQ(expected_rect, controller_test_api_->selection());
292   EXPECT_EQ(expected_rect, observer.last_recognized_rect_);
293 
294   controller_->RemoveObserver(&observer);
295 }
296 
TEST_F(HighlighterControllerTest,HighlighterGesturesScaled)297 TEST_F(HighlighterControllerTest, HighlighterGesturesScaled) {
298   controller_test_api_->SetEnabled(true);
299   ui::test::EventGenerator* event_generator = GetEventGenerator();
300   event_generator->EnterPenPointerMode();
301 
302   const gfx::Rect original_px(200, 100, 400, 300);
303 
304   constexpr float display_scales[] = {1.f, 1.5f, 2.0f};
305   constexpr float ui_scales[] = {0.5f,  0.67f, 1.0f,  1.25f,
306                                  1.33f, 1.5f,  1.67f, 2.0f};
307 
308   for (size_t i = 0; i < sizeof(display_scales) / sizeof(float); ++i) {
309     const float display_scale = display_scales[i];
310     for (size_t j = 0; j < sizeof(ui_scales) / sizeof(float); ++j) {
311       const float ui_scale = ui_scales[j];
312 
313       std::string display_spec =
314           base::StringPrintf("1500x1000*%.2f@%.2f", display_scale, ui_scale);
315       SCOPED_TRACE(display_spec);
316       UpdateDisplayAndWaitForCompositingEnded(display_spec);
317 
318       controller_test_api_->ResetSelection();
319       TraceRect(original_px);
320       EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
321 
322       const float combined_scale = display_scale * ui_scale;
323 
324       const gfx::Rect selection_dp = controller_test_api_->selection();
325       const gfx::Rect selection_px = gfx::ToEnclosingRect(
326           gfx::ScaleRect(gfx::RectF(selection_dp), combined_scale));
327       EXPECT_TRUE(selection_px.Contains(original_px));
328 
329       gfx::Rect inflated_px(original_px);
330       // Allow for rounding errors within 1dp.
331       const int error_margin = static_cast<int>(std::ceil(combined_scale));
332       inflated_px.Inset(-error_margin, -error_margin);
333       EXPECT_TRUE(inflated_px.Contains(selection_px));
334     }
335   }
336 }
337 
338 // Test that stylus gesture recognition correctly handles display rotation
TEST_F(HighlighterControllerTest,HighlighterGesturesRotated)339 TEST_F(HighlighterControllerTest, HighlighterGesturesRotated) {
340   controller_test_api_->SetEnabled(true);
341   ui::test::EventGenerator* event_generator = GetEventGenerator();
342   event_generator->EnterPenPointerMode();
343 
344   const gfx::Rect trace(200, 100, 400, 300);
345 
346   // No rotation
347   UpdateDisplayAndWaitForCompositingEnded("1500x1000");
348   controller_test_api_->ResetSelection();
349   TraceRect(trace);
350   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
351   EXPECT_EQ("200,100 400x300", controller_test_api_->selection().ToString());
352 
353   // Rotate to 90 degrees
354   UpdateDisplayAndWaitForCompositingEnded("1500x1000/r");
355   controller_test_api_->ResetSelection();
356   TraceRect(trace);
357   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
358   EXPECT_EQ("100,900 300x400", controller_test_api_->selection().ToString());
359 
360   // Rotate to 180 degrees
361   UpdateDisplayAndWaitForCompositingEnded("1500x1000/u");
362   controller_test_api_->ResetSelection();
363   TraceRect(trace);
364   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
365   EXPECT_EQ("900,600 400x300", controller_test_api_->selection().ToString());
366 
367   // Rotate to 270 degrees
368   UpdateDisplayAndWaitForCompositingEnded("1500x1000/l");
369   controller_test_api_->ResetSelection();
370   TraceRect(trace);
371   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
372   EXPECT_EQ("600,200 300x400", controller_test_api_->selection().ToString());
373 }
374 
375 // Test that a stroke interrupted close to the screen edge is treated as
376 // contiguous.
TEST_F(HighlighterControllerTest,InterruptedStroke)377 TEST_F(HighlighterControllerTest, InterruptedStroke) {
378   controller_test_api_->SetEnabled(true);
379   ui::test::EventGenerator* event_generator = GetEventGenerator();
380   event_generator->EnterPenPointerMode();
381 
382   UpdateDisplayAndWaitForCompositingEnded("1500x1000");
383 
384   // An interrupted stroke close to the screen edge should be recognized as a
385   // contiguous stroke.
386   controller_test_api_->ResetSelection();
387   event_generator->MoveTouch(gfx::Point(300, 100));
388   event_generator->PressTouch();
389   event_generator->MoveTouch(gfx::Point(0, 100));
390   event_generator->ReleaseTouch();
391   EXPECT_TRUE(controller_test_api_->IsWaitingToResumeStroke());
392   EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
393   EXPECT_FALSE(controller_test_api_->IsFadingAway());
394 
395   event_generator->MoveTouch(gfx::Point(0, 200));
396   event_generator->PressTouch();
397   event_generator->MoveTouch(gfx::Point(300, 200));
398   event_generator->ReleaseTouch();
399   EXPECT_FALSE(controller_test_api_->IsWaitingToResumeStroke());
400   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
401   EXPECT_EQ("0,100 300x100", controller_test_api_->selection().ToString());
402 
403   // Repeat the same gesture, but simulate a timeout after the gap. This should
404   // force the gesture completion.
405   controller_test_api_->ResetSelection();
406   event_generator->MoveTouch(gfx::Point(300, 100));
407   event_generator->PressTouch();
408   event_generator->MoveTouch(gfx::Point(0, 100));
409   event_generator->ReleaseTouch();
410   EXPECT_TRUE(controller_test_api_->IsWaitingToResumeStroke());
411   EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
412   EXPECT_FALSE(controller_test_api_->IsFadingAway());
413 
414   controller_test_api_->SimulateInterruptedStrokeTimeout();
415   EXPECT_FALSE(controller_test_api_->IsWaitingToResumeStroke());
416   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
417   EXPECT_TRUE(controller_test_api_->IsFadingAway());
418 }
419 
420 // Test that the selection is never crossing the screen bounds.
TEST_F(HighlighterControllerTest,SelectionInsideScreen)421 TEST_F(HighlighterControllerTest, SelectionInsideScreen) {
422   controller_test_api_->SetEnabled(true);
423   ui::test::EventGenerator* event_generator = GetEventGenerator();
424   event_generator->EnterPenPointerMode();
425 
426   constexpr float display_scales[] = {1.f, 1.5f, 2.0f};
427 
428   for (size_t i = 0; i < sizeof(display_scales) / sizeof(float); ++i) {
429     // 2nd display is for offscreen test.
430     std::string display_spec = base::StringPrintf(
431         "1000x1000*%.2f,500x1000*%.2f", display_scales[i], display_scales[i]);
432     SCOPED_TRACE(display_spec);
433     UpdateDisplayAndWaitForCompositingEnded(display_spec);
434 
435     const gfx::Rect screen(0, 0, 1000, 1000);
436 
437     // Rectangle completely offscreen.
438     controller_test_api_->ResetSelection();
439     TraceRect(gfx::Rect(-100, -100, 10, 10));
440     controller_test_api_->SimulateInterruptedStrokeTimeout();
441     EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
442 
443     // Rectangle crossing the left edge.
444     controller_test_api_->ResetSelection();
445     TraceRect(gfx::Rect(-100, 100, 200, 200));
446     controller_test_api_->SimulateInterruptedStrokeTimeout();
447     EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
448     EXPECT_TRUE(screen.Contains(controller_test_api_->selection()));
449 
450     // Rectangle crossing the top edge.
451     controller_test_api_->ResetSelection();
452     TraceRect(gfx::Rect(100, -100, 200, 200));
453     controller_test_api_->SimulateInterruptedStrokeTimeout();
454     EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
455     EXPECT_TRUE(screen.Contains(controller_test_api_->selection()));
456 
457     // Rectangle crossing the right edge.
458     controller_test_api_->ResetSelection();
459     TraceRect(gfx::Rect(900, 100, 200, 200));
460     controller_test_api_->SimulateInterruptedStrokeTimeout();
461     EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
462     EXPECT_TRUE(screen.Contains(controller_test_api_->selection()));
463 
464     // Rectangle crossing the bottom edge.
465     controller_test_api_->ResetSelection();
466     TraceRect(gfx::Rect(100, 900, 200, 200));
467     controller_test_api_->SimulateInterruptedStrokeTimeout();
468     EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
469     EXPECT_TRUE(screen.Contains(controller_test_api_->selection()));
470 
471     // Vertical stroke completely offscreen.
472     controller_test_api_->ResetSelection();
473     event_generator->MoveTouch(gfx::Point(1100, 100));
474     event_generator->PressTouch();
475     event_generator->MoveTouch(gfx::Point(1100, 500));
476     event_generator->ReleaseTouch();
477     controller_test_api_->SimulateInterruptedStrokeTimeout();
478     EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
479 
480     // Horizontal stroke along the top edge of the screen.
481     controller_test_api_->ResetSelection();
482     event_generator->MoveTouch(gfx::Point(0, 0));
483     event_generator->PressTouch();
484     event_generator->MoveTouch(gfx::Point(1000, 0));
485     event_generator->ReleaseTouch();
486     controller_test_api_->SimulateInterruptedStrokeTimeout();
487     EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
488     EXPECT_TRUE(screen.Contains(controller_test_api_->selection()));
489 
490     // Horizontal stroke along the bottom edge of the screen.
491     controller_test_api_->ResetSelection();
492     event_generator->MoveTouch(gfx::Point(0, 999));
493     event_generator->PressTouch();
494     event_generator->MoveTouch(gfx::Point(1000, 999));
495     event_generator->ReleaseTouch();
496     controller_test_api_->SimulateInterruptedStrokeTimeout();
497     EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
498     EXPECT_TRUE(screen.Contains(controller_test_api_->selection()));
499   }
500 }
501 
502 // Test that a detached client does not receive notifications.
TEST_F(HighlighterControllerTest,DetachedClient)503 TEST_F(HighlighterControllerTest, DetachedClient) {
504   controller_test_api_->SetEnabled(true);
505   ui::test::EventGenerator* event_generator = GetEventGenerator();
506   event_generator->EnterPenPointerMode();
507 
508   UpdateDisplayAndWaitForCompositingEnded("1500x1000");
509   const gfx::Rect trace(200, 100, 400, 300);
510 
511   // Detach the client, no notifications should reach it.
512   controller_test_api_->DetachClient();
513 
514   controller_test_api_->ResetEnabledState();
515   controller_test_api_->SetEnabled(false);
516   EXPECT_FALSE(controller_test_api_->HandleEnabledStateChangedCalled());
517   controller_test_api_->SetEnabled(true);
518   EXPECT_FALSE(controller_test_api_->HandleEnabledStateChangedCalled());
519 
520   controller_test_api_->ResetSelection();
521   TraceRect(trace);
522   EXPECT_FALSE(controller_test_api_->HandleSelectionCalled());
523 
524   // Attach the client again, notifications should be delivered normally.
525   controller_test_api_->AttachClient();
526 
527   controller_test_api_->ResetEnabledState();
528   controller_test_api_->SetEnabled(false);
529   EXPECT_TRUE(controller_test_api_->HandleEnabledStateChangedCalled());
530   controller_test_api_->SetEnabled(true);
531   EXPECT_TRUE(controller_test_api_->HandleEnabledStateChangedCalled());
532 
533   controller_test_api_->ResetSelection();
534   TraceRect(trace);
535   EXPECT_TRUE(controller_test_api_->HandleSelectionCalled());
536 }
537 
538 // Test enabling/disabling metalayer mode by selecting/deselecting on palette
539 // tool and calling UpdateEnabledState notify observers properly.
TEST_F(HighlighterControllerTest,UpdateEnabledState)540 TEST_F(HighlighterControllerTest, UpdateEnabledState) {
541   TestHighlighterObserver observer;
542   controller_->AddObserver(&observer);
543 
544   // Assert initial state.
545   ASSERT_EQ(0, observer.enabled_count_);
546   ASSERT_EQ(0, observer.disabled_by_user_count_);
547   ASSERT_EQ(0, observer.disabled_by_session_abort_);
548   ASSERT_EQ(0, observer.disabled_by_session_complete_);
549 
550   // Test enabling.
551   tool_->OnEnable();
552   EXPECT_EQ(1, observer.enabled_count_);
553   EXPECT_EQ(0, observer.disabled_by_user_count_);
554   EXPECT_EQ(0, observer.disabled_by_session_abort_);
555   EXPECT_EQ(0, observer.disabled_by_session_complete_);
556 
557   // Test disabling by user.
558   tool_->OnDisable();
559   EXPECT_EQ(1, observer.enabled_count_);
560   EXPECT_EQ(1, observer.disabled_by_user_count_);
561   EXPECT_EQ(0, observer.disabled_by_session_abort_);
562   EXPECT_EQ(0, observer.disabled_by_session_complete_);
563 
564   // Test disabling by session abort.
565   tool_->OnEnable();
566   EXPECT_EQ(2, observer.enabled_count_);
567   EXPECT_EQ(1, observer.disabled_by_user_count_);
568   EXPECT_EQ(0, observer.disabled_by_session_abort_);
569   EXPECT_EQ(0, observer.disabled_by_session_complete_);
570   controller_->UpdateEnabledState(
571       HighlighterEnabledState::kDisabledBySessionAbort);
572   EXPECT_EQ(2, observer.enabled_count_);
573   EXPECT_EQ(1, observer.disabled_by_user_count_);
574   EXPECT_EQ(1, observer.disabled_by_session_abort_);
575   EXPECT_EQ(0, observer.disabled_by_session_complete_);
576 
577   // Test disabling by session complete.
578   tool_->OnEnable();
579   EXPECT_EQ(3, observer.enabled_count_);
580   EXPECT_EQ(1, observer.disabled_by_user_count_);
581   EXPECT_EQ(1, observer.disabled_by_session_abort_);
582   EXPECT_EQ(0, observer.disabled_by_session_complete_);
583   controller_->UpdateEnabledState(
584       HighlighterEnabledState::kDisabledBySessionComplete);
585   EXPECT_EQ(3, observer.enabled_count_);
586   EXPECT_EQ(1, observer.disabled_by_user_count_);
587   EXPECT_EQ(1, observer.disabled_by_session_abort_);
588   EXPECT_EQ(1, observer.disabled_by_session_complete_);
589 
590   controller_->RemoveObserver(&observer);
591 }
592 
593 // Test aborting a metalayer session and notifying observers properly.
TEST_F(HighlighterControllerTest,AbortSession)594 TEST_F(HighlighterControllerTest, AbortSession) {
595   TestHighlighterObserver observer;
596   controller_->AddObserver(&observer);
597 
598   // Assert initial state.
599   ASSERT_EQ(0, observer.enabled_count_);
600   ASSERT_EQ(0, observer.disabled_by_user_count_);
601   ASSERT_EQ(0, observer.disabled_by_session_abort_);
602   ASSERT_EQ(0, observer.disabled_by_session_complete_);
603 
604   // Start metalayer session.
605   tool_->OnEnable();
606   EXPECT_EQ(1, observer.enabled_count_);
607   EXPECT_EQ(0, observer.disabled_by_user_count_);
608   EXPECT_EQ(0, observer.disabled_by_session_abort_);
609   EXPECT_EQ(0, observer.disabled_by_session_complete_);
610 
611   // Abort metalayer session.
612   controller_->AbortSession();
613   EXPECT_EQ(1, observer.enabled_count_);
614   EXPECT_EQ(0, observer.disabled_by_user_count_);
615   EXPECT_EQ(1, observer.disabled_by_session_abort_);
616   EXPECT_EQ(0, observer.disabled_by_session_complete_);
617 
618   // Assert no-op when aborting an aborted session.
619   controller_->AbortSession();
620   EXPECT_EQ(1, observer.enabled_count_);
621   EXPECT_EQ(0, observer.disabled_by_user_count_);
622   EXPECT_EQ(1, observer.disabled_by_session_abort_);
623   EXPECT_EQ(0, observer.disabled_by_session_complete_);
624 
625   // Assert no-op when aborting a completed session.
626   tool_->OnEnable();
627   EXPECT_EQ(2, observer.enabled_count_);
628   EXPECT_EQ(0, observer.disabled_by_user_count_);
629   EXPECT_EQ(1, observer.disabled_by_session_abort_);
630   EXPECT_EQ(0, observer.disabled_by_session_complete_);
631   controller_->UpdateEnabledState(
632       HighlighterEnabledState::kDisabledBySessionComplete);
633   EXPECT_EQ(2, observer.enabled_count_);
634   EXPECT_EQ(0, observer.disabled_by_user_count_);
635   EXPECT_EQ(1, observer.disabled_by_session_abort_);
636   EXPECT_EQ(1, observer.disabled_by_session_complete_);
637   controller_->AbortSession();
638   EXPECT_EQ(2, observer.enabled_count_);
639   EXPECT_EQ(0, observer.disabled_by_user_count_);
640   EXPECT_EQ(1, observer.disabled_by_session_abort_);
641   EXPECT_EQ(1, observer.disabled_by_session_complete_);
642 
643   // Assert no-op when aborting a disabled session.
644   tool_->OnEnable();
645   EXPECT_EQ(3, observer.enabled_count_);
646   EXPECT_EQ(0, observer.disabled_by_user_count_);
647   EXPECT_EQ(1, observer.disabled_by_session_abort_);
648   EXPECT_EQ(1, observer.disabled_by_session_complete_);
649   tool_->OnDisable();
650   EXPECT_EQ(3, observer.enabled_count_);
651   EXPECT_EQ(1, observer.disabled_by_user_count_);
652   EXPECT_EQ(1, observer.disabled_by_session_abort_);
653   EXPECT_EQ(1, observer.disabled_by_session_complete_);
654   controller_->AbortSession();
655   EXPECT_EQ(3, observer.enabled_count_);
656   EXPECT_EQ(1, observer.disabled_by_user_count_);
657   EXPECT_EQ(1, observer.disabled_by_session_abort_);
658   EXPECT_EQ(1, observer.disabled_by_session_complete_);
659 }
660 
661 }  // namespace ash
662