// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ash/wm/video_detector.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" #include "ash/wm/window_state.h" #include "ash/wm/wm_event.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/time/time.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/test/test_windows.h" #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/gfx/geometry/rect.h" #include "ui/wm/public/window_types.h" namespace ash { namespace test { // Implementation that just counts the number of times we've been told that a // video is playing. class TestVideoDetectorObserver : public VideoDetectorObserver { public: TestVideoDetectorObserver() : num_invocations_(0), num_fullscreens_(0), num_not_fullscreens_(0) {} int num_invocations() const { return num_invocations_; } int num_fullscreens() const { return num_fullscreens_; } int num_not_fullscreens() const { return num_not_fullscreens_; } void reset_stats() { num_invocations_ = 0; num_fullscreens_ = 0; num_not_fullscreens_ = 0; } // VideoDetectorObserver implementation. void OnVideoDetected(bool is_fullscreen) override { num_invocations_++; if (is_fullscreen) num_fullscreens_++; else num_not_fullscreens_++; } private: // Number of times that OnVideoDetected() has been called. int num_invocations_; // Number of times that OnVideoDetected() has been called with is_fullscreen // == true. int num_fullscreens_; // Number of times that OnVideoDetected() has been called with is_fullscreen // == false. int num_not_fullscreens_; DISALLOW_COPY_AND_ASSIGN(TestVideoDetectorObserver); }; class VideoDetectorTest : public AshTestBase { public: VideoDetectorTest() {} ~VideoDetectorTest() override {} void SetUp() override { AshTestBase::SetUp(); observer_.reset(new TestVideoDetectorObserver); detector_ = Shell::GetInstance()->video_detector(); detector_->AddObserver(observer_.get()); now_ = base::TimeTicks::Now(); detector_->set_now_for_test(now_); } void TearDown() override { detector_->RemoveObserver(observer_.get()); AshTestBase::TearDown(); } protected: // Move |detector_|'s idea of the current time forward by |delta|. void AdvanceTime(base::TimeDelta delta) { now_ += delta; detector_->set_now_for_test(now_); } VideoDetector* detector_; // not owned scoped_ptr observer_; base::TimeTicks now_; private: DISALLOW_COPY_AND_ASSIGN(VideoDetectorTest); }; TEST_F(VideoDetectorTest, Basic) { gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); scoped_ptr window( CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds)); // Send enough updates, but make them be too small to trigger detection. gfx::Rect update_region( gfx::Point(), gfx::Size(VideoDetector::kMinUpdateWidth - 1, VideoDetector::kMinUpdateHeight)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(0, observer_->num_invocations()); // Send not-quite-enough adaquately-sized updates. observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds(2)); update_region.set_size( gfx::Size(VideoDetector::kMinUpdateWidth, VideoDetector::kMinUpdateHeight)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(0, observer_->num_invocations()); // We should get notified after the next update, but not in response to // additional updates. detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); // Spread out the frames over a longer period of time, but send enough // over a one-second window that the observer should be notified. observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds(2)); detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(0, observer_->num_invocations()); AdvanceTime(base::TimeDelta::FromMilliseconds(500)); const int kNumFrames = VideoDetector::kMinFramesPerSecond + 1; base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(1000 / kNumFrames); for (int i = 0; i < kNumFrames; ++i) { AdvanceTime(kInterval); detector_->OnDelegatedFrameDamage(window.get(), update_region); } EXPECT_EQ(1, observer_->num_invocations()); // Keep going and check that the observer is notified again. for (int i = 0; i < kNumFrames; ++i) { AdvanceTime(kInterval); detector_->OnDelegatedFrameDamage(window.get(), update_region); } EXPECT_EQ(2, observer_->num_invocations()); // Send updates at a slower rate and check that the observer isn't notified. base::TimeDelta kSlowInterval = base::TimeDelta::FromMilliseconds( 1000 / (VideoDetector::kMinFramesPerSecond - 2)); for (int i = 0; i < kNumFrames; ++i) { AdvanceTime(kSlowInterval); detector_->OnDelegatedFrameDamage(window.get(), update_region); } EXPECT_EQ(2, observer_->num_invocations()); } TEST_F(VideoDetectorTest, Shutdown) { gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); scoped_ptr window( CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds)); gfx::Rect update_region( gfx::Point(), gfx::Size(VideoDetector::kMinUpdateWidth, VideoDetector::kMinUpdateHeight)); // It should not detect video during the shutdown. Shell::GetInstance()->OnAppTerminating(); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(0, observer_->num_invocations()); } TEST_F(VideoDetectorTest, WindowNotVisible) { gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); scoped_ptr window( CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds)); // Reparent the window to the root to make sure that visibility changes aren't // animated. Shell::GetPrimaryRootWindow()->AddChild(window.get()); // We shouldn't report video that's played in a hidden window. window->Hide(); gfx::Rect update_region( gfx::Point(), gfx::Size(VideoDetector::kMinUpdateWidth, VideoDetector::kMinUpdateHeight)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(0, observer_->num_invocations()); // Make the window visible and send more updates. observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds(2)); window->Show(); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); // We also shouldn't report video in a window that's fully offscreen. observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds(2)); gfx::Rect offscreen_bounds( gfx::Point(Shell::GetPrimaryRootWindow()->bounds().width(), 0), window_bounds.size()); window->SetBounds(offscreen_bounds); ASSERT_EQ(offscreen_bounds, window->bounds()); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(0, observer_->num_invocations()); } TEST_F(VideoDetectorTest, MultipleWindows) { // Create two windows. gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); scoped_ptr window1( CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds)); scoped_ptr window2( CreateTestWindowInShell(SK_ColorBLUE, 23456, window_bounds)); // Even if there's video playing in both, the observer should only receive a // single notification. gfx::Rect update_region( gfx::Point(), gfx::Size(VideoDetector::kMinUpdateWidth, VideoDetector::kMinUpdateHeight)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window1.get(), update_region); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window2.get(), update_region); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); } // Test that the observer receives repeated notifications. TEST_F(VideoDetectorTest, RepeatedNotifications) { gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768)); scoped_ptr window( CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds)); gfx::Rect update_region( gfx::Point(), gfx::Size(VideoDetector::kMinUpdateWidth, VideoDetector::kMinUpdateHeight)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); // Let enough time pass that a second notification should be sent. observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds( static_cast(VideoDetector::kNotifyIntervalSec + 1))); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), update_region); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); } // Test that the observer receives a true value when the window is fullscreen. TEST_F(VideoDetectorTest, FullscreenWindow) { if (!SupportsMultipleDisplays()) return; UpdateDisplay("1024x768,1024x768"); const gfx::Rect kLeftBounds(gfx::Point(), gfx::Size(1024, 768)); scoped_ptr window( CreateTestWindowInShell(SK_ColorRED, 12345, kLeftBounds)); wm::WindowState* window_state = wm::GetWindowState(window.get()); const wm::WMEvent toggle_fullscreen_event(wm::WM_EVENT_TOGGLE_FULLSCREEN); window_state->OnWMEvent(&toggle_fullscreen_event); ASSERT_TRUE(window_state->IsFullscreen()); window->Focus(); const gfx::Rect kUpdateRegion( gfx::Point(), gfx::Size(VideoDetector::kMinUpdateWidth, VideoDetector::kMinUpdateHeight)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), kUpdateRegion); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(1, observer_->num_fullscreens()); EXPECT_EQ(0, observer_->num_not_fullscreens()); // Make the first window non-fullscreen and open a second fullscreen window on // a different desktop. window_state->OnWMEvent(&toggle_fullscreen_event); ASSERT_FALSE(window_state->IsFullscreen()); const gfx::Rect kRightBounds(gfx::Point(1024, 0), gfx::Size(1024, 768)); scoped_ptr other_window( CreateTestWindowInShell(SK_ColorBLUE, 6789, kRightBounds)); wm::WindowState* other_window_state = wm::GetWindowState(other_window.get()); other_window_state->OnWMEvent(&toggle_fullscreen_event); ASSERT_TRUE(other_window_state->IsFullscreen()); // When video is detected in the first (now non-fullscreen) window, fullscreen // video should still be reported due to the second window being fullscreen. // This avoids situations where non-fullscreen video could be reported when // multiple videos are playing in fullscreen and non-fullscreen windows. observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds(2)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), kUpdateRegion); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(1, observer_->num_fullscreens()); EXPECT_EQ(0, observer_->num_not_fullscreens()); // Make the second window non-fullscreen and check that the next video report // is non-fullscreen. other_window_state->OnWMEvent(&toggle_fullscreen_event); ASSERT_FALSE(other_window_state->IsFullscreen()); observer_->reset_stats(); AdvanceTime(base::TimeDelta::FromSeconds(2)); for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i) detector_->OnDelegatedFrameDamage(window.get(), kUpdateRegion); EXPECT_EQ(1, observer_->num_invocations()); EXPECT_EQ(0, observer_->num_fullscreens()); EXPECT_EQ(1, observer_->num_not_fullscreens()); } } // namespace test } // namespace ash