// 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 <string>
#include <queue>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/renderer_host/media/audio_input_device_manager.h"
#include "content/browser/renderer_host/media/media_stream_dispatcher_host.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/media/media_stream_messages.h"
#include "content/common/media/media_stream_options.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/mock_resource_context.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_content_client.h"
#include "ipc/ipc_message_macros.h"
#include "media/audio/mock_audio_manager.h"
#include "media/base/media_switches.h"
#include "media/video/capture/fake_video_capture_device_factory.h"
#include "net/url_request/url_request_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::DeleteArg;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::SaveArg;

const int kProcessId = 5;
const int kRenderId = 6;
const int kPageRequestId = 7;

namespace content {

class MockMediaStreamDispatcherHost : public MediaStreamDispatcherHost,
                                      public TestContentBrowserClient {
 public:
  MockMediaStreamDispatcherHost(
      const ResourceContext::SaltCallback salt_callback,
      const scoped_refptr<base::MessageLoopProxy>& message_loop,
      MediaStreamManager* manager,
      ResourceContext* resource_context)
      : MediaStreamDispatcherHost(kProcessId, salt_callback, manager,
                                  resource_context),
        message_loop_(message_loop),
        current_ipc_(NULL) {}

  // A list of mock methods.
  MOCK_METHOD4(OnStreamGenerated,
               void(int routing_id, int request_id, int audio_array_size,
                    int video_array_size));
  MOCK_METHOD3(OnStreamGenerationFailed, void(int routing_id,
                                              int request_id,
                                              MediaStreamRequestResult result));
  MOCK_METHOD1(OnDeviceStopped, void(int routing_id));
  MOCK_METHOD2(OnDeviceOpened, void(int routing_id, int request_id));

  // Accessor to private functions.
  void OnGenerateStream(int render_view_id,
                        int page_request_id,
                        const StreamOptions& components,
                        const GURL& security_origin,
                        const base::Closure& quit_closure) {
    quit_closures_.push(quit_closure);
    MediaStreamDispatcherHost::OnGenerateStream(
        render_view_id, page_request_id, components, security_origin, false);
  }

  void OnStopStreamDevice(int render_view_id,
                          const std::string& device_id) {
    MediaStreamDispatcherHost::OnStopStreamDevice(render_view_id, device_id);
  }

  void OnOpenDevice(int render_view_id,
                    int page_request_id,
                    const std::string& device_id,
                    MediaStreamType type,
                    const GURL& security_origin,
                    const base::Closure& quit_closure) {
    quit_closures_.push(quit_closure);
    MediaStreamDispatcherHost::OnOpenDevice(
        render_view_id, page_request_id, device_id, type, security_origin);
  }

  void OnEnumerateDevices(int render_view_id,
                          int page_request_id,
                          MediaStreamType type,
                          const GURL& security_origin,
                          bool hide_labels_if_no_access,
                          const base::Closure& quit_closure) {
    quit_closures_.push(quit_closure);
    MediaStreamDispatcherHost::OnEnumerateDevices(
        render_view_id, page_request_id, type, security_origin,
        hide_labels_if_no_access);
  }

  std::string label_;
  StreamDeviceInfoArray audio_devices_;
  StreamDeviceInfoArray video_devices_;
  StreamDeviceInfo opened_device_;
  StreamDeviceInfoArray enumerated_devices_;

 private:
  virtual ~MockMediaStreamDispatcherHost() {}

  // This method is used to dispatch IPC messages to the renderer. We intercept
  // these messages here and dispatch to our mock methods to verify the
  // conversation between this object and the renderer.
  virtual bool Send(IPC::Message* message) OVERRIDE {
    CHECK(message);
    current_ipc_ = message;

    // In this method we dispatch the messages to the according handlers as if
    // we are the renderer.
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(MockMediaStreamDispatcherHost, *message)
      IPC_MESSAGE_HANDLER(MediaStreamMsg_StreamGenerated,
                          OnStreamGeneratedInternal)
      IPC_MESSAGE_HANDLER(MediaStreamMsg_StreamGenerationFailed,
                          OnStreamGenerationFailedInternal)
      IPC_MESSAGE_HANDLER(MediaStreamMsg_DeviceStopped, OnDeviceStoppedInternal)
      IPC_MESSAGE_HANDLER(MediaStreamMsg_DeviceOpened, OnDeviceOpenedInternal)
      IPC_MESSAGE_HANDLER(MediaStreamMsg_DevicesEnumerated, OnDevicesEnumerated)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    EXPECT_TRUE(handled);

    delete message;
    current_ipc_ = NULL;
    return true;
  }

  // These handler methods do minimal things and delegate to the mock methods.
  void OnStreamGeneratedInternal(
      int request_id,
      std::string label,
      StreamDeviceInfoArray audio_device_list,
      StreamDeviceInfoArray video_device_list) {
    OnStreamGenerated(current_ipc_->routing_id(), request_id,
                      audio_device_list.size(), video_device_list.size());
    // Notify that the event have occurred.
    base::Closure quit_closure = quit_closures_.front();
    quit_closures_.pop();
    message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));

