// 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 "content/browser/renderer_host/media/media_stream_manager.h"

#include <list>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/threading/thread.h"
#include "content/browser/renderer_host/media/audio_input_device_manager.h"
#include "content/browser/renderer_host/media/device_request_message_filter.h"
#include "content/browser/renderer_host/media/media_stream_requester.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/browser/renderer_host/media/web_contents_capture_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/media_observer.h"
#include "content/public/browser/media_request_state.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/media_stream_request.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "url/gurl.h"

#if defined(OS_WIN)
#include "base/win/scoped_com_initializer.h"
#endif

namespace content {

// Creates a random label used to identify requests.
static std::string RandomLabel() {
  // An earlier PeerConnection spec,
  // http://dev.w3.org/2011/webrtc/editor/webrtc.html, specified the
  // MediaStream::label alphabet as containing 36 characters from
  // range: U+0021, U+0023 to U+0027, U+002A to U+002B, U+002D to U+002E,
  // U+0030 to U+0039, U+0041 to U+005A, U+005E to U+007E.
  // Here we use a safe subset.
  static const char kAlphabet[] = "0123456789"
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

  std::string label(36, ' ');
  for (size_t i = 0; i < label.size(); ++i) {
    int random_char = base::RandGenerator(sizeof(kAlphabet) - 1);
    label[i] = kAlphabet[random_char];
  }
  return label;
}

// Helper to verify if a media stream type is part of options or not.
static bool Requested(const MediaStreamRequest& request,
                      MediaStreamType stream_type) {
  return (request.audio_type == stream_type ||
          request.video_type == stream_type);
}

// TODO(xians): Merge DeviceRequest with MediaStreamRequest.
class MediaStreamManager::DeviceRequest {
 public:
  DeviceRequest(MediaStreamRequester* requester,
                const MediaStreamRequest& request)
      : requester(requester),
        request(request),
        state_(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_NOT_REQUESTED) {
  }

  ~DeviceRequest() {}

  // Update the request state and notify observers.
  void SetState(MediaStreamType stream_type, MediaRequestState new_state) {
    if (stream_type == NUM_MEDIA_TYPES) {
      for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) {
        const MediaStreamType stream_type = static_cast<MediaStreamType>(i);
        state_[stream_type] = new_state;
      }
    } else {
      state_[stream_type] = new_state;
    }

    if (request.video_type != MEDIA_TAB_VIDEO_CAPTURE &&
        request.audio_type != MEDIA_TAB_AUDIO_CAPTURE &&
        new_state != MEDIA_REQUEST_STATE_CLOSING) {
      return;
    }

    MediaObserver* media_observer =
        GetContentClient()->browser()->GetMediaObserver();
    if (media_observer == NULL)
      return;

    // If we appended a device_id scheme, we want to remove it when notifying
    // observers which may be in different modules since this scheme is only
    // used internally within the content module.
    std::string device_id =
        WebContentsCaptureUtil::StripWebContentsDeviceScheme(
            request.tab_capture_device_id);

    media_observer->OnMediaRequestStateChanged(
        request.render_process_id, request.render_view_id,
        request.page_request_id,
        MediaStreamDevice(stream_type, device_id, device_id), new_state);
  }

  MediaRequestState state(MediaStreamType stream_type) const {
    return state_[stream_type];
  }

  MediaStreamRequester* const requester;  // Can be NULL.
  MediaStreamRequest request;

  StreamDeviceInfoArray devices;

  // Callback to the requester which audio/video devices have been selected.
  // It can be null if the requester has no interest to know the result.
  // Currently it is only used by |DEVICE_ACCESS| type.
  MediaStreamManager::MediaRequestResponseCallback callback;

  scoped_ptr<MediaStreamUIProxy> ui_proxy;

 private:
  std::vector<MediaRequestState> state_;
};

MediaStreamManager::EnumerationCache::EnumerationCache()
    : valid(false) {
}

MediaStreamManager::EnumerationCache::~EnumerationCache() {
}

MediaStreamManager::MediaStreamManager()
    : audio_manager_(NULL),
      monitoring_started_(false),
      io_loop_(NULL),
      use_fake_ui_(false) {}

MediaStreamManager::MediaStreamManager(media::AudioManager* audio_manager)
    : audio_manager_(audio_manager),
      monitoring_started_(false),
      io_loop_(NULL),
      use_fake_ui_(false) {
  DCHECK(audio_manager_);
  memset(active_enumeration_ref_count_, 0,
         sizeof(active_enumeration_ref_count_));

  // Some unit tests create the MSM in the IO thread and assumes the
  // initialization is done synchronously.
  if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    InitializeDeviceManagersOnIOThread();
  } else {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&MediaStreamManager::InitializeDeviceManagersOnIOThread,
                   base::Unretained(this)));
  }
}

