media_stream_devices_controller.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/media/media_stream_devices_controller.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/metrics/histogram.h"
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/prefs/scoped_user_pref_update.h"
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/values.h"
1190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "chrome/browser/content_settings/host_content_settings_map.h"
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/content_settings/tab_specific_content_settings.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/media/media_capture_devices_dispatcher.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/media/media_stream_capture_indicator.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/media/media_stream_device_permissions.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/profiles/profile.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/ui/browser.h"
18c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "chrome/common/chrome_switches.h"
19c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "chrome/common/pref_names.h"
20c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "chrome/grit/generated_resources.h"
21c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "components/content_settings/core/browser/content_settings_provider.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "components/content_settings/core/common/content_settings.h"
2390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "components/content_settings/core/common/content_settings_pattern.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "components/pref_registry/pref_registry_syncable.h"
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/browser_thread.h"
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/render_widget_host_view.h"
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/common/media_stream_request.h"
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "extensions/common/constants.h"
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/theme_resources.h"
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
3190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::BrowserThread;
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
34c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)namespace {
35c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
36c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)bool HasAvailableDevicesForRequest(const content::MediaStreamRequest& request) {
37c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  const content::MediaStreamDevices* audio_devices =
38c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ?
39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          &MediaCaptureDevicesDispatcher::GetInstance()
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              ->GetAudioCaptureDevices() :
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          NULL;
42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  const content::MediaStreamDevices* video_devices =
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ?
45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          &MediaCaptureDevicesDispatcher::GetInstance()
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              ->GetVideoCaptureDevices() :
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          NULL;
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Check if we're being asked for audio and/or video and that either of those
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // lists is empty.  If they are, we do not have devices available for the
511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // request.
521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // TODO(tommi): It's kind of strange to have this here since if we fail this
531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // test, there'll be a UI shown that indicates to the user that access to
541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // non-existing audio/video devices has been denied.  The user won't have
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // any way to change that but there will be a UI shown which indicates that
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // access is blocked.
5758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)  if ((audio_devices != NULL && audio_devices->empty()) ||
5858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)      (video_devices != NULL && video_devices->empty())) {
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Note: we check requested_[audio|video]_device_id before dereferencing
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // [audio|video]_devices.  If the requested device id is non-empty, then
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the corresponding device list must not be NULL.
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
6690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (!request.requested_audio_device_id.empty() &&
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      !audio_devices->FindById(request.requested_audio_device_id)) {
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (!request.requested_video_device_id.empty() &&
7290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      !video_devices->FindById(request.requested_video_device_id)) {
73558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    return false;
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
757dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
767dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  return true;
777dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
787dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
797dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochenum DevicePermissionActions {
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  kAllowHttps = 0,
8190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  kAllowHttp,
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  kDeny,
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  kCancel,
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  kPermissionActionsMax  // Must always be last!
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
8890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
8990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)MediaStreamDevicesController::MediaStreamTypeSettings::MediaStreamTypeSettings(
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Permission permission, const std::string& requested_device_id):
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    permission(permission), requested_device_id(requested_device_id) {}
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)MediaStreamDevicesController::MediaStreamTypeSettings::
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    MediaStreamTypeSettings(): permission(MEDIA_NONE) {}
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)MediaStreamDevicesController::MediaStreamTypeSettings::
97558790d6acca3451cf3a6b497803a5f07d0bec58Ben Murdoch    ~MediaStreamTypeSettings() {}
9890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)MediaStreamDevicesController::MediaStreamDevicesController(
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    content::WebContents* web_contents,
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const content::MediaStreamRequest& request,
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const content::MediaResponseCallback& callback)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : web_contents_(web_contents),
104      request_(request),
105      callback_(callback) {
106  profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
107  content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
108
109  // For MEDIA_OPEN_DEVICE requests (Pepper) we always request both webcam
110  // and microphone to avoid popping two infobars.
111  // We start with setting the requested media type to allowed or blocked
112  // depending on the policy. If not blocked by policy it may be blocked later
113  // in the two remaining filtering steps (by user setting or by user when
114  // clicking the infobar).
115  // TODO(grunell): It's not the nicest solution to let the MEDIA_OPEN_DEVICE
116  // case take a ride on the MEDIA_DEVICE_*_CAPTURE permission. Should be fixed.
117  if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
118      request.request_type == content::MEDIA_OPEN_DEVICE) {
119    if (GetDevicePolicy(profile_,
120                        request_.security_origin,
121                        prefs::kAudioCaptureAllowed,
122                        prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) {
123      request_permissions_.insert(std::make_pair(
124          content::MEDIA_DEVICE_AUDIO_CAPTURE,
125          MediaStreamTypeSettings(MEDIA_BLOCKED_BY_POLICY,
126                                  request.requested_audio_device_id)));
127    } else {
128      request_permissions_.insert(std::make_pair(
129          content::MEDIA_DEVICE_AUDIO_CAPTURE,
130          MediaStreamTypeSettings(MEDIA_ALLOWED,
131                                  request.requested_audio_device_id)));
132    }
133  }
134  if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
135      request.request_type == content::MEDIA_OPEN_DEVICE) {
136    if (GetDevicePolicy(profile_,
137                        request_.security_origin,
138                        prefs::kVideoCaptureAllowed,
139                        prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) {
140      request_permissions_.insert(std::make_pair(
141          content::MEDIA_DEVICE_VIDEO_CAPTURE,
142          MediaStreamTypeSettings(MEDIA_BLOCKED_BY_POLICY,
143                                  request.requested_video_device_id)));
144    } else {
145      request_permissions_.insert(std::make_pair(
146          content::MEDIA_DEVICE_VIDEO_CAPTURE,
147          MediaStreamTypeSettings(MEDIA_ALLOWED,
148                                  request.requested_video_device_id)));
149    }
150  }
151}
152
153MediaStreamDevicesController::~MediaStreamDevicesController() {
154  if (!callback_.is_null()) {
155    callback_.Run(content::MediaStreamDevices(),
156                  content::MEDIA_DEVICE_FAILED_DUE_TO_SHUTDOWN,
157                  scoped_ptr<content::MediaStreamUI>());
158  }
159}
160
161// static
162void MediaStreamDevicesController::RegisterProfilePrefs(
163    user_prefs::PrefRegistrySyncable* prefs) {
164  prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed,
165                             true,
166                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
167  prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed,
168                             true,
169                             user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
170  prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls,
171                          user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
172  prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls,
173                          user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
174}
175
176// TODO(gbillock): rename? doesn't actually dismiss. More of a 'check profile
177// and system for compatibility' thing.
178bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() {
179  // Tab capture is allowed for extensions only and infobar is not shown for
180  // extensions.
181  if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE ||
182      request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
183    Deny(false, content::MEDIA_DEVICE_INVALID_STATE);
184    return true;
185  }
186
187  // Deny the request if the security origin is empty, this happens with
188  // file access without |--allow-file-access-from-files| flag.
189  if (request_.security_origin.is_empty()) {
190    Deny(false, content::MEDIA_DEVICE_INVALID_SECURITY_ORIGIN);
191    return true;
192  }
193
194  // Deny the request if there is no device attached to the OS of the
195  // requested type. If both audio and video is requested, both types must be
196  // available.
197  if (!HasAvailableDevicesForRequest(request_)) {
198    Deny(false, content::MEDIA_DEVICE_NO_HARDWARE);
199    return true;
200  }
201
202  // Check if any allow exception has been made for this request.
203  if (IsRequestAllowedByDefault()) {
204    Accept(false);
205    return true;
206  }
207
208  // Filter any parts of the request that have been blocked by default and deny
209  // it if nothing is left to accept.
210  if (FilterBlockedByDefaultDevices() == 0) {
211    Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
212    return true;
213  }
214
215  // Check if the media default setting is set to block.
216  if (IsDefaultMediaAccessBlocked()) {
217    Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
218    return true;
219  }
220
221  // Show the infobar.
222  return false;
223}
224
225bool MediaStreamDevicesController::HasAudio() const {
226  return IsDeviceAudioCaptureRequestedAndAllowed();
227}
228
229bool MediaStreamDevicesController::HasVideo() const {
230  return IsDeviceVideoCaptureRequestedAndAllowed();
231}
232
233const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
234  return request_.security_origin.spec();
235}
236
237void MediaStreamDevicesController::Accept(bool update_content_setting) {
238  NotifyUIRequestAccepted();
239
240  // Get the default devices for the request.
241  content::MediaStreamDevices devices;
242  bool audio_allowed = IsDeviceAudioCaptureRequestedAndAllowed();
243  bool video_allowed = IsDeviceVideoCaptureRequestedAndAllowed();
244  if (audio_allowed || video_allowed) {
245    switch (request_.request_type) {
246      case content::MEDIA_OPEN_DEVICE: {
247        const content::MediaStreamDevice* device = NULL;
248        // For open device request, when requested device_id is empty, pick
249        // the first available of the given type. If requested device_id is
250        // not empty, return the desired device if it's available. Otherwise,
251        // return no device.
252        if (audio_allowed &&
253            request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
254          if (!request_.requested_audio_device_id.empty()) {
255            device = MediaCaptureDevicesDispatcher::GetInstance()->
256                GetRequestedAudioDevice(request_.requested_audio_device_id);
257          } else {
258            device = MediaCaptureDevicesDispatcher::GetInstance()->
259                GetFirstAvailableAudioDevice();
260          }
261        } else if (video_allowed &&
262            request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
263          // Pepper API opens only one device at a time.
264          if (!request_.requested_video_device_id.empty()) {
265            device = MediaCaptureDevicesDispatcher::GetInstance()->
266                GetRequestedVideoDevice(request_.requested_video_device_id);
267          } else {
268            device = MediaCaptureDevicesDispatcher::GetInstance()->
269                GetFirstAvailableVideoDevice();
270          }
271        }
272        if (device)
273          devices.push_back(*device);
274        break;
275      }
276      case content::MEDIA_GENERATE_STREAM: {
277        bool get_default_audio_device = audio_allowed;
278        bool get_default_video_device = video_allowed;
279
280        // Get the exact audio or video device if an id is specified.
281        if (audio_allowed && !request_.requested_audio_device_id.empty()) {
282          const content::MediaStreamDevice* audio_device =
283              MediaCaptureDevicesDispatcher::GetInstance()->
284                  GetRequestedAudioDevice(request_.requested_audio_device_id);
285          if (audio_device) {
286            devices.push_back(*audio_device);
287            get_default_audio_device = false;
288          }
289        }
290        if (video_allowed && !request_.requested_video_device_id.empty()) {
291          const content::MediaStreamDevice* video_device =
292              MediaCaptureDevicesDispatcher::GetInstance()->
293                  GetRequestedVideoDevice(request_.requested_video_device_id);
294          if (video_device) {
295            devices.push_back(*video_device);
296            get_default_video_device = false;
297          }
298        }
299
300        // If either or both audio and video devices were requested but not
301        // specified by id, get the default devices.
302        if (get_default_audio_device || get_default_video_device) {
303          MediaCaptureDevicesDispatcher::GetInstance()->
304              GetDefaultDevicesForProfile(profile_,
305                                          get_default_audio_device,
306                                          get_default_video_device,
307                                          &devices);
308        }
309        break;
310      }
311      case content::MEDIA_DEVICE_ACCESS: {
312        // Get the default devices for the request.
313        MediaCaptureDevicesDispatcher::GetInstance()->
314            GetDefaultDevicesForProfile(profile_,
315                                        audio_allowed,
316                                        video_allowed,
317                                        &devices);
318        break;
319      }
320      case content::MEDIA_ENUMERATE_DEVICES: {
321        // Do nothing.
322        NOTREACHED();
323        break;
324      }
325    }  // switch
326
327    // TODO(raymes): We currently set the content permission for non-https
328    // websites for Pepper requests as well. This is temporary and should be
329    // removed.
330    if (update_content_setting) {
331      if ((IsSchemeSecure() && !devices.empty()) ||
332          request_.request_type == content::MEDIA_OPEN_DEVICE) {
333        SetPermission(true);
334      }
335    }
336
337    if (audio_allowed) {
338      profile_->GetHostContentSettingsMap()->UpdateLastUsageByPattern(
339          ContentSettingsPattern::FromURLNoWildcard(request_.security_origin),
340          ContentSettingsPattern::Wildcard(),
341          CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
342    }
343    if (video_allowed) {
344      profile_->GetHostContentSettingsMap()->UpdateLastUsageByPattern(
345          ContentSettingsPattern::FromURLNoWildcard(request_.security_origin),
346          ContentSettingsPattern::Wildcard(),
347          CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
348    }
349  }
350
351  scoped_ptr<content::MediaStreamUI> ui;
352  if (!devices.empty()) {
353    ui = MediaCaptureDevicesDispatcher::GetInstance()->
354        GetMediaStreamCaptureIndicator()->RegisterMediaStream(
355            web_contents_, devices);
356  }
357  content::MediaResponseCallback cb = callback_;
358  callback_.Reset();
359  cb.Run(devices,
360         devices.empty() ?
361             content::MEDIA_DEVICE_NO_HARDWARE : content::MEDIA_DEVICE_OK,
362         ui.Pass());
363}
364
365void MediaStreamDevicesController::Deny(
366    bool update_content_setting,
367    content::MediaStreamRequestResult result) {
368  DLOG(WARNING) << "MediaStreamDevicesController::Deny: " << result;
369  NotifyUIRequestDenied();
370
371  if (update_content_setting) {
372    CHECK_EQ(content::MEDIA_DEVICE_PERMISSION_DENIED, result);
373    SetPermission(false);
374  }
375
376  content::MediaResponseCallback cb = callback_;
377  callback_.Reset();
378  cb.Run(content::MediaStreamDevices(),
379         result,
380         scoped_ptr<content::MediaStreamUI>());
381}
382
383int MediaStreamDevicesController::GetIconID() const {
384  if (HasVideo())
385    return IDR_INFOBAR_MEDIA_STREAM_CAMERA;
386
387  return IDR_INFOBAR_MEDIA_STREAM_MIC;
388}
389
390base::string16 MediaStreamDevicesController::GetMessageText() const {
391  int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO;
392  if (!HasAudio())
393    message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY;
394  else if (!HasVideo())
395    message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY;
396  return l10n_util::GetStringFUTF16(
397      message_id, base::UTF8ToUTF16(GetSecurityOriginSpec()));
398}
399
400base::string16 MediaStreamDevicesController::GetMessageTextFragment() const {
401  int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_PERMISSION_FRAGMENT;
402  if (!HasAudio())
403    message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT;
404  else if (!HasVideo())
405    message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT;
406  return l10n_util::GetStringUTF16(message_id);
407}
408
409bool MediaStreamDevicesController::HasUserGesture() const {
410  return request_.user_gesture;
411}
412
413GURL MediaStreamDevicesController::GetRequestingHostname() const {
414  return request_.security_origin;
415}
416
417void MediaStreamDevicesController::PermissionGranted() {
418  GURL origin(GetSecurityOriginSpec());
419  if (origin.SchemeIsSecure()) {
420    UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
421                              kAllowHttps, kPermissionActionsMax);
422  } else {
423    UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
424                              kAllowHttp, kPermissionActionsMax);
425  }
426  Accept(true);
427}
428
429void MediaStreamDevicesController::PermissionDenied() {
430  UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
431                            kDeny, kPermissionActionsMax);
432  Deny(true, content::MEDIA_DEVICE_PERMISSION_DENIED);
433}
434
435void MediaStreamDevicesController::Cancelled() {
436  UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
437                            kCancel, kPermissionActionsMax);
438  Deny(false, content::MEDIA_DEVICE_PERMISSION_DISMISSED);
439}
440
441void MediaStreamDevicesController::RequestFinished() {
442  delete this;
443}
444
445bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
446  // The request from internal objects like chrome://URLs is always allowed.
447  if (CheckAllowAllMediaStreamContentForOrigin(profile_,
448                                               request_.security_origin)) {
449    return true;
450  }
451
452  struct {
453    bool has_capability;
454    const char* policy_name;
455    const char* list_policy_name;
456    ContentSettingsType settings_type;
457  } device_checks[] = {
458    { IsDeviceAudioCaptureRequestedAndAllowed(), prefs::kAudioCaptureAllowed,
459      prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC },
460    { IsDeviceVideoCaptureRequestedAndAllowed(), prefs::kVideoCaptureAllowed,
461      prefs::kVideoCaptureAllowedUrls,
462      CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA },
463  };
464
465  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) {
466    if (!device_checks[i].has_capability)
467      continue;
468
469    MediaStreamDevicePolicy policy =
470        GetDevicePolicy(profile_,
471                        request_.security_origin,
472                        device_checks[i].policy_name,
473                        device_checks[i].list_policy_name);
474
475    if (policy == ALWAYS_DENY)
476      return false;
477
478    if (policy == POLICY_NOT_SET) {
479      // Only load content settings from secure origins unless it is a
480      // content::MEDIA_OPEN_DEVICE (Pepper) request.
481      if (!IsSchemeSecure() &&
482          request_.request_type != content::MEDIA_OPEN_DEVICE) {
483        return false;
484      }
485      if (profile_->GetHostContentSettingsMap()->GetContentSetting(
486              request_.security_origin, request_.security_origin,
487              device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) !=
488              CONTENT_SETTING_ALLOW) {
489        return false;
490      }
491    }
492    // If we get here, then either policy is set to ALWAYS_ALLOW or the content
493    // settings allow the request by default.
494  }
495
496  return true;
497}
498
499int MediaStreamDevicesController::FilterBlockedByDefaultDevices() {
500  int requested_devices = 0;
501
502  if (IsDeviceAudioCaptureRequestedAndAllowed()) {
503    if (profile_->GetHostContentSettingsMap()->GetContentSetting(
504        request_.security_origin,
505        request_.security_origin,
506        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
507        NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
508      request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
509          MEDIA_BLOCKED_BY_USER_SETTING;
510    } else {
511      ++requested_devices;
512    }
513  }
514
515  if (IsDeviceVideoCaptureRequestedAndAllowed()) {
516    if (profile_->GetHostContentSettingsMap()->GetContentSetting(
517        request_.security_origin,
518        request_.security_origin,
519        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
520        NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
521      request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
522          MEDIA_BLOCKED_BY_USER_SETTING;
523    } else {
524      ++requested_devices;
525    }
526  }
527
528  return requested_devices;
529}
530
531bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const {
532  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
533  // TODO(markusheintz): Replace CONTENT_SETTINGS_TYPE_MEDIA_STREAM with the
534  // appropriate new CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC and
535  // CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA.
536  ContentSetting current_setting =
537      profile_->GetHostContentSettingsMap()->GetDefaultContentSetting(
538          CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL);
539  return (current_setting == CONTENT_SETTING_BLOCK);
540}
541
542bool MediaStreamDevicesController::IsSchemeSecure() const {
543  return request_.security_origin.SchemeIsSecure() ||
544      request_.security_origin.SchemeIs(extensions::kExtensionScheme);
545}
546
547void MediaStreamDevicesController::SetPermission(bool allowed) const {
548  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
549  ContentSettingsPattern primary_pattern =
550      ContentSettingsPattern::FromURLNoWildcard(request_.security_origin);
551  // Check the pattern is valid or not. When the request is from a file access,
552  // no exception will be made.
553  if (!primary_pattern.IsValid())
554    return;
555
556  ContentSetting content_setting = allowed ?
557      CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
558  if (request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE) !=
559      request_permissions_.end()) {
560    profile_->GetHostContentSettingsMap()->SetContentSetting(
561        primary_pattern,
562        ContentSettingsPattern::Wildcard(),
563        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
564        std::string(),
565        content_setting);
566  }
567  if (request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE) !=
568      request_permissions_.end()) {
569    profile_->GetHostContentSettingsMap()->SetContentSetting(
570        primary_pattern,
571        ContentSettingsPattern::Wildcard(),
572        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
573        std::string(),
574        content_setting);
575  }
576}
577
578void MediaStreamDevicesController::NotifyUIRequestAccepted() const {
579  if (!content_settings_)
580    return;
581
582  content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
583                                                request_permissions_);
584}
585
586void MediaStreamDevicesController::NotifyUIRequestDenied() {
587  if (!content_settings_)
588    return;
589
590  if (IsDeviceAudioCaptureRequestedAndAllowed()) {
591    request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
592        MEDIA_BLOCKED_BY_USER;
593  }
594  if (IsDeviceVideoCaptureRequestedAndAllowed()) {
595    request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
596        MEDIA_BLOCKED_BY_USER;
597  }
598
599  content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
600                                                request_permissions_);
601}
602
603bool MediaStreamDevicesController::IsDeviceAudioCaptureRequestedAndAllowed()
604    const {
605  MediaStreamTypeSettingsMap::const_iterator it =
606      request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE);
607  return (it != request_permissions_.end() && IsCaptureDeviceRequestAllowed() &&
608          it->second.permission == MEDIA_ALLOWED);
609}
610
611bool MediaStreamDevicesController::IsDeviceVideoCaptureRequestedAndAllowed()
612    const {
613  MediaStreamTypeSettingsMap::const_iterator it =
614      request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE);
615  return (it != request_permissions_.end() && IsCaptureDeviceRequestAllowed() &&
616          it->second.permission == MEDIA_ALLOWED);
617}
618
619bool MediaStreamDevicesController::IsCaptureDeviceRequestAllowed() const {
620#if defined(OS_ANDROID)
621  // Don't approve device requests if the tab was hidden.
622  // TODO(qinmin): Add a test for this. http://crbug.com/396869.
623  return web_contents_->GetRenderWidgetHostView()->IsShowing();
624#endif
625  return true;
626}
627