    label_ = label;
    audio_devices_ = audio_device_list;
    video_devices_ = video_device_list;
  }

  void OnStreamGenerationFailedInternal(
      int request_id,
      content::MediaStreamRequestResult result) {
    OnStreamGenerationFailed(current_ipc_->routing_id(), request_id, result);
    if (!quit_closures_.empty()) {
      base::Closure quit_closure = quit_closures_.front();
      quit_closures_.pop();
      message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));
    }

    label_= "";
  }

  void OnDeviceStoppedInternal(const std::string& label,
                               const content::StreamDeviceInfo& device) {
    if (IsVideoMediaType(device.device.type))
      EXPECT_TRUE(StreamDeviceInfo::IsEqual(device, video_devices_[0]));
    if (IsAudioInputMediaType(device.device.type))
      EXPECT_TRUE(StreamDeviceInfo::IsEqual(device, audio_devices_[0]));

    OnDeviceStopped(current_ipc_->routing_id());
  }

  void OnDeviceOpenedInternal(int request_id,
                              const std::string& label,
                              const StreamDeviceInfo& device) {
    base::Closure quit_closure = quit_closures_.front();
    quit_closures_.pop();
    message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));
    label_ = label;
    opened_device_ = device;
  }

  void OnDevicesEnumerated(int request_id,
                           const StreamDeviceInfoArray& devices) {
    base::Closure quit_closure = quit_closures_.front();
    quit_closures_.pop();
    message_loop_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));
    enumerated_devices_ = devices;
  }

  scoped_refptr<base::MessageLoopProxy> message_loop_;
  IPC::Message* current_ipc_;
  std::queue<base::Closure> quit_closures_;
};

class MockMediaStreamUIProxy : public FakeMediaStreamUIProxy {
 public:
  MOCK_METHOD2(
      OnStarted,
      void(const base::Closure& stop,
           const MediaStreamUIProxy::WindowIdCallback& window_id_callback));
};

