// 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 "chrome/browser/media/media_stream_devices_controller.h"

#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/content_settings/content_settings_provider.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/media/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/media_stream_capture_indicator.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/content_settings.h"
#include "chrome/common/content_settings_pattern.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/common/media_stream_request.h"
#include "extensions/common/constants.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/users/user_manager.h"
#endif

using content::BrowserThread;

namespace {

// This prefix is combined with request security origins to store media access
// permissions that the user has granted a specific page navigation instance.
// The string value stored with the navigation instance will contain one or more
// kMediaPermissionXxx constants that indicates the permission(s) that the user
// has granted the page.
const char kMediaPermissionKeyPrefix[] = "media_permissions#";
const base::char16 kMediaPermissionAudio = static_cast<base::char16>('a');
const base::char16 kMediaPermissionVideo = static_cast<base::char16>('v');

bool HasAvailableDevicesForRequest(const content::MediaStreamRequest& request) {
  const content::MediaStreamDevices* audio_devices =
      request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ?
          &MediaCaptureDevicesDispatcher::GetInstance()
              ->GetAudioCaptureDevices() :
          NULL;

  const content::MediaStreamDevices* video_devices =
      request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ?
          &MediaCaptureDevicesDispatcher::GetInstance()
              ->GetVideoCaptureDevices() :
          NULL;

  // Check if we're being asked for audio and/or video and that either of those
  // lists is empty.  If they are, we do not have devices available for the
  // request.
  // TODO(tommi): It's kind of strange to have this here since if we fail this
  // test, there'll be a UI shown that indicates to the user that access to
  // non-existing audio/video devices has been denied.  The user won't have
  // any way to change that but there will be a UI shown which indicates that
  // access is blocked.
  if ((audio_devices != NULL && audio_devices->empty()) ||
      (video_devices != NULL && video_devices->empty())) {
    return false;
  }

  // Note: we check requested_[audio|video]_device_id before dereferencing
  // [audio|video]_devices.  If the requested device id is non-empty, then
  // the corresponding device list must not be NULL.

  if (!request.requested_audio_device_id.empty() &&
      !audio_devices->FindById(request.requested_audio_device_id)) {
    return false;
  }

  if (!request.requested_video_device_id.empty() &&
      !video_devices->FindById(request.requested_video_device_id)) {
    return false;
  }

  return true;
}

base::string16 GetMediaPermissionsFromNavigationEntry(
    content::NavigationEntry* navigation_entry,
    const content::MediaStreamRequest& request) {
  const std::string key(kMediaPermissionKeyPrefix +
                        request.security_origin.spec());

  base::string16 permissions;
  if (!navigation_entry->GetExtraData(key, &permissions)) {
    DCHECK(permissions.empty());
  }

  return permissions;
}

void SetMediaPermissionsForNavigationEntry(
    content::NavigationEntry* navigation_entry,
    const content::MediaStreamRequest& request,
    const base::string16& permissions) {
  const std::string key(kMediaPermissionKeyPrefix +
                        request.security_origin.spec());
  permissions.empty() ?
      navigation_entry->ClearExtraData(key) :
      navigation_entry->SetExtraData(key, permissions);
}

void SetMediaPermissionsForNavigationEntry(
    content::NavigationEntry* navigation_entry,
    const content::MediaStreamRequest& request,
    bool allow_audio,
    bool allow_video) {
  base::string16 permissions;
  if (allow_audio)
    permissions += kMediaPermissionAudio;
  if (allow_video)
    permissions += kMediaPermissionVideo;
  SetMediaPermissionsForNavigationEntry(navigation_entry, request, permissions);
}

bool IsRequestAllowedByNavigationEntry(
    content::NavigationEntry* navigation_entry,
    const content::MediaStreamRequest& request) {
  using content::MEDIA_NO_SERVICE;
  using content::MEDIA_DEVICE_AUDIO_CAPTURE;
  using content::MEDIA_DEVICE_VIDEO_CAPTURE;

  // If we aren't being asked for at least one of these two, fail right away.
  if (!navigation_entry ||
      (request.audio_type != MEDIA_DEVICE_AUDIO_CAPTURE &&
       request.video_type != MEDIA_DEVICE_VIDEO_CAPTURE)) {
    return false;
  }

  base::string16 permissions =
      GetMediaPermissionsFromNavigationEntry(navigation_entry, request);

  bool audio_requested_and_granted =
      request.audio_type == MEDIA_DEVICE_AUDIO_CAPTURE &&
      permissions.find(kMediaPermissionAudio) != base::string16::npos;

  bool video_requested_and_granted =
      request.video_type == MEDIA_DEVICE_VIDEO_CAPTURE &&
      permissions.find(kMediaPermissionVideo) != base::string16::npos;

  return
      (audio_requested_and_granted || request.audio_type == MEDIA_NO_SERVICE) &&
      (video_requested_and_granted || request.video_type == MEDIA_NO_SERVICE);
}


bool IsInKioskMode() {
  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
    return true;

#if defined(OS_CHROMEOS)
  const chromeos::UserManager* user_manager = chromeos::UserManager::Get();
  return user_manager && user_manager->IsLoggedInAsKioskApp();
#else
  return false;
#endif
}

enum DevicePermissionActions {
  kAllowHttps = 0,
  kAllowHttp,
  kDeny,
  kCancel,
  kPermissionActionsMax  // Must always be last!
};

}  // namespace

