1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media/media_stream_devices_controller.h"
7 #include "base/metrics/histogram.h"
8 #include "base/prefs/scoped_user_pref_update.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/values.h"
11 #include "chrome/browser/content_settings/host_content_settings_map.h"
12 #include "chrome/browser/content_settings/tab_specific_content_settings.h"
13 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
14 #include "chrome/browser/media/media_stream_capture_indicator.h"
15 #include "chrome/browser/media/media_stream_device_permissions.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/content_settings/core/browser/content_settings_provider.h"
22 #include "components/content_settings/core/common/content_settings.h"
23 #include "components/content_settings/core/common/content_settings_pattern.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/render_widget_host_view.h"
27 #include "content/public/common/media_stream_request.h"
28 #include "extensions/common/constants.h"
29 #include "grit/theme_resources.h"
30 #include "ui/base/l10n/l10n_util.h"
32 using content::BrowserThread;
36 bool HasAvailableDevicesForRequest(const content::MediaStreamRequest& request) {
37 const content::MediaStreamDevices* audio_devices =
38 request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ?
39 &MediaCaptureDevicesDispatcher::GetInstance()
40 ->GetAudioCaptureDevices() :
43 const content::MediaStreamDevices* video_devices =
44 request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ?
45 &MediaCaptureDevicesDispatcher::GetInstance()
46 ->GetVideoCaptureDevices() :
49 // Check if we're being asked for audio and/or video and that either of those
50 // lists is empty. If they are, we do not have devices available for the
52 // TODO(tommi): It's kind of strange to have this here since if we fail this
53 // test, there'll be a UI shown that indicates to the user that access to
54 // non-existing audio/video devices has been denied. The user won't have
55 // any way to change that but there will be a UI shown which indicates that
57 if ((audio_devices != NULL && audio_devices->empty()) ||
58 (video_devices != NULL && video_devices->empty())) {
62 // Note: we check requested_[audio|video]_device_id before dereferencing
63 // [audio|video]_devices. If the requested device id is non-empty, then
64 // the corresponding device list must not be NULL.
66 if (!request.requested_audio_device_id.empty() &&
67 !audio_devices->FindById(request.requested_audio_device_id)) {
71 if (!request.requested_video_device_id.empty() &&
72 !video_devices->FindById(request.requested_video_device_id)) {
79 enum DevicePermissionActions {
84 kPermissionActionsMax // Must always be last!
89 MediaStreamDevicesController::MediaStreamTypeSettings::MediaStreamTypeSettings(
90 Permission permission, const std::string& requested_device_id):
91 permission(permission), requested_device_id(requested_device_id) {}
93 MediaStreamDevicesController::MediaStreamTypeSettings::
94 MediaStreamTypeSettings(): permission(MEDIA_NONE) {}
96 MediaStreamDevicesController::MediaStreamTypeSettings::
97 ~MediaStreamTypeSettings() {}
99 MediaStreamDevicesController::MediaStreamDevicesController(
100 content::WebContents* web_contents,
101 const content::MediaStreamRequest& request,
102 const content::MediaResponseCallback& callback)
103 : web_contents_(web_contents),
105 callback_(callback) {
106 profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
107 content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
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)));
128 request_permissions_.insert(std::make_pair(
129 content::MEDIA_DEVICE_AUDIO_CAPTURE,
130 MediaStreamTypeSettings(MEDIA_ALLOWED,
131 request.requested_audio_device_id)));
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)));
145 request_permissions_.insert(std::make_pair(
146 content::MEDIA_DEVICE_VIDEO_CAPTURE,
147 MediaStreamTypeSettings(MEDIA_ALLOWED,
148 request.requested_video_device_id)));
153 MediaStreamDevicesController::~MediaStreamDevicesController() {
154 if (!callback_.is_null()) {
155 callback_.Run(content::MediaStreamDevices(),
156 content::MEDIA_DEVICE_FAILED_DUE_TO_SHUTDOWN,
157 scoped_ptr<content::MediaStreamUI>());
162 void MediaStreamDevicesController::RegisterProfilePrefs(
163 user_prefs::PrefRegistrySyncable* prefs) {
164 prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed,
166 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
167 prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed,
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);
176 // TODO(gbillock): rename? doesn't actually dismiss. More of a 'check profile
177 // and system for compatibility' thing.
178 bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() {
179 // Tab capture is allowed for extensions only and infobar is not shown for
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);
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);
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
197 if (!HasAvailableDevicesForRequest(request_)) {
198 Deny(false, content::MEDIA_DEVICE_NO_HARDWARE);
202 // Check if any allow exception has been made for this request.
203 if (IsRequestAllowedByDefault()) {
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);
215 // Check if the media default setting is set to block.
216 if (IsDefaultMediaAccessBlocked()) {
217 Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
225 bool MediaStreamDevicesController::HasAudio() const {
226 return IsDeviceAudioCaptureRequestedAndAllowed();
229 bool MediaStreamDevicesController::HasVideo() const {
230 return IsDeviceVideoCaptureRequestedAndAllowed();
233 const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
234 return request_.security_origin.spec();
237 void MediaStreamDevicesController::Accept(bool update_content_setting) {
238 NotifyUIRequestAccepted();
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,
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);
258 device = MediaCaptureDevicesDispatcher::GetInstance()->
259 GetFirstAvailableAudioDevice();
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);
268 device = MediaCaptureDevicesDispatcher::GetInstance()->
269 GetFirstAvailableVideoDevice();
273 devices.push_back(*device);
276 case content::MEDIA_GENERATE_STREAM: {
277 bool get_default_audio_device = audio_allowed;
278 bool get_default_video_device = video_allowed;
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);
286 devices.push_back(*audio_device);
287 get_default_audio_device = false;
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);
295 devices.push_back(*video_device);
296 get_default_video_device = false;
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,
311 case content::MEDIA_DEVICE_ACCESS: {
312 // Get the default devices for the request.
313 MediaCaptureDevicesDispatcher::GetInstance()->
314 GetDefaultDevicesForProfile(profile_,
320 case content::MEDIA_ENUMERATE_DEVICES: {
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
330 if (update_content_setting) {
331 if ((IsSchemeSecure() && !devices.empty()) ||
332 request_.request_type == content::MEDIA_OPEN_DEVICE) {
338 profile_->GetHostContentSettingsMap()->UpdateLastUsageByPattern(
339 ContentSettingsPattern::FromURLNoWildcard(request_.security_origin),
340 ContentSettingsPattern::Wildcard(),
341 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC);
344 profile_->GetHostContentSettingsMap()->UpdateLastUsageByPattern(
345 ContentSettingsPattern::FromURLNoWildcard(request_.security_origin),
346 ContentSettingsPattern::Wildcard(),
347 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
351 scoped_ptr<content::MediaStreamUI> ui;
352 if (!devices.empty()) {
353 ui = MediaCaptureDevicesDispatcher::GetInstance()->
354 GetMediaStreamCaptureIndicator()->RegisterMediaStream(
355 web_contents_, devices);
357 content::MediaResponseCallback cb = callback_;
361 content::MEDIA_DEVICE_NO_HARDWARE : content::MEDIA_DEVICE_OK,
365 void MediaStreamDevicesController::Deny(
366 bool update_content_setting,
367 content::MediaStreamRequestResult result) {
368 DLOG(WARNING) << "MediaStreamDevicesController::Deny: " << result;
369 NotifyUIRequestDenied();
371 if (update_content_setting) {
372 CHECK_EQ(content::MEDIA_DEVICE_PERMISSION_DENIED, result);
373 SetPermission(false);
376 content::MediaResponseCallback cb = callback_;
378 cb.Run(content::MediaStreamDevices(),
380 scoped_ptr<content::MediaStreamUI>());
383 int MediaStreamDevicesController::GetIconID() const {
385 return IDR_INFOBAR_MEDIA_STREAM_CAMERA;
387 return IDR_INFOBAR_MEDIA_STREAM_MIC;
390 base::string16 MediaStreamDevicesController::GetMessageText() const {
391 int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO;
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()));
400 base::string16 MediaStreamDevicesController::GetMessageTextFragment() const {
401 int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_PERMISSION_FRAGMENT;
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);
409 bool MediaStreamDevicesController::HasUserGesture() const {
410 return request_.user_gesture;
413 GURL MediaStreamDevicesController::GetRequestingHostname() const {
414 return request_.security_origin;
417 void MediaStreamDevicesController::PermissionGranted() {
418 GURL origin(GetSecurityOriginSpec());
419 if (origin.SchemeIsSecure()) {
420 UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
421 kAllowHttps, kPermissionActionsMax);
423 UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
424 kAllowHttp, kPermissionActionsMax);
429 void MediaStreamDevicesController::PermissionDenied() {
430 UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
431 kDeny, kPermissionActionsMax);
432 Deny(true, content::MEDIA_DEVICE_PERMISSION_DENIED);
435 void MediaStreamDevicesController::Cancelled() {
436 UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
437 kCancel, kPermissionActionsMax);
438 Deny(false, content::MEDIA_DEVICE_PERMISSION_DISMISSED);
441 void MediaStreamDevicesController::RequestFinished() {
445 bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
446 // The request from internal objects like chrome://URLs is always allowed.
447 if (CheckAllowAllMediaStreamContentForOrigin(profile_,
448 request_.security_origin)) {
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 },
465 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) {
466 if (!device_checks[i].has_capability)
469 MediaStreamDevicePolicy policy =
470 GetDevicePolicy(profile_,
471 request_.security_origin,
472 device_checks[i].policy_name,
473 device_checks[i].list_policy_name);
475 if (policy == ALWAYS_DENY)
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) {
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) {
492 // If we get here, then either policy is set to ALWAYS_ALLOW or the content
493 // settings allow the request by default.
499 int MediaStreamDevicesController::FilterBlockedByDefaultDevices() {
500 int requested_devices = 0;
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;
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;
528 return requested_devices;
531 bool 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);
542 bool MediaStreamDevicesController::IsSchemeSecure() const {
543 return request_.security_origin.SchemeIsSecure() ||
544 request_.security_origin.SchemeIs(extensions::kExtensionScheme);
547 void 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())
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(
562 ContentSettingsPattern::Wildcard(),
563 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
567 if (request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE) !=
568 request_permissions_.end()) {
569 profile_->GetHostContentSettingsMap()->SetContentSetting(
571 ContentSettingsPattern::Wildcard(),
572 CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
578 void MediaStreamDevicesController::NotifyUIRequestAccepted() const {
579 if (!content_settings_)
582 content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
583 request_permissions_);
586 void MediaStreamDevicesController::NotifyUIRequestDenied() {
587 if (!content_settings_)
590 if (IsDeviceAudioCaptureRequestedAndAllowed()) {
591 request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
592 MEDIA_BLOCKED_BY_USER;
594 if (IsDeviceVideoCaptureRequestedAndAllowed()) {
595 request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
596 MEDIA_BLOCKED_BY_USER;
599 content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
600 request_permissions_);
603 bool MediaStreamDevicesController::IsDeviceAudioCaptureRequestedAndAllowed()
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);
611 bool MediaStreamDevicesController::IsDeviceVideoCaptureRequestedAndAllowed()
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);
619 bool 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();