class MediaStreamDispatcherHostTest : public testing::Test {
 public:
  MediaStreamDispatcherHostTest()
      : old_browser_client_(NULL),
        thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
        origin_("https://test.com") {
    audio_manager_.reset(
        new media::MockAudioManager(base::MessageLoopProxy::current()));
    // Make sure we use fake devices to avoid long delays.
    CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kUseFakeDeviceForMediaStream);
    // Create our own MediaStreamManager.
    media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get()));
    video_capture_device_factory_ =
        static_cast<media::FakeVideoCaptureDeviceFactory*>(
            media_stream_manager_->video_capture_manager()
            ->video_capture_device_factory());
    DCHECK(video_capture_device_factory_);

    MockResourceContext* mock_resource_context =
        static_cast<MockResourceContext*>(
            browser_context_.GetResourceContext());
    mock_resource_context->set_mic_access(true);
    mock_resource_context->set_camera_access(true);

    host_ = new MockMediaStreamDispatcherHost(
        mock_resource_context->GetMediaDeviceIDSalt(),
        base::MessageLoopProxy::current(),
        media_stream_manager_.get(),
        mock_resource_context);

    // Use the fake content client and browser.
    content_client_.reset(new TestContentClient());
    SetContentClient(content_client_.get());
    old_browser_client_ = SetBrowserClientForTesting(host_.get());
  }

  virtual ~MediaStreamDispatcherHostTest() {
  }

  virtual void SetUp() OVERRIDE {
    video_capture_device_factory_->GetDeviceNames(&physical_video_devices_);
    ASSERT_GT(physical_video_devices_.size(), 0u);

    media_stream_manager_->audio_input_device_manager()->GetFakeDeviceNames(
        &physical_audio_devices_);
    ASSERT_GT(physical_audio_devices_.size(), 0u);
  }

  virtual void TearDown() OVERRIDE {
    host_->OnChannelClosing();
  }

 protected:
  virtual void SetupFakeUI(bool expect_started) {
    scoped_ptr<MockMediaStreamUIProxy> stream_ui(new MockMediaStreamUIProxy());
    if (expect_started) {
      EXPECT_CALL(*stream_ui, OnStarted(_, _));
    }
    media_stream_manager_->UseFakeUI(
        stream_ui.PassAs<FakeMediaStreamUIProxy>());
  }

  void GenerateStreamAndWaitForResult(int render_view_id,
                                      int page_request_id,
                                      const StreamOptions& options) {
    base::RunLoop run_loop;
    int expected_audio_array_size =
        (options.audio_requested &&
         physical_audio_devices_.size() > 0) ? 1 : 0;
    int expected_video_array_size =
        (options.video_requested &&
         physical_video_devices_.size() > 0) ? 1 : 0;
    EXPECT_CALL(*host_.get(), OnStreamGenerated(render_view_id, page_request_id,
                                                expected_audio_array_size,
                                                expected_video_array_size));
    host_->OnGenerateStream(render_view_id, page_request_id, options, origin_,
                            run_loop.QuitClosure());
    run_loop.Run();
    EXPECT_FALSE(DoesContainRawIds(host_->audio_devices_));
    EXPECT_FALSE(DoesContainRawIds(host_->video_devices_));
    EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->audio_devices_, origin_));
    EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->video_devices_, origin_));
  }

  void GenerateStreamAndWaitForFailure(
    int render_view_id,
    int page_request_id,
    const StreamOptions& options,
    MediaStreamRequestResult expected_result) {
      base::RunLoop run_loop;
      EXPECT_CALL(*host_.get(),
                  OnStreamGenerationFailed(render_view_id,
                                           page_request_id,
                                           expected_result));
      host_->OnGenerateStream(render_view_id, page_request_id, options, origin_,
                              run_loop.QuitClosure());
      run_loop.Run();
  }

  void OpenVideoDeviceAndWaitForResult(int render_view_id,
                                       int page_request_id,
                                       const std::string& device_id) {
    base::RunLoop run_loop;
    host_->OnOpenDevice(render_view_id, page_request_id, device_id,
                        MEDIA_DEVICE_VIDEO_CAPTURE, origin_,
                        run_loop.QuitClosure());
    run_loop.Run();
    EXPECT_FALSE(DoesContainRawIds(host_->video_devices_));
    EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->video_devices_, origin_));
  }

  void EnumerateDevicesAndWaitForResult(int render_view_id,
                                        int page_request_id,
                                        MediaStreamType type,
                                        bool hide_labels_if_no_access) {
    base::RunLoop run_loop;
    host_->OnEnumerateDevices(render_view_id, page_request_id, type, origin_,
                              hide_labels_if_no_access, run_loop.QuitClosure());
    run_loop.Run();
    ASSERT_FALSE(host_->enumerated_devices_.empty());
    EXPECT_FALSE(DoesContainRawIds(host_->enumerated_devices_));
    EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->enumerated_devices_, origin_));
  }

  bool DoesContainRawIds(const StreamDeviceInfoArray& devices) {
    for (size_t i = 0; i < devices.size(); ++i) {
      media::AudioDeviceNames::const_iterator audio_it =
          physical_audio_devices_.begin();
      for (; audio_it != physical_audio_devices_.end(); ++audio_it) {
        if (audio_it->unique_id == devices[i].device.id)
          return true;
      }
      media::VideoCaptureDevice::Names::const_iterator video_it =
          physical_video_devices_.begin();
      for (; video_it != physical_video_devices_.end(); ++video_it) {
        if (video_it->id() == devices[i].device.id)
          return true;
      }
    }
    return false;
  }

  bool DoesEveryDeviceMapToRawId(const StreamDeviceInfoArray& devices,
                                 const GURL& origin) {
    for (size_t i = 0; i < devices.size(); ++i) {
      bool found_match = false;
      media::AudioDeviceNames::const_iterator audio_it =
          physical_audio_devices_.begin();
      for (; audio_it != physical_audio_devices_.end(); ++audio_it) {
        if (content::DoesMediaDeviceIDMatchHMAC(
                browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
                origin,
                devices[i].device.id,
                audio_it->unique_id)) {
          EXPECT_FALSE(found_match);
          found_match = true;
        }
      }
      media::VideoCaptureDevice::Names::const_iterator video_it =
          physical_video_devices_.begin();
      for (; video_it != physical_video_devices_.end(); ++video_it) {
        if (content::DoesMediaDeviceIDMatchHMAC(
                browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
                origin,
                devices[i].device.id,
                video_it->id())) {
          EXPECT_FALSE(found_match);
          found_match = true;
        }
      }
      if (!found_match)
        return false;
    }
    return true;
  }

  // Returns true if all devices have labels, false otherwise.
  bool DoesContainLabels(const StreamDeviceInfoArray& devices) {
    for (size_t i = 0; i < devices.size(); ++i) {
      if (devices[i].device.name.empty())
        return false;
    }
    return true;
  }

  // Returns true if no devices have labels, false otherwise.
  bool DoesNotContainLabels(const StreamDeviceInfoArray& devices) {
    for (size_t i = 0; i < devices.size(); ++i) {
      if (!devices[i].device.name.empty())
        return false;
    }
    return true;
  }

  void AddSourceIdConstraint(const std::string& source_id,
                             StreamOptions::Constraints* constraints) {
    constraints->push_back(StreamOptions::Constraint(kMediaStreamSourceInfoId,
                                                     source_id));
  }

  scoped_refptr<MockMediaStreamDispatcherHost> host_;
  scoped_ptr<media::AudioManager> audio_manager_;
  scoped_ptr<MediaStreamManager> media_stream_manager_;
  ContentBrowserClient* old_browser_client_;
  scoped_ptr<ContentClient> content_client_;
  content::TestBrowserThreadBundle thread_bundle_;
  content::TestBrowserContext browser_context_;
  media::AudioDeviceNames physical_audio_devices_;
  media::VideoCaptureDevice::Names physical_video_devices_;
  GURL origin_;
  media::FakeVideoCaptureDeviceFactory* video_capture_device_factory_;
};

TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithVideoOnly) {
  StreamOptions options(false, true);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
}

TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithAudioOnly) {
  StreamOptions options(true, false);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  EXPECT_EQ(host_->audio_devices_.size(), 1u);
  EXPECT_EQ(host_->video_devices_.size(), 0u);
}

TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithNothing) {
  StreamOptions options(false, false);

  GenerateStreamAndWaitForFailure(
      kRenderId,
      kPageRequestId,
      options,
      MEDIA_DEVICE_INVALID_STATE);
}

TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithAudioAndVideo) {
  StreamOptions options(true, true);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  EXPECT_EQ(host_->audio_devices_.size(), 1u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
}

// This test generates two streams with video only using the same render view
// id. The same capture device  with the same device and session id is expected
// to be used.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsFromSameRenderId) {
  StreamOptions options(false, true);

  // Generate first stream.
  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  // Check the latest generated stream.
  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
  const std::string label1 = host_->label_;
  const std::string device_id1 = host_->video_devices_.front().device.id;
  const int session_id1 = host_->video_devices_.front().session_id;

  // Generate second stream.
  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId + 1, options);

  // Check the latest generated stream.
  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
  const std::string label2 = host_->label_;
  const std::string device_id2 = host_->video_devices_.front().device.id;
  int session_id2 = host_->video_devices_.front().session_id;
  EXPECT_EQ(device_id1, device_id2);
  EXPECT_EQ(session_id1, session_id2);
  EXPECT_NE(label1, label2);
}