MediaStreamDevicesController::MediaStreamTypeSettings::MediaStreamTypeSettings(
    Permission permission, const std::string& requested_device_id):
    permission(permission), requested_device_id(requested_device_id) {}

MediaStreamDevicesController::MediaStreamTypeSettings::
    MediaStreamTypeSettings(): permission(MEDIA_NONE) {}

MediaStreamDevicesController::MediaStreamTypeSettings::
    ~MediaStreamTypeSettings() {}

MediaStreamDevicesController::MediaStreamDevicesController(
    content::WebContents* web_contents,
    const content::MediaStreamRequest& request,
    const content::MediaResponseCallback& callback)
    : web_contents_(web_contents),
      request_(request),
      callback_(callback) {
  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
  content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);

  // For MEDIA_OPEN_DEVICE requests (Pepper) we always request both webcam
  // and microphone to avoid popping two infobars.
  // We start with setting the requested media type to allowed or blocked
  // depending on the policy. If not blocked by policy it may be blocked later
  // in the two remaining filtering steps (by user setting or by user when
  // clicking the infobar).
  // TODO(grunell): It's not the nicest solution to let the MEDIA_OPEN_DEVICE
  // case take a ride on the MEDIA_DEVICE_*_CAPTURE permission. Should be fixed.
  if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
      request.request_type == content::MEDIA_OPEN_DEVICE) {
    if (GetDevicePolicy(prefs::kAudioCaptureAllowed,
                        prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) {
      request_permissions_.insert(std::make_pair(
          content::MEDIA_DEVICE_AUDIO_CAPTURE,
          MediaStreamTypeSettings(MEDIA_BLOCKED_BY_POLICY,
                                  request.requested_audio_device_id)));
    } else {
      request_permissions_.insert(std::make_pair(
          content::MEDIA_DEVICE_AUDIO_CAPTURE,
          MediaStreamTypeSettings(MEDIA_ALLOWED,
                                  request.requested_audio_device_id)));
    }
  }
  if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
      request.request_type == content::MEDIA_OPEN_DEVICE) {
    if (GetDevicePolicy(prefs::kVideoCaptureAllowed,
                        prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) {
      request_permissions_.insert(std::make_pair(
          content::MEDIA_DEVICE_VIDEO_CAPTURE,
          MediaStreamTypeSettings(MEDIA_BLOCKED_BY_POLICY,
                                  request.requested_video_device_id)));
    } else {
      request_permissions_.insert(std::make_pair(
          content::MEDIA_DEVICE_VIDEO_CAPTURE,
          MediaStreamTypeSettings(MEDIA_ALLOWED,
                                  request.requested_video_device_id)));
    }
  }
}