MediaStreamManager::~MediaStreamManager() {
  DCHECK(requests_.empty());
  DCHECK(!device_thread_.get());
}

VideoCaptureManager* MediaStreamManager::video_capture_manager() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(video_capture_manager_.get());
  return video_capture_manager_.get();
}

AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(audio_input_device_manager_.get());
  return audio_input_device_manager_.get();
}

std::string MediaStreamManager::MakeMediaAccessRequest(
    int render_process_id,
    int render_view_id,
    int page_request_id,
    const StreamOptions& options,
    const GURL& security_origin,
    const MediaRequestResponseCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  // Create a new request based on options.
  MediaStreamRequest stream_request(
      render_process_id, render_view_id, page_request_id, std::string(),
      security_origin, MEDIA_DEVICE_ACCESS, std::string(), std::string(),
      options.audio_type, options.video_type);
  DeviceRequest* request = new DeviceRequest(NULL, stream_request);
  const std::string& label = AddRequest(request);

  request->callback = callback;

  HandleRequest(label);

  return label;
}

std::string MediaStreamManager::GenerateStream(
    MediaStreamRequester* requester,
    int render_process_id,
    int render_view_id,
    int page_request_id,
    const StreamOptions& options,
    const GURL& security_origin) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kUseFakeDeviceForMediaStream)) {
    UseFakeDevice();
  }
  if (CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kUseFakeUIForMediaStream)) {
    UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy>());
  }

  int target_render_process_id = render_process_id;
  int target_render_view_id = render_view_id;
  std::string tab_capture_device_id;

  // Customize options for a WebContents based capture.
  if (options.audio_type == MEDIA_TAB_AUDIO_CAPTURE ||
      options.video_type == MEDIA_TAB_VIDEO_CAPTURE) {
    // TODO(justinlin): Can't plumb audio mirroring using stream type right
    // now, so plumbing by device_id. Will revisit once it's refactored.
    // http://crbug.com/163100
    tab_capture_device_id =
        WebContentsCaptureUtil::AppendWebContentsDeviceScheme(
            !options.video_device_id.empty() ?
            options.video_device_id : options.audio_device_id);

    bool has_valid_device_id = WebContentsCaptureUtil::ExtractTabCaptureTarget(
        tab_capture_device_id, &target_render_process_id,
        &target_render_view_id);
    if (!has_valid_device_id ||
        (options.audio_type != MEDIA_TAB_AUDIO_CAPTURE &&
         options.audio_type != MEDIA_NO_SERVICE) ||
        (options.video_type != MEDIA_TAB_VIDEO_CAPTURE &&
         options.video_type != MEDIA_NO_SERVICE)) {
      LOG(ERROR) << "Invalid request.";
      return std::string();
    }
  }

  std::string translated_audio_device_id;
  std::string translated_video_device_id;
  if (options.audio_type == MEDIA_DEVICE_AUDIO_CAPTURE) {
    bool found_match = TranslateGUIDToRawId(
        MEDIA_DEVICE_AUDIO_CAPTURE, security_origin, options.audio_device_id,
        &translated_audio_device_id);
    DCHECK(found_match || translated_audio_device_id.empty());
  }

  if (options.video_type == MEDIA_DEVICE_VIDEO_CAPTURE) {
    bool found_match = TranslateGUIDToRawId(
        MEDIA_DEVICE_VIDEO_CAPTURE, security_origin, options.video_device_id,
        &translated_video_device_id);
    DCHECK(found_match || translated_video_device_id.empty());
  }

  if (options.video_type == MEDIA_DESKTOP_VIDEO_CAPTURE ||
      options.audio_type == MEDIA_SYSTEM_AUDIO_CAPTURE) {
    // For screen capture we only support two valid combinations:
    // (1) screen video capture only, or
    // (2) screen video capture with system audio capture.
    if (options.video_type != MEDIA_DESKTOP_VIDEO_CAPTURE ||
        (options.audio_type != MEDIA_NO_SERVICE &&
         options.audio_type != MEDIA_SYSTEM_AUDIO_CAPTURE)) {
      // TODO(sergeyu): Surface error message to the calling JS code.
      LOG(ERROR) << "Invalid screen capture request.";
      return std::string();
    }
    translated_video_device_id = options.video_device_id;
  }

  // Create a new request based on options.
  MediaStreamRequest stream_request(
      target_render_process_id, target_render_view_id, page_request_id,
      tab_capture_device_id, security_origin, MEDIA_GENERATE_STREAM,
      translated_audio_device_id, translated_video_device_id,
      options.audio_type, options.video_type);
  DeviceRequest* request = new DeviceRequest(requester, stream_request);
  const std::string& label = AddRequest(request);
  HandleRequest(label);
  return label;
}