TEST_F(MediaStreamDispatcherHostTest,
       GenerateStreamAndOpenDeviceFromSameRenderId) {
  StreamOptions options(false, true);

  // Generate first stream.
  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
  const std::string label1 = host_->label_;
  const std::string device_id1 = host_->video_devices_.front().device.id;
  const int session_id1 = host_->video_devices_.front().session_id;

  // Generate second stream.
  OpenVideoDeviceAndWaitForResult(kRenderId, kPageRequestId, device_id1);

  const std::string device_id2 = host_->opened_device_.device.id;
  const int session_id2 = host_->opened_device_.session_id;
  const std::string label2 = host_->label_;

  EXPECT_EQ(device_id1, device_id2);
  EXPECT_NE(session_id1, session_id2);
  EXPECT_NE(label1, label2);
}


// This test generates two streams with video only using two separate render
// view ids. The same device id but different session ids are expected.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsDifferentRenderId) {
  StreamOptions options(false, true);

  // Generate first stream.
  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  // Check the latest generated stream.
  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
  const std::string label1 = host_->label_;
  const std::string device_id1 = host_->video_devices_.front().device.id;
  const int session_id1 = host_->video_devices_.front().session_id;

  // Generate second stream from another render view.
  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId+1, kPageRequestId + 1, options);

  // Check the latest generated stream.
  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);
  const std::string label2 = host_->label_;
  const std::string device_id2 = host_->video_devices_.front().device.id;
  const int session_id2 = host_->video_devices_.front().session_id;
  EXPECT_EQ(device_id1, device_id2);
  EXPECT_NE(session_id1, session_id2);
  EXPECT_NE(label1, label2);
}

// This test request two streams with video only without waiting for the first
// stream to be generated before requesting the second.
// The same device id and session ids are expected.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithoutWaiting) {
  StreamOptions options(false, true);

  // Generate first stream.
  SetupFakeUI(true);
  {
    InSequence s;
    EXPECT_CALL(*host_.get(),
                OnStreamGenerated(kRenderId, kPageRequestId, 0, 1));

    // Generate second stream.
    EXPECT_CALL(*host_.get(),
                OnStreamGenerated(kRenderId, kPageRequestId + 1, 0, 1));
  }
  base::RunLoop run_loop1;
  base::RunLoop run_loop2;
  host_->OnGenerateStream(kRenderId, kPageRequestId, options, origin_,
                          run_loop1.QuitClosure());
  host_->OnGenerateStream(kRenderId, kPageRequestId + 1, options, origin_,
                          run_loop2.QuitClosure());

  run_loop1.Run();
  run_loop2.Run();
}

// Test that we can generate streams where a mandatory sourceId is specified in
// the request.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithMandatorySourceId) {
  ASSERT_GE(physical_audio_devices_.size(), 1u);
  ASSERT_GE(physical_video_devices_.size(), 1u);

  media::AudioDeviceNames::const_iterator audio_it =
      physical_audio_devices_.begin();
  for (; audio_it != physical_audio_devices_.end(); ++audio_it) {
    std::string source_id = content::GetHMACForMediaDeviceID(
        browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
        origin_,
        audio_it->unique_id);
    ASSERT_FALSE(source_id.empty());
    StreamOptions options(true, true);
    AddSourceIdConstraint(source_id, &options.mandatory_audio);

    SetupFakeUI(true);
    GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
    EXPECT_EQ(host_->audio_devices_[0].device.id, source_id);
  }

  media::VideoCaptureDevice::Names::const_iterator video_it =
      physical_video_devices_.begin();
  for (; video_it != physical_video_devices_.end(); ++video_it) {
    std::string source_id = content::GetHMACForMediaDeviceID(
        browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
        origin_,
        video_it->id());
    ASSERT_FALSE(source_id.empty());
    StreamOptions options(true, true);
    AddSourceIdConstraint(source_id, &options.mandatory_video);

    SetupFakeUI(true);
    GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
    EXPECT_EQ(host_->video_devices_[0].device.id, source_id);
  }
}

