// 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 <map>
#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/macros.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(WebContents* source, InvalidateTypes changed_flags));
};

}  // namespace

class AudioStreamMonitorTest : public RenderViewHostTestHarness {
 public:
  AudioStreamMonitorTest() {
    // Start |clock_| at non-zero.
    clock_.Advance(base::TimeDelta::FromSeconds(1000000));
  }

  void SetUp() override {
    RenderViewHostTestHarness::SetUp();

    WebContentsImpl* web_contents = reinterpret_cast<WebContentsImpl*>(
        RenderViewHostTestHarness::web_contents());
    web_contents->SetDelegate(&mock_web_contents_delegate_);

    monitor_ = web_contents->audio_stream_monitor();
    const_cast<base::TickClock*&>(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 ExpectIsCurrentlyAudible() const {
    EXPECT_TRUE(monitor_->IsCurrentlyAudible());
  }

  void ExpectNotCurrentlyAudible() const {
    EXPECT_FALSE(monitor_->IsCurrentlyAudible());
  }

  void ExpectRecentlyAudibleChangeNotification(bool new_recently_audible) {
    EXPECT_CALL(
        mock_web_contents_delegate_,
        NavigationStateChanged(RenderViewHostTestHarness::web_contents(),
                               INVALIDATE_TYPE_TAB))
        .WillOnce(InvokeWithoutArgs(
            this,
            new_recently_audible
                ? &AudioStreamMonitorTest::ExpectWasRecentlyAudible
                : &AudioStreamMonitorTest::ExpectNotRecentlyAudible))
        .RetiresOnSaturation();
  }

  void ExpectCurrentlyAudibleChangeNotification(bool new_audible) {
    EXPECT_CALL(
        mock_web_contents_delegate_,
        NavigationStateChanged(RenderViewHostTestHarness::web_contents(),
                               INVALIDATE_TYPE_TAB))
        .WillOnce(InvokeWithoutArgs(
            this,
            new_audible
                ? &AudioStreamMonitorTest::ExpectIsCurrentlyAudible
                : &AudioStreamMonitorTest::ExpectNotCurrentlyAudible))
        .RetiresOnSaturation();
  }

  static base::TimeDelta one_polling_interval() {
    return base::TimeDelta::FromSeconds(1) /
           static_cast<int>(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<float, bool> ReadPower(int stream_id) {
    return std::make_pair(current_power_[stream_id], false);
  }

  void ExpectWasRecentlyAudible() const {
    EXPECT_TRUE(monitor_->WasRecentlyAudible());
  }

  void ExpectNotRecentlyAudible() const {
    EXPECT_FALSE(monitor_->WasRecentlyAudible());
  }

  MockWebContentsDelegate mock_web_contents_delegate_;
  base::SimpleTestTickClock clock_;
  std::map<int, float> 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());
  ExpectNotCurrentlyAudible();
  ExpectIsPolling(kRenderProcessId, kStreamId, false);

  StartMonitoring(kRenderProcessId, kStreamId, CreatePollCallback(kStreamId));
  EXPECT_FALSE(monitor_->WasRecentlyAudible());
  ExpectNotCurrentlyAudible();
  ExpectIsPolling(kRenderProcessId, kStreamId, true);

  StopMonitoring(kRenderProcessId, kStreamId);
  EXPECT_FALSE(monitor_->WasRecentlyAudible());
  ExpectNotCurrentlyAudible();
  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.
  ExpectRecentlyAudibleChangeNotification(true);

  // Loop, each time testing a slightly longer period of polled silence.  The
  // recently audible state should not change while the currently audible one
  // should.
  int num_silence_polls = 1;
  base::TimeTicks last_blurt_time;
  do {
    ExpectCurrentlyAudibleChangeNotification(true);

    // 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());

    ExpectCurrentlyAudibleChangeNotification(false);

    // 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.
  ExpectRecentlyAudibleChangeNotification(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);
  ExpectNotCurrentlyAudible();

  // Measure audible sound from the first stream and silence from the second.
  // The tab becomes audible.
  ExpectRecentlyAudibleChangeNotification(true);
  ExpectCurrentlyAudibleChangeNotification(true);

  SetStreamPower(kStreamId, media::AudioPowerMonitor::max_power());
  SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power());
  last_blurt_time = GetTestClockTime();
  SimulatePollTimerFired();
  ExpectTabWasRecentlyAudible(true, last_blurt_time);
  ExpectIsCurrentlyAudible();

  // 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);
  ExpectIsCurrentlyAudible();

  // Now, measure silence from both streams.  After an entire holding period
  // has passed (since the second stream joined in), the tab will no longer
  // become audible nor recently audible.
  ExpectCurrentlyAudibleChangeNotification(false);
  ExpectRecentlyAudibleChangeNotification(false);

  AdvanceClock(holding_period());
  SimulateOffTimerFired();
  SetStreamPower(kStreamId, media::AudioPowerMonitor::zero_power());
  SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power());
  SimulatePollTimerFired();
  ExpectTabWasRecentlyAudible(false, last_blurt_time);
  ExpectNotCurrentlyAudible();

  // Now, measure silence from the first stream and audible sound from the
  // second.  The tab becomes audible again.
  ExpectRecentlyAudibleChangeNotification(true);
  ExpectCurrentlyAudibleChangeNotification(true);

  SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::max_power());
  last_blurt_time = GetTestClockTime();
  SimulatePollTimerFired();
  ExpectTabWasRecentlyAudible(true, last_blurt_time);
  ExpectIsCurrentlyAudible();

  // From here onwards, both streams are silent.  Halfway through the holding
  // period, the tab is no longer audible but stays as recently audible.
  ExpectCurrentlyAudibleChangeNotification(false);

  SetStreamPower(kAnotherStreamId, media::AudioPowerMonitor::zero_power());
  AdvanceClock(holding_period() / 2);
  SimulatePollTimerFired();
  SimulateOffTimerFired();
  ExpectTabWasRecentlyAudible(true, last_blurt_time);
  ExpectNotCurrentlyAudible();

  // Just past the holding period, the tab is no longer marked as recently
  // audible.
  ExpectRecentlyAudibleChangeNotification(false);

  AdvanceClock(holding_period() - (GetTestClockTime() - last_blurt_time));
  SimulateOffTimerFired();
  ExpectTabWasRecentlyAudible(false, last_blurt_time);
  ExpectNotCurrentlyAudible();

  // 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);
    ExpectNotCurrentlyAudible();
  }
}

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