void MediaStreamManager::CancelRequest(const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  DeviceRequests::iterator it = requests_.find(label);
  if (it != requests_.end()) {
    if (!RequestDone(*it->second)) {
      // TODO(xians): update the |state| to STATE_DONE to trigger a state
      // changed notification to UI before deleting the request?
      scoped_ptr<DeviceRequest> request(it->second);
      RemoveRequest(it);
      for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) {
        const MediaStreamType stream_type = static_cast<MediaStreamType>(i);
        MediaStreamProvider* device_manager = GetDeviceManager(stream_type);
        if (!device_manager)
          continue;
        if (request->state(stream_type) != MEDIA_REQUEST_STATE_OPENING &&
            request->state(stream_type) != MEDIA_REQUEST_STATE_DONE) {
          continue;
        }
        for (StreamDeviceInfoArray::const_iterator device_it =
                 request->devices.begin();
             device_it != request->devices.end(); ++device_it) {
          if (device_it->device.type == stream_type) {
            device_manager->Close(device_it->session_id);
          }
        }
      }
      // Cancel the request if still pending at UI side.
      request->SetState(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_CLOSING);
    } else {
      StopGeneratedStream(label);
    }
  }
}

void MediaStreamManager::StopGeneratedStream(const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Find the request and close all open devices for the request.
  DeviceRequests::iterator it = requests_.find(label);
  if (it != requests_.end()) {
    if (it->second->request.request_type == MEDIA_ENUMERATE_DEVICES) {
      StopEnumerateDevices(label);
      return;
    }

    scoped_ptr<DeviceRequest> request(it->second);
    RemoveRequest(it);
    for (StreamDeviceInfoArray::const_iterator device_it =
             request->devices.begin();
         device_it != request->devices.end(); ++device_it) {
      GetDeviceManager(device_it->device.type)->Close(device_it->session_id);
    }
    if (request->request.request_type == MEDIA_GENERATE_STREAM &&
        RequestDone(*request)) {
      // Notify observers that this device is being closed.
      for (int i = MEDIA_NO_SERVICE + 1; i != NUM_MEDIA_TYPES; ++i) {
        if (request->state(static_cast<MediaStreamType>(i)) !=
            MEDIA_REQUEST_STATE_NOT_REQUESTED) {
          request->SetState(static_cast<MediaStreamType>(i),
                            MEDIA_REQUEST_STATE_CLOSING);
        }
      }
    }
  }
}

std::string MediaStreamManager::EnumerateDevices(
    MediaStreamRequester* requester,
    int render_process_id,
    int render_view_id,
    int page_request_id,
    MediaStreamType type,
    const GURL& security_origin) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE ||
         type == MEDIA_DEVICE_VIDEO_CAPTURE);

  // When the requester is NULL, the request is made by the UI to ensure MSM
  // starts monitoring devices.
  if (!requester) {
    if (!monitoring_started_)
      StartMonitoring();

    return std::string();
  }

  // Create a new request.
  StreamOptions options;
  EnumerationCache* cache = NULL;
  if (type == MEDIA_DEVICE_AUDIO_CAPTURE) {
    options.audio_type = type;
    cache = &audio_enumeration_cache_;
  } else if (type == MEDIA_DEVICE_VIDEO_CAPTURE) {
    options.video_type = type;
    cache = &video_enumeration_cache_;
  } else {
    NOTREACHED();
    return std::string();
  }

  MediaStreamRequest stream_request(
      render_process_id, render_view_id, page_request_id, std::string(),
      security_origin, MEDIA_ENUMERATE_DEVICES, std::string(), std::string(),
      options.audio_type, options.video_type);
  DeviceRequest* request = new DeviceRequest(requester, stream_request);
  const std::string& label = AddRequest(request);

  if (cache->valid) {
    // Cached device list of this type exists. Just send it out.
    request->SetState(type, MEDIA_REQUEST_STATE_REQUESTED);

    // Need to post a task since the requester won't have label till
    // this function returns.
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&MediaStreamManager::SendCachedDeviceList,
                   base::Unretained(this), cache, label));
  } else {
    StartEnumeration(request);
  }

  return label;
}

void MediaStreamManager::StopEnumerateDevices(const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  DeviceRequests::iterator it = requests_.find(label);
  if (it != requests_.end()) {
    DCHECK_EQ(it->second->request.request_type, MEDIA_ENUMERATE_DEVICES);
    // Delete the DeviceRequest.
    scoped_ptr<DeviceRequest> request(it->second);
    RemoveRequest(it);
  }
}