// Test that we can generate streams where a optional sourceId is specified in
// the request.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithOptionalSourceId) {
  ASSERT_GE(physical_audio_devices_.size(), 1u);
  ASSERT_GE(physical_video_devices_.size(), 1u);

  media::AudioDeviceNames::const_iterator audio_it =
      physical_audio_devices_.begin();
  for (; audio_it != physical_audio_devices_.end(); ++audio_it) {
    std::string source_id = content::GetHMACForMediaDeviceID(
        browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
        origin_,
        audio_it->unique_id);
    ASSERT_FALSE(source_id.empty());
    StreamOptions options(true, true);
    AddSourceIdConstraint(source_id, &options.optional_audio);

    SetupFakeUI(true);
    GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
    EXPECT_EQ(host_->audio_devices_[0].device.id, source_id);
  }

  media::VideoCaptureDevice::Names::const_iterator video_it =
      physical_video_devices_.begin();
  for (; video_it != physical_video_devices_.end(); ++video_it) {
    std::string source_id = content::GetHMACForMediaDeviceID(
        browser_context_.GetResourceContext()->GetMediaDeviceIDSalt(),
        origin_,
        video_it->id());
    ASSERT_FALSE(source_id.empty());
    StreamOptions options(true, true);
    AddSourceIdConstraint(source_id, &options.optional_video);

    SetupFakeUI(true);
    GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
    EXPECT_EQ(host_->video_devices_[0].device.id, source_id);
  }
}

// Test that generating a stream with an invalid mandatory video source id fail.
TEST_F(MediaStreamDispatcherHostTest,
       GenerateStreamsWithInvalidMandatoryVideoSourceId) {
  StreamOptions options(true, true);
  AddSourceIdConstraint("invalid source id", &options.mandatory_video);

  GenerateStreamAndWaitForFailure(
      kRenderId,
      kPageRequestId,
      options,
      MEDIA_DEVICE_NO_HARDWARE);
}

// Test that generating a stream with an invalid mandatory audio source id fail.
TEST_F(MediaStreamDispatcherHostTest,
       GenerateStreamsWithInvalidMandatoryAudioSourceId) {
  StreamOptions options(true, true);
  AddSourceIdConstraint("invalid source id", &options.mandatory_audio);

  GenerateStreamAndWaitForFailure(
      kRenderId,
      kPageRequestId,
      options,
      MEDIA_DEVICE_NO_HARDWARE);
}

// Test that generating a stream with an invalid optional video source id
// succeed.
TEST_F(MediaStreamDispatcherHostTest,
       GenerateStreamsWithInvalidOptionalVideoSourceId) {
  StreamOptions options(true, true);
  AddSourceIdConstraint("invalid source id", &options.optional_video);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
}

// Test that generating a stream with an invalid optional audio source id
// succeed.
TEST_F(MediaStreamDispatcherHostTest,
       GenerateStreamsWithInvalidOptionalAudioSourceId) {
  StreamOptions options(true, true);
  AddSourceIdConstraint("invalid source id", &options.optional_audio);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
}

TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsNoAvailableVideoDevice) {
  physical_video_devices_.clear();
  video_capture_device_factory_->set_number_of_devices(0);
  video_capture_device_factory_->GetDeviceNames(&physical_video_devices_);
  StreamOptions options(true, true);

  SetupFakeUI(false);
  GenerateStreamAndWaitForFailure(kRenderId, kPageRequestId, options,
                                  MEDIA_DEVICE_NO_HARDWARE);
}

// Test that if a OnStopStreamDevice message is received for a device that has
// been opened in a MediaStream and by pepper, the device is only stopped for
// the MediaStream.
TEST_F(MediaStreamDispatcherHostTest, StopDeviceInStream) {
  StreamOptions options(false, true);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  std::string stream_request_label = host_->label_;
  StreamDeviceInfo video_device_info = host_->video_devices_.front();
  ASSERT_EQ(1u, media_stream_manager_->GetDevicesOpenedByRequest(
      stream_request_label).size());

  // Open the same device by Pepper.
  OpenVideoDeviceAndWaitForResult(kRenderId, kPageRequestId,
                                  video_device_info.device.id);
  std::string open_device_request_label = host_->label_;

  // Stop the device in the MediaStream.
  host_->OnStopStreamDevice(kRenderId, video_device_info.device.id);

  EXPECT_EQ(0u, media_stream_manager_->GetDevicesOpenedByRequest(
      stream_request_label).size());
  EXPECT_EQ(1u, media_stream_manager_->GetDevicesOpenedByRequest(
      open_device_request_label).size());
}