MediaStreamDevicesController::~MediaStreamDevicesController() {
  if (!callback_.is_null()) {
    callback_.Run(content::MediaStreamDevices(),
                  content::MEDIA_DEVICE_INVALID_STATE,
                  scoped_ptr<content::MediaStreamUI>());
  }
}

// static
void MediaStreamDevicesController::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* prefs) {
  prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed,
                             true,
                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
  prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed,
                             true,
                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
  prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls,
                          user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
  prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls,
                          user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}

// TODO(gbillock): rename? doesn't actually dismiss. More of a 'check profile
// and system for compatibility' thing.
bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() {
  // Tab capture is allowed for extensions only and infobar is not shown for
  // extensions.
  if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE ||
      request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
    Deny(false, content::MEDIA_DEVICE_INVALID_STATE);
    return true;
  }

  // Deny the request if the security origin is empty, this happens with
  // file access without |--allow-file-access-from-files| flag.
  if (request_.security_origin.is_empty()) {
    Deny(false, content::MEDIA_DEVICE_INVALID_SECURITY_ORIGIN);
    return true;
  }

  // Deny the request if there is no device attached to the OS of the
  // requested type. If both audio and video is requested, both types must be
  // available.
  if (!HasAvailableDevicesForRequest(request_)) {
    Deny(false, content::MEDIA_DEVICE_NO_HARDWARE);
    return true;
  }

  // Check if any allow exception has been made for this request.
  if (IsRequestAllowedByDefault()) {
    Accept(false);
    return true;
  }

  // Filter any parts of the request that have been blocked by default and deny
  // it if nothing is left to accept.
  if (FilterBlockedByDefaultDevices() == 0) {
    Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
    return true;
  }

  // Check if the media default setting is set to block.
  if (IsDefaultMediaAccessBlocked()) {
    Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
    return true;
  }

  // Check if the navigation entry has previously been granted access.
  // We do this after the IsDefaultMediaAccessBlocked check to handle the use
  // case where the user modifies the content settings to 'deny' after having
  // previously granted the page access and the permissions in the
  // NavigationEntry are out of date.
  content::NavigationEntry* navigation_entry =
      web_contents_->GetController().GetVisibleEntry();
  if (IsRequestAllowedByNavigationEntry(navigation_entry, request_)) {
    Accept(false);
    return true;
  }

  // Show the infobar.
  return false;
}

bool MediaStreamDevicesController::HasAudio() const {
  return IsDeviceAudioCaptureRequestedAndAllowed();
}

bool MediaStreamDevicesController::HasVideo() const {
  return IsDeviceVideoCaptureRequestedAndAllowed();
}

const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
  return request_.security_origin.spec();
}