std::string MediaStreamManager::OpenDevice(
    MediaStreamRequester* requester,
    int render_process_id,
    int render_view_id,
    int page_request_id,
    const std::string& device_id,
    MediaStreamType type,
    const GURL& security_origin) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DCHECK(type == MEDIA_DEVICE_AUDIO_CAPTURE ||
         type == MEDIA_DEVICE_VIDEO_CAPTURE);

  // Create a new request.
  StreamOptions options;
  if (IsAudioMediaType(type)) {
    options.audio_type = type;
    options.audio_device_id = device_id;
  } else if (IsVideoMediaType(type)) {
    options.video_type = type;
    options.video_device_id = device_id;
  } else {
    NOTREACHED();
    return std::string();
  }

  MediaStreamRequest stream_request(
      render_process_id, render_view_id, page_request_id, std::string(),
      security_origin, MEDIA_OPEN_DEVICE, options.audio_device_id,
      options.video_device_id, options.audio_type, options.video_type);
  DeviceRequest* request = new DeviceRequest(requester, stream_request);
  const std::string& label = AddRequest(request);
  StartEnumeration(request);

  return label;
}

void MediaStreamManager::SendCachedDeviceList(
    EnumerationCache* cache,
    const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (cache->valid) {
    DeviceRequests::iterator it = requests_.find(label);
    if (it != requests_.end()) {
      it->second->requester->DevicesEnumerated(label, cache->devices);
    }
  }
}

void MediaStreamManager::StartMonitoring() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!base::SystemMonitor::Get())
    return;

  if (!monitoring_started_) {
    monitoring_started_ = true;
    base::SystemMonitor::Get()->AddDevicesChangedObserver(this);

    // Enumerate both the audio and video devices to cache the device lists
    // and send them to media observer.
    ++active_enumeration_ref_count_[MEDIA_DEVICE_AUDIO_CAPTURE];
    audio_input_device_manager_->EnumerateDevices(MEDIA_DEVICE_AUDIO_CAPTURE);
    ++active_enumeration_ref_count_[MEDIA_DEVICE_VIDEO_CAPTURE];
    video_capture_manager_->EnumerateDevices(MEDIA_DEVICE_VIDEO_CAPTURE);
  }
}

void MediaStreamManager::StopMonitoring() {
  DCHECK_EQ(base::MessageLoop::current(), io_loop_);
  if (monitoring_started_) {
    base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
    monitoring_started_ = false;
    ClearEnumerationCache(&audio_enumeration_cache_);
    ClearEnumerationCache(&video_enumeration_cache_);
  }
}

bool MediaStreamManager::TranslateGUIDToRawId(MediaStreamType stream_type,
                                              const GURL& security_origin,
                                              const std::string& device_guid,
                                              std::string* raw_device_id) {
  DCHECK(stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ||
         stream_type == MEDIA_DEVICE_VIDEO_CAPTURE);
  if (device_guid.empty())
    return false;

  EnumerationCache* cache =
      stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ?
      &audio_enumeration_cache_ : &video_enumeration_cache_;

  // If device monitoring hasn't started, the |device_guid| is not valid.
  if (!cache->valid)
    return false;

  for (StreamDeviceInfoArray::const_iterator it = cache->devices.begin();
       it != cache->devices.end();
       ++it) {
    if (DeviceRequestMessageFilter::DoesRawIdMatchGuid(
        security_origin, device_guid, it->device.id)) {
      *raw_device_id = it->device.id;
      return true;
    }
  }
  return false;
}

void MediaStreamManager::ClearEnumerationCache(EnumerationCache* cache) {
  DCHECK_EQ(base::MessageLoop::current(), io_loop_);
  cache->valid = false;
}

void MediaStreamManager::StartEnumeration(DeviceRequest* request) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Start monitoring the devices when doing the first enumeration.
  if (!monitoring_started_ && base::SystemMonitor::Get()) {
    StartMonitoring();
  }

  // Start enumeration for devices of all requested device types.
  for (int i = MEDIA_NO_SERVICE + 1; i < NUM_MEDIA_TYPES; ++i) {
    const MediaStreamType stream_type = static_cast<MediaStreamType>(i);
    if (Requested(request->request, stream_type)) {
      request->SetState(stream_type, MEDIA_REQUEST_STATE_REQUESTED);
      DCHECK_GE(active_enumeration_ref_count_[stream_type], 0);
      if (active_enumeration_ref_count_[stream_type] == 0) {
        ++active_enumeration_ref_count_[stream_type];
        GetDeviceManager(stream_type)->EnumerateDevices(stream_type);
      }
    }
  }
}

