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 22 namespace ash { 23 namespace test { 24 25 // Implementation that just counts the number of times we've been told that a 26 // video is playing. 27 class 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 64 class 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 101 TEST_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 170 TEST_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 186 TEST_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 228 TEST_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. 252 TEST_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. 278 TEST_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