void MediaStreamDevicesController::Accept(bool update_content_setting) {
  NotifyUIRequestAccepted();

  // Get the default devices for the request.
  content::MediaStreamDevices devices;
  bool audio_allowed = IsDeviceAudioCaptureRequestedAndAllowed();
  bool video_allowed = IsDeviceVideoCaptureRequestedAndAllowed();
  if (audio_allowed || video_allowed) {
    switch (request_.request_type) {
      case content::MEDIA_OPEN_DEVICE: {
        const content::MediaStreamDevice* device = NULL;
        // For open device request, when requested device_id is empty, pick
        // the first available of the given type. If requested device_id is
        // not empty, return the desired device if it's available. Otherwise,
        // return no device.
        if (audio_allowed &&
            request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
          if (!request_.requested_audio_device_id.empty()) {
            device = MediaCaptureDevicesDispatcher::GetInstance()->
                GetRequestedAudioDevice(request_.requested_audio_device_id);
          } else {
            device = MediaCaptureDevicesDispatcher::GetInstance()->
                GetFirstAvailableAudioDevice();
          }
        } else if (video_allowed &&
            request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
          // Pepper API opens only one device at a time.
          if (!request_.requested_video_device_id.empty()) {
            device = MediaCaptureDevicesDispatcher::GetInstance()->
                GetRequestedVideoDevice(request_.requested_video_device_id);
          } else {
            device = MediaCaptureDevicesDispatcher::GetInstance()->
                GetFirstAvailableVideoDevice();
          }
        }
        if (device)
          devices.push_back(*device);
        break;
      }
      case content::MEDIA_GENERATE_STREAM: {
        bool get_default_audio_device = audio_allowed;
        bool get_default_video_device = video_allowed;

        // Get the exact audio or video device if an id is specified.
        if (audio_allowed && !request_.requested_audio_device_id.empty()) {
          const content::MediaStreamDevice* audio_device =
              MediaCaptureDevicesDispatcher::GetInstance()->
                  GetRequestedAudioDevice(request_.requested_audio_device_id);
          if (audio_device) {
            devices.push_back(*audio_device);
            get_default_audio_device = false;
          }
        }
        if (video_allowed && !request_.requested_video_device_id.empty()) {
          const content::MediaStreamDevice* video_device =
              MediaCaptureDevicesDispatcher::GetInstance()->
                  GetRequestedVideoDevice(request_.requested_video_device_id);
          if (video_device) {
            devices.push_back(*video_device);
            get_default_video_device = false;
          }
        }

        // If either or both audio and video devices were requested but not
        // specified by id, get the default devices.
        if (get_default_audio_device || get_default_video_device) {
          MediaCaptureDevicesDispatcher::GetInstance()->
              GetDefaultDevicesForProfile(profile_,
                                          get_default_audio_device,
                                          get_default_video_device,
                                          &devices);
        }

        // For pages accessed via http (not https), tag this navigation entry
        // with the granted permissions.  This avoids repeated prompts for
        // device access.
        if (!IsSchemeSecure()) {
          content::NavigationEntry* navigation_entry =
              web_contents_->GetController().GetVisibleEntry();
          if (navigation_entry) {
            SetMediaPermissionsForNavigationEntry(
                navigation_entry, request_, audio_allowed, video_allowed);
          }
        }
        break;
      }
      case content::MEDIA_DEVICE_ACCESS: {
        // Get the default devices for the request.
        MediaCaptureDevicesDispatcher::GetInstance()->
            GetDefaultDevicesForProfile(profile_,
                                        audio_allowed,
                                        video_allowed,
                                        &devices);
        break;
      }
      case content::MEDIA_ENUMERATE_DEVICES: {
        // Do nothing.
        NOTREACHED();
        break;
      }
    }  // switch

    // TODO(raymes): We currently set the content permission for non-https
    // websites for Pepper requests as well. This is temporary and should be
    // removed.
    if (update_content_setting) {
      if ((IsSchemeSecure() && !devices.empty()) ||
          request_.request_type == content::MEDIA_OPEN_DEVICE) {
        SetPermission(true);
      }
    }
  }

  scoped_ptr<content::MediaStreamUI> ui;
  if (!devices.empty()) {
    ui = MediaCaptureDevicesDispatcher::GetInstance()->
        GetMediaStreamCaptureIndicator()->RegisterMediaStream(
            web_contents_, devices);
  }
  content::MediaResponseCallback cb = callback_;
  callback_.Reset();
  cb.Run(devices,
         devices.empty() ?
             content::MEDIA_DEVICE_NO_HARDWARE : content::MEDIA_DEVICE_OK,
         ui.Pass());
}

void MediaStreamDevicesController::Deny(
    bool update_content_setting,
    content::MediaStreamRequestResult result) {
  DLOG(WARNING) << "MediaStreamDevicesController::Deny: " << result;
  NotifyUIRequestDenied();

  // Clear previously allowed permissions from the navigation entry if any.
  content::NavigationEntry* navigation_entry =
      web_contents_->GetController().GetVisibleEntry();
  if (navigation_entry) {
    SetMediaPermissionsForNavigationEntry(
        navigation_entry, request_, false, false);
  }

  if (update_content_setting) {
    CHECK_EQ(content::MEDIA_DEVICE_PERMISSION_DENIED, result);
    SetPermission(false);
  }

  content::MediaResponseCallback cb = callback_;
  callback_.Reset();
  cb.Run(content::MediaStreamDevices(),
         result,
         scoped_ptr<content::MediaStreamUI>());
}