std::string MediaStreamManager::AddRequest(DeviceRequest* request) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Create a label for this request and verify it is unique.
  std::string unique_label;
  do {
    unique_label = RandomLabel();
  } while (requests_.find(unique_label) != requests_.end());

  requests_.insert(std::make_pair(unique_label, request));

  return unique_label;
}

void MediaStreamManager::RemoveRequest(DeviceRequests::iterator it) {
  requests_.erase(it);
}

void MediaStreamManager::PostRequestToUI(const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DeviceRequest* request = requests_[label];

  if (use_fake_ui_) {
    if (!fake_ui_)
      fake_ui_.reset(new FakeMediaStreamUIProxy());

    MediaStreamDevices devices;
    if (audio_enumeration_cache_.valid) {
      for (StreamDeviceInfoArray::const_iterator it =
               audio_enumeration_cache_.devices.begin();
           it != audio_enumeration_cache_.devices.end(); ++it) {
        devices.push_back(it->device);
      }
    }
    if (video_enumeration_cache_.valid) {
      for (StreamDeviceInfoArray::const_iterator it =
               video_enumeration_cache_.devices.begin();
           it != video_enumeration_cache_.devices.end(); ++it) {
        devices.push_back(it->device);
      }
    }

    fake_ui_->SetAvailableDevices(devices);

    request->ui_proxy = fake_ui_.Pass();
  } else {
    request->ui_proxy = MediaStreamUIProxy::Create();
  }

  request->ui_proxy->RequestAccess(
      request->request,
      base::Bind(&MediaStreamManager::HandleAccessRequestResponse,
                 base::Unretained(this), label));
}

void MediaStreamManager::HandleRequest(const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  DeviceRequest* request = requests_[label];

  const MediaStreamType audio_type = request->request.audio_type;
  const MediaStreamType video_type = request->request.video_type;

  bool is_web_contents_capture =
      audio_type == MEDIA_TAB_AUDIO_CAPTURE ||
      video_type == MEDIA_TAB_VIDEO_CAPTURE;

  bool is_screen_capture =
      video_type == MEDIA_DESKTOP_VIDEO_CAPTURE;

  if (!is_web_contents_capture &&
      !is_screen_capture &&
      ((IsAudioMediaType(audio_type) && !audio_enumeration_cache_.valid) ||
       (IsVideoMediaType(video_type) && !video_enumeration_cache_.valid))) {
    // Enumerate the devices if there is no valid device lists to be used.
    StartEnumeration(request);
    return;
  }

  // No need to do new device enumerations, post the request to UI
  // immediately.
  if (IsAudioMediaType(audio_type))
    request->SetState(audio_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
  if (IsVideoMediaType(video_type))
    request->SetState(video_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);

  PostRequestToUI(label);
}

void MediaStreamManager::InitializeDeviceManagersOnIOThread() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (device_thread_)
    return;

  device_thread_.reset(new base::Thread("MediaStreamDeviceThread"));
#if defined(OS_WIN)
  device_thread_->init_com_with_mta(true);
#endif
  CHECK(device_thread_->Start());

  audio_input_device_manager_ = new AudioInputDeviceManager(audio_manager_);
  audio_input_device_manager_->Register(
      this, device_thread_->message_loop_proxy().get());

  video_capture_manager_ = new VideoCaptureManager();
  video_capture_manager_->Register(this,
                                   device_thread_->message_loop_proxy().get());

  // We want to be notified of IO message loop destruction to delete the thread
  // and the device managers.
  io_loop_ = base::MessageLoop::current();
  io_loop_->AddDestructionObserver(this);
}