TEST_F(MediaStreamDispatcherHostTest, StopDeviceInStreamAndRestart) {
  StreamOptions options(true, true);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  std::string request_label1 = host_->label_;
  StreamDeviceInfo video_device_info = host_->video_devices_.front();
  // Expect that 1 audio and 1 video device has been opened.
  EXPECT_EQ(2u, media_stream_manager_->GetDevicesOpenedByRequest(
      request_label1).size());

  host_->OnStopStreamDevice(kRenderId, video_device_info.device.id);
  EXPECT_EQ(1u, media_stream_manager_->GetDevicesOpenedByRequest(
      request_label1).size());

  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
  std::string request_label2 = host_->label_;

  StreamDeviceInfoArray request1_devices =
      media_stream_manager_->GetDevicesOpenedByRequest(request_label1);
  StreamDeviceInfoArray request2_devices =
      media_stream_manager_->GetDevicesOpenedByRequest(request_label2);

  ASSERT_EQ(1u, request1_devices.size());
  ASSERT_EQ(2u, request2_devices.size());

  // Test that the same audio device has been opened in both streams.
  EXPECT_TRUE(StreamDeviceInfo::IsEqual(request1_devices[0],
                                        request2_devices[0]) ||
              StreamDeviceInfo::IsEqual(request1_devices[0],
                                        request2_devices[1]));
}

TEST_F(MediaStreamDispatcherHostTest,
       GenerateTwoStreamsAndStopDeviceWhileWaitingForSecondStream) {
  StreamOptions options(false, true);

  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
  EXPECT_EQ(host_->video_devices_.size(), 1u);

  // Generate a second stream.
  EXPECT_CALL(*host_.get(),
              OnStreamGenerated(kRenderId, kPageRequestId + 1, 0, 1));

  base::RunLoop run_loop1;
  host_->OnGenerateStream(kRenderId, kPageRequestId + 1, options, origin_,
                          run_loop1.QuitClosure());

  // Stop the video stream device from stream 1 while waiting for the
  // second stream to be generated.
  host_->OnStopStreamDevice(kRenderId, host_->video_devices_[0].device.id);
  run_loop1.Run();

  EXPECT_EQ(host_->video_devices_.size(), 1u);
}

TEST_F(MediaStreamDispatcherHostTest, CancelPendingStreamsOnChannelClosing) {
  StreamOptions options(false, true);

  base::RunLoop run_loop;

  // Create multiple GenerateStream requests.
  size_t streams = 5;
  for (size_t i = 1; i <= streams; ++i) {
    host_->OnGenerateStream(kRenderId, kPageRequestId + i, options, origin_,
                            run_loop.QuitClosure());
  }

  // Calling OnChannelClosing() to cancel all the pending requests.
  host_->OnChannelClosing();
  run_loop.RunUntilIdle();
}

TEST_F(MediaStreamDispatcherHostTest, StopGeneratedStreamsOnChannelClosing) {
  StreamOptions options(false, true);

  // Create first group of streams.
  size_t generated_streams = 3;
  for (size_t i = 0; i < generated_streams; ++i) {
    SetupFakeUI(true);
    GenerateStreamAndWaitForResult(kRenderId, kPageRequestId + i, options);
  }

  // Calling OnChannelClosing() to cancel all the pending/generated streams.
  host_->OnChannelClosing();
  base::RunLoop().RunUntilIdle();
}