int MediaStreamDevicesController::GetIconID() const {
  if (HasVideo())
    return IDR_INFOBAR_MEDIA_STREAM_CAMERA;

  return IDR_INFOBAR_MEDIA_STREAM_MIC;
}

base::string16 MediaStreamDevicesController::GetMessageText() const {
  int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO;
  if (!HasAudio())
    message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY;
  else if (!HasVideo())
    message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY;
  return l10n_util::GetStringFUTF16(
      message_id, base::UTF8ToUTF16(GetSecurityOriginSpec()));
}

base::string16 MediaStreamDevicesController::GetMessageTextFragment() const {
  int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_PERMISSION_FRAGMENT;
  if (!HasAudio())
    message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT;
  else if (!HasVideo())
    message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT;
  return l10n_util::GetStringUTF16(message_id);
}

bool MediaStreamDevicesController::HasUserGesture() const {
  return request_.user_gesture;
}

GURL MediaStreamDevicesController::GetRequestingHostname() const {
  return request_.security_origin;
}

void MediaStreamDevicesController::PermissionGranted() {
  GURL origin(GetSecurityOriginSpec());
  if (origin.SchemeIsSecure()) {
    UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
                              kAllowHttps, kPermissionActionsMax);
  } else {
    UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
                              kAllowHttp, kPermissionActionsMax);
  }
  Accept(true);
}

void MediaStreamDevicesController::PermissionDenied() {
  UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
                            kDeny, kPermissionActionsMax);
  Deny(true, content::MEDIA_DEVICE_PERMISSION_DENIED);
}

void MediaStreamDevicesController::Cancelled() {
  UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
                            kCancel, kPermissionActionsMax);
  Deny(false, content::MEDIA_DEVICE_PERMISSION_DISMISSED);
}

void MediaStreamDevicesController::RequestFinished() {
  delete this;
}

MediaStreamDevicesController::DevicePolicy
MediaStreamDevicesController::GetDevicePolicy(
    const char* policy_name,
    const char* whitelist_policy_name) const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  // If the security origin policy matches a value in the whitelist, allow it.
  // Otherwise, check the |policy_name| master switch for the default behavior.

  PrefService* prefs = profile_->GetPrefs();

  // TODO(tommi): Remove the kiosk mode check when the whitelist below
  // is visible in the media exceptions UI.
  // See discussion here: https://codereview.chromium.org/15738004/
  if (IsInKioskMode()) {
    const base::ListValue* list = prefs->GetList(whitelist_policy_name);
    std::string value;
    for (size_t i = 0; i < list->GetSize(); ++i) {
      if (list->GetString(i, &value)) {
        ContentSettingsPattern pattern =
            ContentSettingsPattern::FromString(value);
        if (pattern == ContentSettingsPattern::Wildcard()) {
          DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value;
          continue;
        }
        DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value;
        if (pattern.IsValid() && pattern.Matches(request_.security_origin))
          return ALWAYS_ALLOW;
      }
    }
  }

  // If a match was not found, check if audio capture is otherwise disallowed
  // or if the user should be prompted.  Setting the policy value to "true"
  // is equal to not setting it at all, so from hereon out, we will return
  // either POLICY_NOT_SET (prompt) or ALWAYS_DENY (no prompt, no access).
  if (!prefs->GetBoolean(policy_name))
    return ALWAYS_DENY;

  return POLICY_NOT_SET;
}

bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
  // The request from internal objects like chrome://URLs is always allowed.
  if (ShouldAlwaysAllowOrigin())
    return true;

  struct {
    bool has_capability;
    const char* policy_name;
    const char* list_policy_name;
    ContentSettingsType settings_type;
  } device_checks[] = {
    { IsDeviceAudioCaptureRequestedAndAllowed(), prefs::kAudioCaptureAllowed,
      prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC },
    { IsDeviceVideoCaptureRequestedAndAllowed(), prefs::kVideoCaptureAllowed,
      prefs::kVideoCaptureAllowedUrls,
      CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) {
    if (!device_checks[i].has_capability)
      continue;

    DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name,
                                          device_checks[i].list_policy_name);

    if (policy == ALWAYS_DENY)
      return false;

    if (policy == POLICY_NOT_SET) {
      // Only load content settings from secure origins unless it is a
      // content::MEDIA_OPEN_DEVICE (Pepper) request.
      if (!IsSchemeSecure() &&
          request_.request_type != content::MEDIA_OPEN_DEVICE) {
        return false;
      }
      if (profile_->GetHostContentSettingsMap()->GetContentSetting(
              request_.security_origin, request_.security_origin,
              device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) !=
              CONTENT_SETTING_ALLOW) {
        return false;
      }
    }
    // If we get here, then either policy is set to ALWAYS_ALLOW or the content
    // settings allow the request by default.
  }

  return true;
}