void MediaStreamManager::Opened(MediaStreamType stream_type,
                                int capture_session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Find the request containing this device and mark it as used.
  DeviceRequest* request = NULL;
  StreamDeviceInfoArray* devices = NULL;
  std::string label;
  for (DeviceRequests::iterator request_it = requests_.begin();
       request_it != requests_.end() && request == NULL; ++request_it) {
    devices = &(request_it->second->devices);
    for (StreamDeviceInfoArray::iterator device_it = devices->begin();
         device_it != devices->end(); ++device_it) {
      if (device_it->device.type == stream_type &&
          device_it->session_id == capture_session_id) {
        // We've found the request.
        device_it->in_use = true;
        label = request_it->first;
        request = request_it->second;
        break;
      }
    }
  }
  if (request == NULL) {
    // The request doesn't exist.
    return;
  }

  DCHECK_NE(request->state(stream_type), MEDIA_REQUEST_STATE_REQUESTED);

  // Check if all devices for this stream type are opened. Update the state if
  // they are.
  for (StreamDeviceInfoArray::iterator device_it = devices->begin();
       device_it != devices->end(); ++device_it) {
    if (device_it->device.type != stream_type) {
      continue;
    }
    if (device_it->in_use == false) {
      // Wait for more devices to be opened before we're done.
      return;
    }
  }

  request->SetState(stream_type, MEDIA_REQUEST_STATE_DONE);

  if (!RequestDone(*request)) {
    // This stream_type is done, but not the other type.
    return;
  }

  switch (request->request.request_type) {
    case MEDIA_OPEN_DEVICE:
      request->requester->DeviceOpened(label, devices->front());
      break;
    case MEDIA_GENERATE_STREAM: {
      // Partition the array of devices into audio vs video.
      StreamDeviceInfoArray audio_devices, video_devices;
      for (StreamDeviceInfoArray::iterator device_it = devices->begin();
           device_it != devices->end(); ++device_it) {
        if (IsAudioMediaType(device_it->device.type)) {
          // Store the native audio parameters in the device struct.
          // TODO(xians): Handle the tab capture sample rate/channel layout
          // in AudioInputDeviceManager::Open().
          if (device_it->device.type != content::MEDIA_TAB_AUDIO_CAPTURE) {
            const StreamDeviceInfo* info =
                audio_input_device_manager_->GetOpenedDeviceInfoById(
                    device_it->session_id);
            DCHECK_EQ(info->device.id, device_it->device.id);
            device_it->device.input = info->device.input;
            device_it->device.matched_output = info->device.matched_output;
          }
          audio_devices.push_back(*device_it);
        } else if (IsVideoMediaType(device_it->device.type)) {
          video_devices.push_back(*device_it);
        } else {
          NOTREACHED();
        }
      }

      request->requester->StreamGenerated(label, audio_devices, video_devices);
      request->ui_proxy->OnStarted(
          base::Bind(&MediaStreamManager::StopStreamFromUI,
                     base::Unretained(this), label));
      break;
    }
    default:
      NOTREACHED();
      break;
  }
}

void MediaStreamManager::Closed(MediaStreamType stream_type,
                                int capture_session_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
}

void MediaStreamManager::DevicesEnumerated(
    MediaStreamType stream_type, const StreamDeviceInfoArray& devices) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Only cache the device list when the device list has been changed.
  bool need_update_clients = false;
  EnumerationCache* cache =
      stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ?
      &audio_enumeration_cache_ : &video_enumeration_cache_;
  if (!cache->valid ||
      devices.size() != cache->devices.size() ||
      !std::equal(devices.begin(), devices.end(), cache->devices.begin(),
                  StreamDeviceInfo::IsEqual)) {
    cache->valid = true;
    cache->devices = devices;
    need_update_clients = true;
  }

  if (need_update_clients && monitoring_started_)
    NotifyDevicesChanged(stream_type, devices);

  // Publish the result for all requests waiting for device list(s).
  // Find the requests waiting for this device list, store their labels and
  // release the iterator before calling device settings. We might get a call
  // back from device_settings that will need to iterate through devices.
  std::list<std::string> label_list;
  for (DeviceRequests::iterator it = requests_.begin(); it != requests_.end();
       ++it) {
    if (it->second->state(stream_type) == MEDIA_REQUEST_STATE_REQUESTED &&
        Requested(it->second->request, stream_type)) {
      if (it->second->request.request_type != MEDIA_ENUMERATE_DEVICES)
        it->second->SetState(stream_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
      label_list.push_back(it->first);
    }
  }
  for (std::list<std::string>::iterator it = label_list.begin();
       it != label_list.end(); ++it) {
    DeviceRequest* request = requests_[*it];
    switch (request->request.request_type) {
      case MEDIA_ENUMERATE_DEVICES:
        if (need_update_clients && request->requester)
          request->requester->DevicesEnumerated(*it, devices);
        break;
      default:
        if (request->state(request->request.audio_type) ==
                MEDIA_REQUEST_STATE_REQUESTED ||
            request->state(request->request.video_type) ==
                MEDIA_REQUEST_STATE_REQUESTED) {
          // We are doing enumeration for other type of media, wait until it is
          // all done before posting the request to UI because UI needs
          // the device lists to handle the request.
          break;
        }

        // Post the request to UI for permission approval.
        PostRequestToUI(*it);
        break;
    }
  }
  label_list.clear();
  --active_enumeration_ref_count_[stream_type];
  DCHECK_GE(active_enumeration_ref_count_[stream_type], 0);
}