TEST_F(MediaStreamDispatcherHostTest, CloseFromUI) {
  StreamOptions options(false, true);

  base::Closure close_callback;
  scoped_ptr<MockMediaStreamUIProxy> stream_ui(new MockMediaStreamUIProxy());
  EXPECT_CALL(*stream_ui, OnStarted(_, _))
      .WillOnce(SaveArg<0>(&close_callback));
  media_stream_manager_->UseFakeUI(stream_ui.PassAs<FakeMediaStreamUIProxy>());

  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);

  EXPECT_EQ(host_->audio_devices_.size(), 0u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);

  ASSERT_FALSE(close_callback.is_null());
  EXPECT_CALL(*host_.get(), OnDeviceStopped(kRenderId));
  close_callback.Run();
  base::RunLoop().RunUntilIdle();
}

// Test that the dispatcher is notified if a video device that is in use is
// being unplugged.
TEST_F(MediaStreamDispatcherHostTest, VideoDeviceUnplugged) {
  StreamOptions options(true, true);
  SetupFakeUI(true);
  GenerateStreamAndWaitForResult(kRenderId, kPageRequestId, options);
  EXPECT_EQ(host_->audio_devices_.size(), 1u);
  EXPECT_EQ(host_->video_devices_.size(), 1u);

  video_capture_device_factory_->set_number_of_devices(0);

  base::RunLoop run_loop;
  EXPECT_CALL(*host_.get(), OnDeviceStopped(kRenderId))
      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
  media_stream_manager_->OnDevicesChanged(
      base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);

  run_loop.Run();
}

TEST_F(MediaStreamDispatcherHostTest, EnumerateAudioDevices) {
  EnumerateDevicesAndWaitForResult(kRenderId, kPageRequestId,
                                   MEDIA_DEVICE_AUDIO_CAPTURE, true);
  EXPECT_TRUE(DoesContainLabels(host_->enumerated_devices_));
}

TEST_F(MediaStreamDispatcherHostTest, EnumerateVideoDevices) {
  EnumerateDevicesAndWaitForResult(kRenderId, kPageRequestId,
                                   MEDIA_DEVICE_VIDEO_CAPTURE, true);
  EXPECT_TRUE(DoesContainLabels(host_->enumerated_devices_));
}

TEST_F(MediaStreamDispatcherHostTest, EnumerateAudioDevicesNoAccessHideLabels) {
  MockResourceContext* mock_resource_context =
      static_cast<MockResourceContext*>(browser_context_.GetResourceContext());
  mock_resource_context->set_mic_access(false);
  EnumerateDevicesAndWaitForResult(kRenderId, kPageRequestId,
                                   MEDIA_DEVICE_AUDIO_CAPTURE, true);
  EXPECT_TRUE(DoesNotContainLabels(host_->enumerated_devices_));
}

TEST_F(MediaStreamDispatcherHostTest, EnumerateVideoDevicesNoAccessHideLabels) {
  MockResourceContext* mock_resource_context =
      static_cast<MockResourceContext*>(browser_context_.GetResourceContext());
  mock_resource_context->set_camera_access(false);
  EnumerateDevicesAndWaitForResult(kRenderId, kPageRequestId,
                                   MEDIA_DEVICE_VIDEO_CAPTURE, true);
  EXPECT_TRUE(DoesNotContainLabels(host_->enumerated_devices_));
}

TEST_F(MediaStreamDispatcherHostTest,
       EnumerateAudioDevicesNoAccessNoHideLabels) {
  MockResourceContext* mock_resource_context =
      static_cast<MockResourceContext*>(browser_context_.GetResourceContext());
  mock_resource_context->set_mic_access(false);
  EnumerateDevicesAndWaitForResult(kRenderId, kPageRequestId,
                                   MEDIA_DEVICE_AUDIO_CAPTURE, false);
  EXPECT_TRUE(DoesContainLabels(host_->enumerated_devices_));
}

TEST_F(MediaStreamDispatcherHostTest,
       EnumerateVideoDevicesNoAccessNoHideLabels) {
  MockResourceContext* mock_resource_context =
      static_cast<MockResourceContext*>(browser_context_.GetResourceContext());
  mock_resource_context->set_camera_access(false);
  EnumerateDevicesAndWaitForResult(kRenderId, kPageRequestId,
                                   MEDIA_DEVICE_VIDEO_CAPTURE, false);
  EXPECT_TRUE(DoesContainLabels(host_->enumerated_devices_));
}

};  // namespace content