int MediaStreamDevicesController::FilterBlockedByDefaultDevices() {
  int requested_devices = 0;

  if (IsDeviceAudioCaptureRequestedAndAllowed()) {
    if (profile_->GetHostContentSettingsMap()->GetContentSetting(
        request_.security_origin,
        request_.security_origin,
        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
        NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
      request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
          MEDIA_BLOCKED_BY_USER_SETTING;
    } else {
      ++requested_devices;
    }
  }

  if (IsDeviceVideoCaptureRequestedAndAllowed()) {
    if (profile_->GetHostContentSettingsMap()->GetContentSetting(
        request_.security_origin,
        request_.security_origin,
        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
        NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
      request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
          MEDIA_BLOCKED_BY_USER_SETTING;
    } else {
      ++requested_devices;
    }
  }

  return requested_devices;
}

bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
  ContentSetting current_setting =
      profile_->GetHostContentSettingsMap()->GetDefaultContentSetting(
          CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL);
  return (current_setting == CONTENT_SETTING_BLOCK);
}

bool MediaStreamDevicesController::IsSchemeSecure() const {
  return request_.security_origin.SchemeIsSecure() ||
      request_.security_origin.SchemeIs(extensions::kExtensionScheme);
}

bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const {
  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
  return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent(
      request_.security_origin, request_.security_origin,
      CONTENT_SETTINGS_TYPE_MEDIASTREAM);
}

void MediaStreamDevicesController::SetPermission(bool allowed) const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  ContentSettingsPattern primary_pattern =
      ContentSettingsPattern::FromURLNoWildcard(request_.security_origin);
  // Check the pattern is valid or not. When the request is from a file access,
  // no exception will be made.
  if (!primary_pattern.IsValid())
    return;

  ContentSetting content_setting = allowed ?
      CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
  if (request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE) !=
      request_permissions_.end()) {
    profile_->GetHostContentSettingsMap()->SetContentSetting(
        primary_pattern,
        ContentSettingsPattern::Wildcard(),
        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
        std::string(),
        content_setting);
  }
  if (request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE) !=
      request_permissions_.end()) {
    profile_->GetHostContentSettingsMap()->SetContentSetting(
        primary_pattern,
        ContentSettingsPattern::Wildcard(),
        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
        std::string(),
        content_setting);
  }
}

void MediaStreamDevicesController::NotifyUIRequestAccepted() const {
  if (!content_settings_)
    return;

  content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
                                                request_permissions_);
}

void MediaStreamDevicesController::NotifyUIRequestDenied() {
  if (!content_settings_)
    return;

  if (IsDeviceAudioCaptureRequestedAndAllowed()) {
    request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
        MEDIA_BLOCKED_BY_USER;
  }
  if (IsDeviceVideoCaptureRequestedAndAllowed()) {
    request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
        MEDIA_BLOCKED_BY_USER;
  }

  content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
                                                request_permissions_);
}

bool MediaStreamDevicesController::IsDeviceAudioCaptureRequestedAndAllowed()
    const {
  MediaStreamTypeSettingsMap::const_iterator it =
      request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE);
  return (it != request_permissions_.end() &&
          it->second.permission == MEDIA_ALLOWED);
}

bool MediaStreamDevicesController::IsDeviceVideoCaptureRequestedAndAllowed()
    const {
  MediaStreamTypeSettingsMap::const_iterator it =
      request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE);
  return (it != request_permissions_.end() &&
          it->second.permission == MEDIA_ALLOWED);
}