void MediaStreamManager::Error(MediaStreamType stream_type,
                               int capture_session_id,
                               MediaStreamProviderError error) {
  // Find the device for the error call.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  for (DeviceRequests::iterator it = requests_.begin(); it != requests_.end();
       ++it) {
    StreamDeviceInfoArray& devices = it->second->devices;

    // TODO(miu): BUG.  It's possible for the audio (or video) device array in
    // the "requester" to become out-of-sync with the order of devices we have
    // here.  See http://crbug.com/147650
    int audio_device_idx = -1;
    int video_device_idx = -1;
    for (StreamDeviceInfoArray::iterator device_it = devices.begin();
         device_it != devices.end(); ++device_it) {
      if (IsAudioMediaType(device_it->device.type)) {
        ++audio_device_idx;
      } else if (IsVideoMediaType(device_it->device.type)) {
        ++video_device_idx;
      } else {
        NOTREACHED();
        continue;
      }
      if (device_it->device.type != stream_type ||
          device_it->session_id != capture_session_id) {
        continue;
      }
      // We've found the failing device. Find the error case:
      // An error should only be reported to the MediaStreamManager if
      // the request has not been fulfilled yet.
      DCHECK(it->second->state(stream_type) != MEDIA_REQUEST_STATE_DONE);
      if (it->second->state(stream_type) != MEDIA_REQUEST_STATE_DONE) {
        // Request is not done, devices are not opened in this case.
        if (devices.size() <= 1) {
          scoped_ptr<DeviceRequest> request(it->second);
          // 1. Device not opened and no other devices for this request ->
          //    signal stream error and remove the request.
          if (request->requester)
            request->requester->StreamGenerationFailed(it->first);

          RemoveRequest(it);
        } else {
          // 2. Not opened but other devices exists for this request -> remove
          //    device from list, but don't signal an error.
          devices.erase(device_it);  // NOTE: This invalidates device_it!
        }
      }
      return;
    }
  }
}

void MediaStreamManager::HandleAccessRequestResponse(
    const std::string& label,
    const MediaStreamDevices& devices) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  DeviceRequests::iterator request_it = requests_.find(label);
  if (request_it == requests_.end()) {
    return;
  }

  // Handle the case when the request was denied.
  if (devices.empty()) {
    // Notify the users about the request result.
    scoped_ptr<DeviceRequest> request(request_it->second);
    if (request->requester)
      request->requester->StreamGenerationFailed(label);

    if (request->request.request_type == MEDIA_DEVICE_ACCESS &&
        !request->callback.is_null()) {
      request->callback.Run(MediaStreamDevices(), request->ui_proxy.Pass());
    }

    RemoveRequest(request_it);
    return;
  }

  if (request_it->second->request.request_type == MEDIA_DEVICE_ACCESS) {
    scoped_ptr<DeviceRequest> request(request_it->second);
    if (!request->callback.is_null())
      request->callback.Run(devices, request->ui_proxy.Pass());

    // Delete the request since it is done.
    RemoveRequest(request_it);
    return;
  }

  // Process all newly-accepted devices for this request.
  DeviceRequest* request = request_it->second;
  bool found_audio = false;
  bool found_video = false;
  for (MediaStreamDevices::const_iterator device_it = devices.begin();
       device_it != devices.end(); ++device_it) {
    StreamDeviceInfo device_info;
    device_info.device = *device_it;

    // TODO(justinlin): Nicer way to do this?
    // Re-append the device's id since we lost it when posting request to UI.
    if (device_info.device.type == content::MEDIA_TAB_VIDEO_CAPTURE ||
        device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) {
      device_info.device.id = request->request.tab_capture_device_id;

      // Initialize the sample_rate and channel_layout here since for audio
      // mirroring, we don't go through EnumerateDevices where these are usually
      // initialized.
      if (device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) {
        const media::AudioParameters parameters =
            audio_manager_->GetDefaultOutputStreamParameters();
        int sample_rate = parameters.sample_rate();
        // If we weren't able to get the native sampling rate or the sample_rate
        // is outside the valid range for input devices set reasonable defaults.
        if (sample_rate <= 0 || sample_rate > 96000)
          sample_rate = 44100;

        device_info.device.input.sample_rate = sample_rate;
        device_info.device.input.channel_layout = media::CHANNEL_LAYOUT_STEREO;
      }
    }

    // Set in_use to false to be able to track if this device has been
    // opened. in_use might be true if the device type can be used in more
    // than one session.
    device_info.in_use = false;

    device_info.session_id =
        GetDeviceManager(device_info.device.type)->Open(device_info);
    request->SetState(device_info.device.type, MEDIA_REQUEST_STATE_OPENING);
    request->devices.push_back(device_info);

    if (device_info.device.type == request->request.audio_type) {
      found_audio = true;
    } else if (device_info.device.type == request->request.video_type) {
      found_video = true;
    }
  }

  // Check whether we've received all stream types requested.
  if (!found_audio && IsAudioMediaType(request->request.audio_type))
    request->SetState(request->request.audio_type, MEDIA_REQUEST_STATE_ERROR);

  if (!found_video && IsVideoMediaType(request->request.video_type))
    request->SetState(request->request.video_type, MEDIA_REQUEST_STATE_ERROR);
}

