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/video_detector.h"
6
7#include "ash/shell.h"
8#include "ash/test/ash_test_base.h"
9#include "ash/wm/window_state.h"
10#include "ash/wm/wm_event.h"
11#include "base/compiler_specific.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/time/time.h"
14#include "third_party/skia/include/core/SkColor.h"
15#include "ui/aura/client/aura_constants.h"
16#include "ui/aura/test/test_windows.h"
17#include "ui/aura/window.h"
18#include "ui/aura/window_event_dispatcher.h"
19#include "ui/gfx/rect.h"
20#include "ui/wm/public/window_types.h"
21
22namespace ash {
23namespace test {
24
25// Implementation that just counts the number of times we've been told that a
26// video is playing.
27class TestVideoDetectorObserver : public VideoDetectorObserver {
28 public:
29  TestVideoDetectorObserver() : num_invocations_(0),
30                                num_fullscreens_(0),
31                                num_not_fullscreens_(0) {}
32
33  int num_invocations() const { return num_invocations_; }
34  int num_fullscreens() const { return num_fullscreens_; }
35  int num_not_fullscreens() const { return num_not_fullscreens_; }
36  void reset_stats() {
37    num_invocations_ = 0;
38    num_fullscreens_ = 0;
39    num_not_fullscreens_ = 0;
40  }
41
42  // VideoDetectorObserver implementation.
43  virtual void OnVideoDetected(bool is_fullscreen) OVERRIDE {
44    num_invocations_++;
45    if (is_fullscreen)
46      num_fullscreens_++;
47    else
48      num_not_fullscreens_++;
49  }
50
51 private:
52  // Number of times that OnVideoDetected() has been called.
53  int num_invocations_;
54  // Number of times that OnVideoDetected() has been called with is_fullscreen
55  // == true.
56  int num_fullscreens_;
57  // Number of times that OnVideoDetected() has been called with is_fullscreen
58  // == false.
59  int num_not_fullscreens_;
60
61  DISALLOW_COPY_AND_ASSIGN(TestVideoDetectorObserver);
62};
63
64class VideoDetectorTest : public AshTestBase {
65 public:
66  VideoDetectorTest() {}
67  virtual ~VideoDetectorTest() {}
68
69  virtual void SetUp() OVERRIDE {
70    AshTestBase::SetUp();
71    observer_.reset(new TestVideoDetectorObserver);
72    detector_ = Shell::GetInstance()->video_detector();
73    detector_->AddObserver(observer_.get());
74
75    now_ = base::TimeTicks::Now();
76    detector_->set_now_for_test(now_);
77  }
78
79  virtual void TearDown() OVERRIDE {
80    detector_->RemoveObserver(observer_.get());
81    AshTestBase::TearDown();
82  }
83
84 protected:
85  // Move |detector_|'s idea of the current time forward by |delta|.
86  void AdvanceTime(base::TimeDelta delta) {
87    now_ += delta;
88    detector_->set_now_for_test(now_);
89  }
90
91  VideoDetector* detector_;  // not owned
92
93  scoped_ptr<TestVideoDetectorObserver> observer_;
94
95  base::TimeTicks now_;
96
97 private:
98  DISALLOW_COPY_AND_ASSIGN(VideoDetectorTest);
99};
100
101TEST_F(VideoDetectorTest, Basic) {
102  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
103  scoped_ptr<aura::Window> window(
104      CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
105
106  // Send enough updates, but make them be too small to trigger detection.
107  gfx::Rect update_region(
108      gfx::Point(),
109      gfx::Size(VideoDetector::kMinUpdateWidth - 1,
110                VideoDetector::kMinUpdateHeight));
111  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
112    detector_->OnDelegatedFrameDamage(window.get(), update_region);
113  EXPECT_EQ(0, observer_->num_invocations());
114
115  // Send not-quite-enough adaquately-sized updates.
116  observer_->reset_stats();
117  AdvanceTime(base::TimeDelta::FromSeconds(2));
118  update_region.set_size(
119      gfx::Size(VideoDetector::kMinUpdateWidth,
120                VideoDetector::kMinUpdateHeight));
121  for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i)
122    detector_->OnDelegatedFrameDamage(window.get(), update_region);
123  EXPECT_EQ(0, observer_->num_invocations());
124
125  // We should get notified after the next update, but not in response to
126  // additional updates.
127  detector_->OnDelegatedFrameDamage(window.get(), update_region);
128  EXPECT_EQ(1, observer_->num_invocations());
129  EXPECT_EQ(0, observer_->num_fullscreens());
130  EXPECT_EQ(1, observer_->num_not_fullscreens());
131  detector_->OnDelegatedFrameDamage(window.get(), update_region);
132  EXPECT_EQ(1, observer_->num_invocations());
133  EXPECT_EQ(0, observer_->num_fullscreens());
134  EXPECT_EQ(1, observer_->num_not_fullscreens());
135
136  // Spread out the frames over a longer period of time, but send enough
137  // over a one-second window that the observer should be notified.
138  observer_->reset_stats();
139  AdvanceTime(base::TimeDelta::FromSeconds(2));
140  detector_->OnDelegatedFrameDamage(window.get(), update_region);
141  EXPECT_EQ(0, observer_->num_invocations());
142
143  AdvanceTime(base::TimeDelta::FromMilliseconds(500));
144  const int kNumFrames = VideoDetector::kMinFramesPerSecond + 1;
145  base::TimeDelta kInterval =
146      base::TimeDelta::FromMilliseconds(1000 / kNumFrames);
147  for (int i = 0; i < kNumFrames; ++i) {
148    AdvanceTime(kInterval);
149    detector_->OnDelegatedFrameDamage(window.get(), update_region);
150  }
151  EXPECT_EQ(1, observer_->num_invocations());
152
153  // Keep going and check that the observer is notified again.
154  for (int i = 0; i < kNumFrames; ++i) {
155    AdvanceTime(kInterval);
156    detector_->OnDelegatedFrameDamage(window.get(), update_region);
157  }
158  EXPECT_EQ(2, observer_->num_invocations());
159
160  // Send updates at a slower rate and check that the observer isn't notified.
161  base::TimeDelta kSlowInterval = base::TimeDelta::FromMilliseconds(
162      1000 / (VideoDetector::kMinFramesPerSecond - 2));
163  for (int i = 0; i < kNumFrames; ++i) {
164    AdvanceTime(kSlowInterval);
165    detector_->OnDelegatedFrameDamage(window.get(), update_region);
166  }
167  EXPECT_EQ(2, observer_->num_invocations());
168}
169
170TEST_F(VideoDetectorTest, Shutdown) {
171  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
172  scoped_ptr<aura::Window> window(
173      CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
174  gfx::Rect update_region(
175      gfx::Point(),
176      gfx::Size(VideoDetector::kMinUpdateWidth,
177                VideoDetector::kMinUpdateHeight));
178
179  // It should not detect video during the shutdown.
180  Shell::GetInstance()->OnAppTerminating();
181  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
182    detector_->OnDelegatedFrameDamage(window.get(), update_region);
183  EXPECT_EQ(0, observer_->num_invocations());
184}
185
186TEST_F(VideoDetectorTest, WindowNotVisible) {
187  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
188  scoped_ptr<aura::Window> window(
189      CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
190
191  // Reparent the window to the root to make sure that visibility changes aren't
192  // animated.
193  Shell::GetPrimaryRootWindow()->AddChild(window.get());
194
195  // We shouldn't report video that's played in a hidden window.
196  window->Hide();
197  gfx::Rect update_region(
198      gfx::Point(),
199      gfx::Size(VideoDetector::kMinUpdateWidth,
200                VideoDetector::kMinUpdateHeight));
201  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
202    detector_->OnDelegatedFrameDamage(window.get(), update_region);
203  EXPECT_EQ(0, observer_->num_invocations());
204
205  // Make the window visible and send more updates.
206  observer_->reset_stats();
207  AdvanceTime(base::TimeDelta::FromSeconds(2));
208  window->Show();
209  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
210    detector_->OnDelegatedFrameDamage(window.get(), update_region);
211  EXPECT_EQ(1, observer_->num_invocations());
212  EXPECT_EQ(0, observer_->num_fullscreens());
213  EXPECT_EQ(1, observer_->num_not_fullscreens());
214
215  // We also shouldn't report video in a window that's fully offscreen.
216  observer_->reset_stats();
217  AdvanceTime(base::TimeDelta::FromSeconds(2));
218  gfx::Rect offscreen_bounds(
219      gfx::Point(Shell::GetPrimaryRootWindow()->bounds().width(), 0),
220      window_bounds.size());
221  window->SetBounds(offscreen_bounds);
222  ASSERT_EQ(offscreen_bounds, window->bounds());
223  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
224    detector_->OnDelegatedFrameDamage(window.get(), update_region);
225  EXPECT_EQ(0, observer_->num_invocations());
226}
227
228TEST_F(VideoDetectorTest, MultipleWindows) {
229  // Create two windows.
230  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
231  scoped_ptr<aura::Window> window1(
232      CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
233  scoped_ptr<aura::Window> window2(
234      CreateTestWindowInShell(SK_ColorBLUE, 23456, window_bounds));
235
236  // Even if there's video playing in both, the observer should only receive a
237  // single notification.
238  gfx::Rect update_region(
239      gfx::Point(),
240      gfx::Size(VideoDetector::kMinUpdateWidth,
241                VideoDetector::kMinUpdateHeight));
242  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
243    detector_->OnDelegatedFrameDamage(window1.get(), update_region);
244  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
245    detector_->OnDelegatedFrameDamage(window2.get(), update_region);
246  EXPECT_EQ(1, observer_->num_invocations());
247  EXPECT_EQ(0, observer_->num_fullscreens());
248  EXPECT_EQ(1, observer_->num_not_fullscreens());
249}
250
251// Test that the observer receives repeated notifications.
252TEST_F(VideoDetectorTest, RepeatedNotifications) {
253  gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
254  scoped_ptr<aura::Window> window(
255      CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
256
257  gfx::Rect update_region(
258      gfx::Point(),
259      gfx::Size(VideoDetector::kMinUpdateWidth,
260                VideoDetector::kMinUpdateHeight));
261  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
262    detector_->OnDelegatedFrameDamage(window.get(), update_region);
263  EXPECT_EQ(1, observer_->num_invocations());
264  EXPECT_EQ(0, observer_->num_fullscreens());
265  EXPECT_EQ(1, observer_->num_not_fullscreens());
266  // Let enough time pass that a second notification should be sent.
267  observer_->reset_stats();
268  AdvanceTime(base::TimeDelta::FromSeconds(
269      static_cast<int64>(VideoDetector::kNotifyIntervalSec + 1)));
270  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
271    detector_->OnDelegatedFrameDamage(window.get(), update_region);
272  EXPECT_EQ(1, observer_->num_invocations());
273  EXPECT_EQ(0, observer_->num_fullscreens());
274  EXPECT_EQ(1, observer_->num_not_fullscreens());
275}
276
277// Test that the observer receives a true value when the window is fullscreen.
278TEST_F(VideoDetectorTest, FullscreenWindow) {
279  if (!SupportsMultipleDisplays())
280    return;
281
282  UpdateDisplay("1024x768,1024x768");
283
284  const gfx::Rect kLeftBounds(gfx::Point(), gfx::Size(1024, 768));
285  scoped_ptr<aura::Window> window(
286      CreateTestWindowInShell(SK_ColorRED, 12345, kLeftBounds));
287  wm::WindowState* window_state = wm::GetWindowState(window.get());
288  const wm::WMEvent toggle_fullscreen_event(wm::WM_EVENT_TOGGLE_FULLSCREEN);
289  window_state->OnWMEvent(&toggle_fullscreen_event);
290  ASSERT_TRUE(window_state->IsFullscreen());
291  window->Focus();
292  const gfx::Rect kUpdateRegion(
293      gfx::Point(),
294      gfx::Size(VideoDetector::kMinUpdateWidth,
295                VideoDetector::kMinUpdateHeight));
296  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
297    detector_->OnDelegatedFrameDamage(window.get(), kUpdateRegion);
298  EXPECT_EQ(1, observer_->num_invocations());
299  EXPECT_EQ(1, observer_->num_fullscreens());
300  EXPECT_EQ(0, observer_->num_not_fullscreens());
301
302  // Make the first window non-fullscreen and open a second fullscreen window on
303  // a different desktop.
304  window_state->OnWMEvent(&toggle_fullscreen_event);
305  ASSERT_FALSE(window_state->IsFullscreen());
306  const gfx::Rect kRightBounds(gfx::Point(1024, 0), gfx::Size(1024, 768));
307  scoped_ptr<aura::Window> other_window(
308      CreateTestWindowInShell(SK_ColorBLUE, 6789, kRightBounds));
309  wm::WindowState* other_window_state = wm::GetWindowState(other_window.get());
310  other_window_state->OnWMEvent(&toggle_fullscreen_event);
311  ASSERT_TRUE(other_window_state->IsFullscreen());
312
313  // When video is detected in the first (now non-fullscreen) window, fullscreen
314  // video should still be reported due to the second window being fullscreen.
315  // This avoids situations where non-fullscreen video could be reported when
316  // multiple videos are playing in fullscreen and non-fullscreen windows.
317  observer_->reset_stats();
318  AdvanceTime(base::TimeDelta::FromSeconds(2));
319  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
320    detector_->OnDelegatedFrameDamage(window.get(), kUpdateRegion);
321  EXPECT_EQ(1, observer_->num_invocations());
322  EXPECT_EQ(1, observer_->num_fullscreens());
323  EXPECT_EQ(0, observer_->num_not_fullscreens());
324
325  // Make the second window non-fullscreen and check that the next video report
326  // is non-fullscreen.
327  other_window_state->OnWMEvent(&toggle_fullscreen_event);
328  ASSERT_FALSE(other_window_state->IsFullscreen());
329  observer_->reset_stats();
330  AdvanceTime(base::TimeDelta::FromSeconds(2));
331  for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
332    detector_->OnDelegatedFrameDamage(window.get(), kUpdateRegion);
333  EXPECT_EQ(1, observer_->num_invocations());
334  EXPECT_EQ(0, observer_->num_fullscreens());
335  EXPECT_EQ(1, observer_->num_not_fullscreens());
336}
337
338}  // namespace test
339}  // namespace ash
340