// Copyright 2014 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 "content/browser/media/audio_stream_monitor.h" #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" #include "base/test/simple_test_tick_clock.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/invalidate_type.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/test/test_renderer_host.h" #include "media/audio/audio_power_monitor.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::InvokeWithoutArgs; namespace content { namespace { const int kRenderProcessId = 1; const int kAnotherRenderProcessId = 2; const int kStreamId = 3; const int kAnotherStreamId = 6; // Used to confirm audio indicator state changes occur at the correct times. class MockWebContentsDelegate : public WebContentsDelegate { public: MOCK_METHOD2(NavigationStateChanged, void(const WebContents* source, InvalidateTypes changed_flags)); }; } // namespace class AudioStreamMonitorTest : public RenderViewHostTestHarness { public: AudioStreamMonitorTest() { // Start |clock_| at non-zero. clock_.Advance(base::TimeDelta::FromSeconds(1000000)); } virtual void SetUp() override { RenderViewHostTestHarness::SetUp(); WebContentsImpl* web_contents = reinterpret_cast( RenderViewHostTestHarness::web_contents()); web_contents->SetDelegate(&mock_web_contents_delegate_); monitor_ = web_contents->audio_stream_monitor(); const_cast(monitor_->clock_) = &clock_; } base::TimeTicks GetTestClockTime() { return clock_.NowTicks(); } void AdvanceClock(const base::TimeDelta& delta) { clock_.Advance(delta); } AudioStreamMonitor::ReadPowerAndClipCallback CreatePollCallback( int stream_id) { return base::Bind( &AudioStreamMonitorTest::ReadPower, base::Unretained(this), stream_id); } void SetStreamPower(int stream_id, float power) { current_power_[stream_id] = power; } void SimulatePollTimerFired() { monitor_->Poll(); } void SimulateOffTimerFired() { monitor_->MaybeToggle(); } void ExpectIsPolling(int render_process_id, int stream_id, bool is_polling) { const AudioStreamMonitor::StreamID key(render_process_id, stream_id); EXPECT_EQ( is_polling, monitor_->poll_callbacks_.find(key) != monitor_->poll_callbacks_.end()); EXPECT_EQ(!monitor_->poll_callbacks_.empty(), monitor_->poll_timer_.IsRunning()); } void ExpectTabWasRecentlyAudible(bool was_audible, const base::TimeTicks& last_blurt_time) { EXPECT_EQ(was_audible, monitor_->was_recently_audible_); EXPECT_EQ(last_blurt_time, monitor_->last_blurt_time_); EXPECT_EQ(monitor_->was_recently_audible_, monitor_->off_timer_.IsRunning()); } void ExpectWebContentsWillBeNotifiedOnce(bool should_be_audible) { EXPECT_CALL( mock_web_contents_delegate_, NavigationStateChanged(RenderViewHostTestHarness::web_contents(), INVALIDATE_TYPE_TAB)) .WillOnce(InvokeWithoutArgs( this, should_be_audible ? &AudioStreamMonitorTest::ExpectIsNotifyingForToggleOn : &AudioStreamMonitorTest::ExpectIsNotifyingForToggleOff)) .RetiresOnSaturation(); } static base::TimeDelta one_polling_interval() { return base::TimeDelta::FromSeconds(1) / AudioStreamMonitor::kPowerMeasurementsPerSecond; } static base::TimeDelta holding_period() { return base::TimeDelta::FromMilliseconds( AudioStreamMonitor::kHoldOnMilliseconds); } void StartMonitoring( int render_process_id, int stream_id, const AudioStreamMonitor::ReadPowerAndClipCallback& callback) { monitor_->StartMonitoringStreamOnUIThread( render_process_id, stream_id, callback); } void StopMonitoring(int render_process_id, int stream_id) { monitor_->StopMonitoringStreamOnUIThread(render_process_id, stream_id); } protected: AudioStreamMonitor* monitor_; private: std::pair ReadPower(int stream_id) { return std::make_pair(current_power_[stream_id], false); } void ExpectIsNotifyingForToggleOn() { EXPECT_TRUE(monitor_->WasRecentlyAudible()); } void ExpectIsNotifyingForToggleOff() { EXPECT_FALSE(monitor_->WasRecentlyAudible()); } MockWebContentsDelegate mock_web_contents_delegate_; base::SimpleTestTickClock clock_; std::map current_power_; DISALLOW_COPY_AND_ASSIGN(AudioStreamMonitorTest); }; // Tests that AudioStreamMonitor is polling while it has a // ReadPowerAndClipCallback, and is not polling at other times. TEST_F(AudioStreamMonitorTest, PollsWhenProvidedACallback) { EXPECT_FALSE(monitor_->WasRecentlyAudible()); ExpectIsPolling(kRenderProcessId, kStreamId, false); StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); EXPECT_FALSE(monitor_->WasRecentlyAudible()); ExpectIsPolling(kRenderProcessId, kStreamId, true); StopMonitoring(kRenderProcessId, kStreamId); EXPECT_FALSE(monitor_->WasRecentlyAudible()); ExpectIsPolling(kRenderProcessId, kStreamId, false); } // Tests that AudioStreamMonitor debounces the power level readings it's taking, // which could be fluctuating rapidly between the audible versus silence // threshold. See comments in audio_stream_monitor.h for expected behavior. TEST_F(AudioStreamMonitorTest, ImpulsesKeepIndicatorOnUntilHoldingPeriodHasPassed) { StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); // Expect WebContents will get one call form AudioStreamMonitor to toggle the // indicator on upon the very first poll. ExpectWebContentsWillBeNotifiedOnce(true); // Loop, each time testing a slightly longer period of polled silence. The // indicator should remain on throughout. int num_silence_polls = 0; base::TimeTicks last_blurt_time; do { // Poll an audible signal, and expect tab indicator state is on. SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power()); last_blurt_time = GetTestClockTime(); SimulatePollTimerFired(); ExpectTabWasRecentlyAudible(true, last_blurt_time); AdvanceClock(one_polling_interval()); // Poll a silent signal repeatedly, ensuring that the indicator is being // held on during the holding period. SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power()); for (int i = 0; i < num_silence_polls; ++i) { SimulatePollTimerFired(); ExpectTabWasRecentlyAudible(true, last_blurt_time); // Note: Redundant off timer firings should not have any effect. SimulateOffTimerFired(); ExpectTabWasRecentlyAudible(true, last_blurt_time); AdvanceClock(one_polling_interval()); } ++num_silence_polls; } while (GetTestClockTime() < last_blurt_time + holding_period()); // At this point, the clock has just advanced to beyond the holding period, so // the next firing of the off timer should turn off the tab indicator. Also, // make sure it stays off for several cycles thereafter. ExpectWebContentsWillBeNotifiedOnce(false); for (int i = 0; i < 10; ++i) { SimulateOffTimerFired(); ExpectTabWasRecentlyAudible(false, last_blurt_time); AdvanceClock(one_polling_interval()); } } // Tests that the AudioStreamMonitor correctly processes the blurts from two // different streams in the same tab. TEST_F(AudioStreamMonitorTest, HandlesMultipleStreamsBlurting) { StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); StartMonitoring( kRenderProcessId, kAnotherStreamId, CreatePollCallback(kAnotherStreamId)); base::TimeTicks last_blurt_time; ExpectTabWasRecentlyAudible(false, last_blurt_time); // Measure audible sound from the first stream and silence from the second. // The indicator turns on (i.e., tab was recently audible). ExpectWebContentsWillBeNotifiedOnce(true); SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power()); SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power()); last_blurt_time = GetTestClockTime(); SimulatePollTimerFired(); ExpectTabWasRecentlyAudible(true, last_blurt_time); // Halfway through the holding period, the second stream joins in. The // indicator stays on. AdvanceClock(holding_period() / 2); SimulateOffTimerFired(); SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::max_power()); last_blurt_time = GetTestClockTime(); SimulatePollTimerFired(); // Restarts holding period. ExpectTabWasRecentlyAudible(true, last_blurt_time); // Now, measure silence from both streams. After an entire holding period // has passed (since the second stream joined in), the indicator should turn // off. ExpectWebContentsWillBeNotifiedOnce(false); AdvanceClock(holding_period()); SimulateOffTimerFired(); SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power()); SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power()); SimulatePollTimerFired(); ExpectTabWasRecentlyAudible(false, last_blurt_time); // Now, measure silence from the first stream and audible sound from the // second. The indicator turns back on. ExpectWebContentsWillBeNotifiedOnce(true); SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::max_power()); last_blurt_time = GetTestClockTime(); SimulatePollTimerFired(); ExpectTabWasRecentlyAudible(true, last_blurt_time); // From here onwards, both streams are silent. Halfway through the holding // period, the indicator should not have changed. SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power()); AdvanceClock(holding_period() / 2); SimulatePollTimerFired(); SimulateOffTimerFired(); ExpectTabWasRecentlyAudible(true, last_blurt_time); // Just past the holding period, the indicator should be turned off. ExpectWebContentsWillBeNotifiedOnce(false); AdvanceClock(holding_period() - (GetTestClockTime() - last_blurt_time)); SimulateOffTimerFired(); ExpectTabWasRecentlyAudible(false, last_blurt_time); // Polling should not turn the indicator back while both streams are remaining // silent. for (int i = 0; i < 100; ++i) { AdvanceClock(one_polling_interval()); SimulatePollTimerFired(); ExpectTabWasRecentlyAudible(false, last_blurt_time); } } TEST_F(AudioStreamMonitorTest, MultipleRendererProcesses) { StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); StartMonitoring( kAnotherRenderProcessId, kStreamId, CreatePollCallback(kStreamId)); ExpectIsPolling(kRenderProcessId, kStreamId, true); ExpectIsPolling(kAnotherRenderProcessId, kStreamId, true); StopMonitoring(kAnotherRenderProcessId, kStreamId); ExpectIsPolling(kRenderProcessId, kStreamId, true); ExpectIsPolling(kAnotherRenderProcessId, kStreamId, false); } } // namespace content