void MediaStreamManager::StopStreamFromUI(const std::string& label) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  DeviceRequests::iterator it = requests_.find(label);
  if (it == requests_.end())
    return;

  // Notify renderers that the stream has been stopped.
  if (it->second->requester)
    it->second->requester->StopGeneratedStream(label);

  StopGeneratedStream(label);
}

void MediaStreamManager::UseFakeDevice() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  video_capture_manager()->UseFakeDevice();
  audio_input_device_manager()->UseFakeDevice();
}

void MediaStreamManager::UseFakeUI(scoped_ptr<FakeMediaStreamUIProxy> fake_ui) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  use_fake_ui_ = true;
  fake_ui_ = fake_ui.Pass();
}

void MediaStreamManager::WillDestroyCurrentMessageLoop() {
  DCHECK_EQ(base::MessageLoop::current(), io_loop_);
  DCHECK(requests_.empty());
  if (device_thread_) {
    StopMonitoring();

    video_capture_manager_->Unregister();
    audio_input_device_manager_->Unregister();
    device_thread_.reset();
  }

  audio_input_device_manager_ = NULL;
  video_capture_manager_ = NULL;
}

void MediaStreamManager::NotifyDevicesChanged(
    MediaStreamType stream_type,
    const StreamDeviceInfoArray& devices) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  MediaObserver* media_observer =
      GetContentClient()->browser()->GetMediaObserver();
  if (media_observer == NULL)
    return;

  // Map the devices to MediaStreamDevices.
  MediaStreamDevices new_devices;
  for (StreamDeviceInfoArray::const_iterator it = devices.begin();
       it != devices.end(); ++it) {
    new_devices.push_back(it->device);
  }

  if (IsAudioMediaType(stream_type)) {
    media_observer->OnAudioCaptureDevicesChanged(new_devices);
  } else if (IsVideoMediaType(stream_type)) {
    media_observer->OnVideoCaptureDevicesChanged(new_devices);
  } else {
    NOTREACHED();
  }
}

bool MediaStreamManager::RequestDone(const DeviceRequest& request) const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  const bool requested_audio = IsAudioMediaType(request.request.audio_type);
  const bool requested_video = IsVideoMediaType(request.request.video_type);

  const bool audio_done =
      !requested_audio ||
      request.state(request.request.audio_type) ==
      MEDIA_REQUEST_STATE_DONE ||
      request.state(request.request.audio_type) ==
      MEDIA_REQUEST_STATE_ERROR;
  if (!audio_done)
    return false;

  const bool video_done =
      !requested_video ||
      request.state(request.request.video_type) ==
      MEDIA_REQUEST_STATE_DONE ||
      request.state(request.request.video_type) ==
      MEDIA_REQUEST_STATE_ERROR;
  if (!video_done)
    return false;

  for (StreamDeviceInfoArray::const_iterator it = request.devices.begin();
       it != request.devices.end(); ++it) {
    if (it->in_use == false)
      return false;
  }

  return true;
}

MediaStreamProvider* MediaStreamManager::GetDeviceManager(
    MediaStreamType stream_type) {
  if (IsVideoMediaType(stream_type)) {
    return video_capture_manager();
  } else if (IsAudioMediaType(stream_type)) {
    return audio_input_device_manager();
  }
  NOTREACHED();
  return NULL;
}

void MediaStreamManager::OnDevicesChanged(
    base::SystemMonitor::DeviceType device_type) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // NOTE: This method is only called in response to physical audio/video device
  // changes (from the operating system).

  MediaStreamType stream_type;
  if (device_type == base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE) {
    stream_type = MEDIA_DEVICE_AUDIO_CAPTURE;
  } else if (device_type == base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE) {
    stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
  } else {
    return;  // Uninteresting device change.
  }

  // Always do enumeration even though some enumeration is in progress,
  // because those enumeration commands could be sent before these devices
  // change.
  ++active_enumeration_ref_count_[stream_type];
  GetDeviceManager(stream_type)->EnumerateDevices(stream_type);
}

}  // namespace content
